require 'date'
require 'find'
require 'ostruct'
require 'logging'
require 'runeblog_version'
require 'global'
require 'helpers-blog'
require 'default'
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 :root, :views, :sequence, :editor
attr_accessor :view # overridden
attr_accessor :post_views, :post_tags, :dirty_views
include Helpers
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, :drafts, :views, :posts)
new_sequence
end
copy_data(:config, repo_root/:data)
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
copy_data(:config, @root/:data)
write_repo_config(root: @root)
get_repo_config
@views = get_views
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 _deploy_local(dir)
log!(enter: __method__, args: [dir], level: 1)
Dir.chdir(dir) do
views = _retrieve_metadata(:views)
views.each do |v|
unless self.view?(v)
puts "#{fx("Warning:", :red)} #{fx(v, :bold)} is not a view"
next
end
system!("cp *html #@root/views/#{v}/remote", show: true)
end
end
rescue => err
_tmp_error(err)
end
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?
xlate cwd: dir, src: @root/:drafts/sourcefile # , 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 RuneBlog::View
@view = arg
_set_publisher
when String
new_view = str2view(arg)
raise NoSuchView(arg) if new_view.nil?
@view = new_view
_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/:sequence).to_i
end
def next_sequence
log!(enter: __method__, level: 3)
@sequence += 1
dump(@sequence, @root/: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")
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]
xlate src: post_entry_name, dst: "/tmp/post_entry.html" # , deps: depend # , debug: true
@_post_entry ||= File.read("/tmp/post_entry.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