if ! defined?(Already_runeblog)
Already_runeblog = nil
require 'date'
require 'find'
require 'ostruct'
require 'logging'
require 'runeblog_version'
require 'helpers-blog'
require 'view'
require 'publish'
require 'post'
require 'pathmagic'
###
class RuneBlog
DotDir = ".blogs"
ConfigFile = "config"
Themes = RuneBlog::Path/"../themes"
make_exception(:FileNotFound, "File $1 was not found")
make_exception(:BlogRepoAlreadyExists, "Blog repo $1 already exists")
make_exception(:CantAssignView, "$1 is not a view")
make_exception(:ViewAlreadyExists, "View $1 already exists")
make_exception(:DirAlreadyExists, "Directory $1 already exists")
make_exception(:CantCreateDir, "Can't create directory $1")
make_exception(:EditorProblem, "Could not edit $1")
make_exception(:NoSuchView, "No such view: $1")
make_exception(:NoBlogAccessor, "Runeblog.blog is not set")
class << self
attr_accessor :blog
include Helpers
end
attr_reader :views, :sequence
attr_accessor :root, :editor, :features
attr_accessor :view # overridden
attr_accessor :post_views, :post_tags, :dirty_views
include Helpers
class Default
# This will all become much more generic later.
def RuneBlog.post_template(num: 0, title: "No title", date: nil, view: "test_view",
teaser: "No teaser", body: "No body", tags: ["untagged"],
views: [], back: "javascript:history.go(-1)", home: "no url")
log!(enter: __method__, args: [num, title, date, view, teaser, body, tags, views, back, home], level: 3)
viewlist = (views + [view.to_s]).join(" ")
taglist = ".tags " + tags.join(" ")
<<~TEXT
.post #{num}
.title #{title}
.pubdate #{date}
.views #{viewlist}
#{taglist}
.teaser
#{teaser}
.end
#{body}
TEXT
end
end
def _tmp_error(err) # FIXME move to helpers
out = "/tmp/blog#{rand(100)}.txt"
File.open(out, "w") do |f|
f.puts err
f.puts err.backtrace.join("\n")
end
puts "Error: See #{out}"
end
def self.create_new_blog_repo(root_rel = ".blogs")
log!(enter: __method__, args: [root_rel])
raise ArgumentError unless root_rel.is_a?(String) && ! root_rel.empty?
self.blog = self # Weird. Like a singleton - dumbass circular dependency?
repo_root = Dir.pwd/root_rel
raise BlogRepoAlreadyExists if Dir.exist?(repo_root)
create_dirs(repo_root)
Dir.chdir(repo_root) do
create_dirs(:data, :config, :drafts, :views, :posts)
new_sequence
end
unless File.exist?(repo_root/"data/VIEW")
copy_data(:config, repo_root/:data)
end
copy_data(:extra, repo_root/:config)
write_repo_config(root: repo_root)
@blog = self.new
@blog
rescue => err
puts "Can't create blog repo: '#{repo_root}' - #{err}"
puts err.backtrace.join("\n")
end
def self.open(root_rel = ".blogs")
log!(enter: __method__, args: [root_rel])
self.blog = self # Weird. Like a singleton - dumbass circular dependency?
blog = self.new(root_rel)
rescue => err
_tmp_error(err)
end
def initialize(root_rel = ".blogs") # always assumes existing blog
log!(enter: "initialize", args: [root_rel])
self.class.blog = self # Weird. Like a singleton - dumbass circular dependency?
@root = Dir.pwd/root_rel
write_repo_config(root: @root) # ?? FIXME
get_repo_config
read_features # top level
@views = retrieve_views
self.view = File.read(@root/"data/VIEW").chomp
md = Dir.pwd.match(%r[.*/views/(.*?)/])
if md
@view_name = md[1]
@view = str2view(@view_name)
end
@sequence = get_sequence
@post_views = []
@post_tags = []
end
def complete_file(name, vars, hash)
debugging = vars.nil?
return if hash.empty?
text = File.read(name)
if vars.nil? # FIXME dumbest hack ever?
vars = {}
hash.values.each {|val| vars[val] = val }
end
hash.each_pair {|key, var| text.gsub!(key, vars[var]) }
File.write(name, text)
end
def _generate_settings(view = nil)
vars = read_vars("#@root/data/universal.lt3")
hash = {/AUTHOR/ => "view.author",
/SITE/ => "view.site",
/FONT/ => "font.family",
/CHARSET/ => :charset,
/LOCALE/ => :locale}
# rubytext.txt - LATER
# complete_file(settings/"rubytext.txt", {}
if view
settings = @root/view/"settings"
### ??? Where to get hash of view-specific vars?
# features.txt - handle specially
fname = settings/"features.txt"
# view.txt
complete_file(settings/"view.txt",
/AUTHOR/ => "view.author",
/TITLE/ => "view.title",
/SUBTITLE/ => "view.subtitle",
/SITE/ => "view.site")
# publish.txt
complete_file(settings/"publish.txt",
/USER/ => "publish.user",
/SERVER/ => "publish.server",
/DOCROOT/ => "publish.docroot",
/PATH/ => "publish.path",
/PROTO/ => "publish.proto")
# recent.txt - SKIP THIS?
complete_file(settings/"recent.txt", {})
end
end
def _generate_global
vars = read_vars("#@root/data/universal.lt3")
gfile = "#@root/data/global.lt3"
hash = {/AUTHOR/ => "univ.author",
/SITE/ => "univ.site",
/FONT/ => "font.family",
/CHARSET/ => :charset,
/LOCALE/ => :locale}
complete_file(gfile, vars, hash)
_generate_settings
end
def _deploy_local(dir)
log!(enter: __method__, args: [dir], level: 1)
Dir.chdir(dir) do
views = _retrieve_metadata(:views)
views.each do |v|
next unless _check_view?(v)
system!("cp *html #@root/views/#{v}/remote", show: true)
end
end
rescue => err
_tmp_error(err)
end
# FIXME reconcile with _get_draft data
def _retrieve_metadata(key)
key = key.to_s
lines = File.readlines("metadata.txt")
lines = lines.grep(/^#{key}: /)
case lines.size
when 0
result = nil # not found
when 1
front = "#{key}: "
n = front.size
str = lines.first.chomp[n..-1]
case key
when "views", "tags" # plurals
result = str.split
else
result = str
end
else
raise "Too many #{key} instances in metadata.txt!"
end
return result
rescue => err
_tmp_error(err)
end
def process_post(sourcefile)
log!(enter: __method__, args: [sourcefile], level: 2)
nslug = sourcefile.sub(/.lt3/, "")
dir = @root/:posts/nslug
create_dirs(dir)
# FIXME dependencies?
preprocess cwd: dir, src: @root/:drafts/sourcefile, dst: @root/:posts/sourcefile.sub(/.lt3/, ".html"), # ZZZ
mix: "liveblog" # , debug: true
_deploy_local(dir)
rescue => err
_tmp_error(err)
end
def inspect
log!(enter: __method__, level: 3)
str = "blog: "
ivars = ["@root", "@sequence"] # self.instance_variables
ivars.each do |iv|
val = self.instance_variable_get(iv)
str << "#{iv}: #{val} "
end
str
end
def view?(name)
log!(enter: __method__, args: [name], level: 3)
raise ArgumentError unless name.is_a?(String) && ! name.empty?
views.any? {|x| x.name == name }
end
def view(name = nil)
log!(enter: __method__, args: [name], level: 3)
raise ArgumentError unless name.nil? || (name.is_a?(String) && ! name.empty?)
name.nil? ? @view : str2view(name)
end
def str2view(str)
log!(enter: __method__, args: [str], level: 3)
raise ArgumentError unless str.is_a?(String) && ! str.empty?
@views.find {|x| x.name == str }
end
def _set_publisher
log!(enter: __method__, level: 3)
@view.publisher = RuneBlog::Publishing.new(@view.to_s) # FIXME refactor
rescue => err
_tmp_error(err)
end
def view=(arg)
log!(enter: __method__, args: [arg], level: 2)
case arg
when "[no view]"
# puts "Warning: No current view set"
@view = nil
when RuneBlog::View
@view = arg
read_features(@view)
@view.get_globals
_set_publisher
when String
new_view = str2view(arg)
raise NoSuchView(arg) if new_view.nil?
@view = new_view
read_features(@view)
@view.get_globals
_set_publisher
else
raise CantAssignView(arg.class.to_s)
end
rescue => err
_tmp_error(err)
end
def get_sequence
log!(enter: __method__, level: 3)
File.read(@root/"data/sequence").to_i
end
def next_sequence
log!(enter: __method__, level: 3)
@sequence += 1
dump(@sequence, @root/"data/sequence")
@sequence
end
def viewdir(v = nil) # delete?
log!(enter: __method__, args: [v], level: 3)
v ||= @view
v = str2view(v) if v.is_a?(String)
raise ArgumentError unless v.nil? || v.is_a?(RuneBlog::View)
return @root/:views/v
end
def self.exist?
log!(enter: __method__, level: 3)
Dir.exist?(DotDir)
end
def mark_last_published(str)
log!(enter: __method__, args: [str], level: 2)
dump(str, "#{self.view.dir}/last_published")
end
def add_view(view_name)
log!(enter: __method__, args: [view_name], level: 2)
view = RuneBlog::View.new(view_name)
self.view = view # current view
File.write(@root/"data/VIEW", view_name)
@views << view # all views
view
end
def make_empty_view_tree(view_name)
log!(enter: __method__, args: [view_name], level: 2)
Dir.chdir(@root) do
cmd = "cp -r #{RuneBlog::Path}/../empty_view views/#{view_name}"
system!(cmd)
end
rescue => err
_tmp_error(err)
end
def check_valid_new_view(view_name)
log!(enter: __method__, args: [view_name], level: 3)
raise ArgumentError unless view_name.is_a?(String)
raise ArgumentError if view_name.empty?
names = self.views.map(&:to_s)
bad = names.include?(view_name)
raise ViewAlreadyExists(view_name) if bad
vdir = @root/:views/view_name
raise DirAlreadyExists(view_name) if Dir.exist?(vdir)
return true # hm?
end
def create_view(view_name)
log!(enter: __method__, args: [view_name], level: 2)
make_empty_view_tree(view_name)
add_view(view_name)
mark_last_published("Initial creation")
system("cp #@root/data/global.lt3 #@root/views/#{view_name}/themes/standard/global.lt3")
@view.get_globals
rescue => err
_tmp_error(err)
end
def delete_view(name, force = false)
log!(enter: __method__, args: [name, force])
raise ArgumentError unless name.is_a?(String) && ! name.empty?
if force
vname = @root/:views/name
system!("rm -rf #{vname}")
@views -= [str2view(name)]
end
end
def view_files
log!(enter: __method__, level: 2)
vdir = self.view.dir
files = [vdir/"index.html"]
files += posts.map {|x| vdir/x }
files.reject! {|f| File.mtime(f) < File.mtime(vdir/:last_published) }
end
def post_lookup(postid) # side-effect?
log!(enter: __method__, args: [postid], level: 2)
slug = title = date = teaser_text = nil
dir_posts = @vdir/:posts
posts = Dir.entries(dir_posts).grep(/^\d\d\d\d/).map {|x| dir_posts/x }
posts.select! {|x| File.directory?(x) }
post = posts.select {|x| File.basename(x).to_i == postid }
raise "Error: More than one post #{postid}" if post.size > 1
postdir = post.first
vp = RuneBlog::ViewPost.new(self.view, postdir)
vp
rescue => err
_tmp_error(err)
end
def index_entry(slug)
log!(enter: __method__, args: [slug], level: 2)
id = slug.to_i
text = nil
@theme = @view.dir/"themes/standard"
post_entry_name = @theme/"blog/post_entry.lt3"
depend = [post_entry_name]
html = "/tmp/post_entry.html"
preprocess src: post_entry_name, dst: html,
call: ".nopara" # , deps: depend # , debug: true
@_post_entry = File.read(html)
vp = post_lookup(id)
nslug, aslug, title, date, teaser_text =
vp.nslug, vp.aslug, vp.title, vp.date, vp.teaser_text
path = vp.path
url = aslug + ".html"
date = ::Date.parse(date)
date = date.strftime("%B %e