class PBSimply

Constants

JSON_LIB
POST_PROCESSORS

Attributes

indexes[R]

Public Class Methods

load_config() click to toggle source

Load config file.

# File lib/pbsimply.rb, line 158
def self.load_config
  config = nil
  begin
    File.open(".pbsimply.yaml") do |f|
      config = Psych.unsafe_load(f)
    end
  rescue
    abort "Failed to load config file (./.pbsimply.yaml)"
  end

  # Required values
  config["outdir"] or abort "Output directory is not set (outdir)."
  config["template"] ||= "./template.html"

  config
end
new(config) click to toggle source
# File lib/pbsimply.rb, line 213
def initialize(config)
  @config = config
  @docobject = {}
  @this_time_processed = []

  # --metadata-file
  @frontmatter = {}

  @refresh = false # Force generate all documents.
  @skip_index = false # Don't register to index.
  @outfile = nil # Fixed output filename
  @add_meta = nil
  @accs = nil
  @accs_index = {}
  @now = Time.now
end

Public Instance Methods

doc() click to toggle source

Accessor reader.

# File lib/pbsimply.rb, line 266
def doc
  @docobject
end
generate(dir, filename, frontmatter) click to toggle source
# File lib/pbsimply.rb, line 482
def generate(dir, filename, frontmatter)
  print_fileproc_msg(filename) # at sub-class

  # Preparing and pre script.
  orig_filepath = [dir, filename].join("/")
  ext = File.extname(filename)
  procdoc = sprintf(".current_document%s", ext)
  pre_plugins(procdoc, frontmatter)

  doc = process_document(dir, filename, frontmatter, orig_filepath, ext, procdoc) # at sub-class

  ##### Post eRuby
  if @config["post_eruby"]
    STDERR.puts "Porcessing with eRuby."
    doc = ERB.new(doc, nil, "%<>").result(binding)
  end

  # Write out
  outext = frontmatter["force_ext"] || ".html"
  outpath = case
  when @outfile
    @outfile
  when @accs_processing
    File.join(@config["outdir"], @dir, "index") + outext
  else
    File.join(@config["outdir"], @dir, File.basename(filename, ".*")) + outext
  end

  File.open(outpath, "w") do |f|
    f.write(doc)
  end

  # Mark processed
  @this_time_processed.push({source: orig_filepath, dest: outpath})
end
load_index() click to toggle source

Load document index database (.indexes.${ext}).

# File lib/pbsimply.rb, line 252
def load_index
  if @db.exist?
    @indexes = @db.load
  else
    @indexes = Hash.new
  end
  @docobject[:indexes] = @indexes
end
main() click to toggle source

Run PureBuilder Simply.

# File lib/pbsimply.rb, line 369
def main
  # If target file is regular file, run as single mode.
  @singlemode = true if File.file?(@dir)

  # Check single file mode.
  if @singlemode
    # Single file mode
    if @dir =~ %r:(.*)/([^/]+):
      dir = $1
      filename = $2
    else
      dir = "."
      filename = @dir
    end
    @dir = dir
    setup_config(dir)

    load_index

    frontmatter, pos = read_frontmatter(dir, filename)
    frontmatter = @frontmatter.merge frontmatter
    @index = frontmatter

    ext = File.extname filename
    File.open(File.join(dir, filename)) do |f|
      f.seek(pos)
      File.open(".current_document#{ext}", "w") {|fo| fo.write f.read}
    end

    generate(dir, filename, frontmatter)

    post_plugins(frontmatter)

  else
    # Normal (directory) mode.
    setup_config(@dir)
    load_index

    @accs = true if File.exist?(File.join(@dir, ".accs.yaml"))

    # Check existing in indexes.
    @indexes.delete_if {|k,v| ! File.exist?([@dir, k].join("/")) }

    proc_dir
  end
ensure
  # Clean up temporary files.
  Dir.children(".").each do |fn|
    if fn[0, 22] == ".pbsimply-defaultfiles.yaml" or
       fn[0, 21] == ".pbsimply-frontmatter" or
       fn[0, 17] == ".current_document"
      File.delete fn
    end
  end
end
post_plugins(frontmatter=nil) click to toggle source
# File lib/pbsimply.rb, line 449
def post_plugins(frontmatter=nil)
  if File.directory?(".post_generate")

    STDERR.puts("Processing with post plugins")

    @this_time_processed.each do |v|
      STDERR.puts "Processing #{v[:dest]} (from #{v[:source]})"
      procdoc = v[:dest]
      frontmatter ||= @indexes[File.basename v[:source]]
      File.open(".pbsimply-frontmatter.json", "w") {|f| f.write JSON_LIB.dump(frontmatter)}
      Dir.entries(".post_generate").sort.each do |script_file|
        next if script_file =~ /^\./
        STDERR.puts "Running script: #{script_file}"
        script_file = File.join(".post_generate", script_file)
        post_script_result = nil
        script_cmdline = case
        when File.executable?(script_file)
          [script_file, procdoc]
        when POST_PROCESSORS[File.extname(script_file)]
          [POST_PROCESSORS[File.extname(script_file)], script_file, procdoc]
        else
          ["perl", script_file, procdoc]
        end
        IO.popen({"pbsimply_frontmatter" => ".pbsimply-frontmatter.json", "pbsimply_indexes" => @db.path}, script_cmdline) do |io|
          post_script_result = io.read
        end

        File.open(procdoc, "w") {|f| f.write post_script_result}
      end
    end
  end
end
pre_plugins(procdoc, frontmatter) click to toggle source
# File lib/pbsimply.rb, line 425
def pre_plugins(procdoc, frontmatter)
  if File.directory?(".pre_generate")
    STDERR.puts("Processing with pre plugins")
    script_file = File.join(".pre_generate", script_file)
    Dir.entries(".pre_generate").sort.each do |script_file|
      next if script_file =~ /^\./
      STDERR.puts "Running script: #{File.basename script_file}"
      pre_script_result = nil
      script_cmdline = case
      when File.executable?(script_file)
        [script_file, procdoc]
      when POST_PROCESSORS[File.extname(script_file)]
        [POST_PROCESSORS[File.extname(script_file)], script_file, procdoc]
      else
        ["perl", script_file, procdoc]
      end
      IO.popen({"pbsimply_doc_frontmatter" => YAML.dump(frontmatter)}, script_cmdline) do |io|
        pre_script_result = io.read
      end
      File.open(procdoc, "w") {|f| f.write pre_script_result}
    end
  end
end
proc_dir() click to toggle source

Directory mode’s main function. Read Frontmatters from all documents and proc each documents.

# File lib/pbsimply.rb, line 279
def proc_dir
  draft_articles = []
  target_docs = []
  @indexes_orig = {}
  STDERR.puts "in #{@dir}..."

  STDERR.puts "Checking Frontmatter..."
  Dir.foreach(@dir) do |filename|
    next if filename == "." || filename == ".."
    if filename =~ /^\./ || filename =~ /^draft-/
      draft_articles.push filename.sub(/^(?:\.|draft-)/, "")
      next
    end
    next unless File.file?([@dir, filename].join("/"))

    if !@ignore_ext and not target_file_extensions.include? File.extname filename
      next
    end

    STDERR.puts "Checking frontmatter in #{filename}"
    frontmatter, pos = read_frontmatter(@dir, filename)
    frontmatter = @frontmatter.merge frontmatter
    frontmatter.merge!(@add_meta) if @add_meta

    if frontmatter["draft"]
      @indexes.delete(filename) if @indexes[filename]
      draft_articles.push filename
      next
    end

    @indexes_orig[filename] = @indexes[filename]
    @indexes[filename] = frontmatter

    # Push to target documents without checking modification.
    target_docs.push([filename, frontmatter, pos])
  end

  # Delete turn to draft article.
  draft_articles.each do |df|
    STDERR.puts "#{df} was turn to draft. deleting..."
    [df, (df + ".html"), File.basename(df, ".*"), (File.basename(df, ".*") + ".html")].each do |tfn|
      tfp = File.join(@config["outdir"], @dir, tfn)
      File.delete tfp if File.file?(tfp)
    end
  end

  # Save index.
  @db.dump(@indexes) unless @skip_index

  STDERR.puts "Blessing..."

  # Modify frontmatter `BLESSING`
  target_docs.each do |filename, frontmatter, pos|
    if @config["bless_style"] == "cmd"
      bless_cmd(frontmatter)
    else
      bless_ruby(frontmatter)
    end
  end

  STDERR.puts "Checking modification..."

  target_docs.delete_if {|filename, frontmatter, pos| !check_modify([@dir, filename], frontmatter)}

  STDERR.puts "Okay, Now ready. Process documents..."

  # Proccess documents
  target_docs.each do |filename, frontmatter, pos|
    ext = File.extname filename
    @index = frontmatter
    File.open(File.join(@dir, filename)) do |f|
      f.seek(pos)
      File.open(".current_document#{ext}", "w") {|fo| fo.write f.read}
    end

    STDERR.puts "Processing #{filename}"
    generate(@dir, filename, frontmatter)
  end

  @db.dump(@indexes) unless @skip_index

  post_plugins

  # ACCS processing
  if @accs && !target_docs.empty?
    process_accs
  end
end
process_accs() click to toggle source

letsaccs

This method called on the assumption that processed all documents and run as directory mode.

# File lib/pbsimply.rb, line 521
def process_accs
  STDERR.puts "Processing ACCS index..."
  if File.exist?(File.join(@dir, ".accsindex.erb"))
    erbtemplate = File.read(File.join(@dir, ".accsindex.erb"))
  elsif File.exist?(".accsindex.erb")
    erbtemplate = File.read(".accsindex.erb")
  else
    erbtemplate = ACCS::INDEX
  end

  # Get infomation
  @accs_index = Psych.unsafe_load(File.read([@dir, ".accs.yaml"].join("/")))

  @accs_index["title"] ||= (@config["accs_index_title"] || "Index")
  @accs_index["date"] ||= Time.now.strftime("%Y-%m-%d")
  @accs_index["pagetype"] = "accs_index"

  @index = @frontmatter.merge @accs_index

  doc = ERB.new(erbtemplate, trim_mode: "%<>").result(binding)
  File.open(File.join(@dir, ".index.md"), "w") do |f|
    f.write doc
  end

  accsmode
  @dir = File.join(@dir, ".index.md")
  main
end
setup_config(dir) click to toggle source

initialize phase,

# File lib/pbsimply.rb, line 176
def setup_config(dir)
  ENV["pbsimply_outdir"] = @config["outdir"]
  @docobject[:config] = @config

  if @singlemode
    outdir = [@config["outdir"], @dir.sub(%r:/[^/]*$:, "")].join("/")
  else
    outdir = [@config["outdir"], @dir].join("/")
  end

  p dir

  # Format for Indexes database
  @db = case @config["dbstyle"]
  when "yaml"
    DocDB::YAML.new(dir)
  when "json"
    DocDB::JSON.new(dir)
  when "oj"
    DocDB::Oj.new(dir)
  else
    DocDB::Marshal.new(dir)
  end

  @frontmatter.merge!(@config["default_meta"]) if @config["default_meta"]

  # Merge ACCS Frontmatter
  if @accs_processing && @config["alt_frontmatter"]
    @frontmatter.merge!(@config["alt_frontmatter"])
  end

  unless File.exist? outdir
    STDERR.puts "destination directory is not exist. creating (only one step.)"
    FileUtils.mkdir_p outdir
  end
end
target_file_extensions() click to toggle source
# File lib/pbsimply.rb, line 261
def target_file_extensions
  [".md"]
end
treat_cmdline(dir=nil) click to toggle source

Process command-line

# File lib/pbsimply.rb, line 231
def treat_cmdline(dir=nil)
  # Options definition.
  opts = OptionParser.new
  opts.on("-f", "--force-refresh") { @refresh = true }
  opts.on("-X", "--ignore-ext") { @ignore_ext = true }
  opts.on("-I", "--skip-index") { @skip_index = true }
  opts.on("-o FILE", "--output") {|v| @outfile = v }
  opts.on("-m FILE", "--additional-metafile") {|v| @add_meta = Psych.unsafe_load(File.read(v))}
  opts.parse!(ARGV)

  if File.exist?(".pbsimply-bless.rb")
    require "./.pbsimply-bless.rb"
  end

  # Set target directory.
  @dir = ARGV.shift unless dir
  @dir ||= "."
  ENV["pbsimply_subdir"] = @dir
end

Private Instance Methods

accsmode() click to toggle source

Turn on ACCS processing mode.

# File lib/pbsimply.rb, line 557
def accsmode
  @accs_processing = true
  @singlemode = true
  @skip_index = true
end
autobless(frontmatter) click to toggle source

Blessing automatic method with configuration.

# File lib/pbsimply.rb, line 818
def autobless(frontmatter)
  catch(:accs_rel) do
    # find Next/Prev page on accs
    if @accs && @config["blessmethod_accs_rel"]
      # Preparing. Run at once.
      if !@article_order
        @rev_article_order_index = {}

        case @config["blessmethod_accs_rel"]
        when "numbering"
          @article_order = @indexes.to_a.sort_by {|i| i[1]["_filename"].to_i }
        when "date"
          begin
            @article_order = @indexes.to_a.sort_by {|i| i[1]["date"]}
          rescue
            abort "*** Automatic Blessing Method Error: Maybe some article have no date."
          end
        when "timestamp"
          begin
            @article_order = @indexes.to_a.sort_by {|i| i[1]["timestamp"]}
          rescue
            abort "*** Automatic Blessing Method Error: Maybe some article have no timetsamp."
          end
        when "lexical"
          @article_order = @indexes.to_a.sort_by {|i| i[1]["_filename"]}
        end
        @article_order.each_with_index {|x,i| @rev_article_order_index[x[0]] = i }
      end

      throw(:accs_rel) unless index = @rev_article_order_index[frontmatter["_filename"]]
      if @article_order[index + 1]
        frontmatter["next_article"] = {"url" => @article_order[index + 1][1]["page_url"],
                                       "title" => @article_order[index + 1][1]["title"]}
      end
      if index > 0
        frontmatter["prev_article"] = {"url" => @article_order[index - 1][1]["page_url"],
                                       "title" => @article_order[index - 1][1]["title"]}
      end
    end
  end
end
bless_cmd(frontmatter) click to toggle source
# File lib/pbsimply.rb, line 801
def bless_cmd(frontmatter)
  File.open(".pbsimply-frontmatter.json", "w") {|f| f.write JSON_LIB.dump(frontmatter) }
  # BLESSING (Always)
  if @config["bless_cmd"]
    (Array === @config["bless_cmd"] ? system(*@config["bless_cmd"]) : system(@config["bless_cmd"]) ) or abort "*** BLESS COMMAND RETURNS NON-ZERO STATUS"
  end
  # BLESSING (ACCS)
  if @config["bless_accscmd"]
    (Array === @config["bless_accscmd"] ? system({"pbsimply_frontmatter" => ".pbsimply-frontmatter.json", "pbsimply_indexes" => @db.path}, *@config["bless_accscmd"]) : system({"pbsimply_frontmatter" => ".pbsimply-frontmatter.json", "pbsimply_indexes" => @db.path}, @config["bless_accscmd"]) ) or abort "*** BLESS COMMAND RETURNS NON-ZERO STATUS"
  end
  mod_frontmatter = JSON.load(File.read(".pbsimply-frontmatter.json"))
  frontmatter.replace(mod_frontmatter)

  autobless(frontmatter)
end
bless_ruby(frontmatter) click to toggle source
# File lib/pbsimply.rb, line 765
def bless_ruby(frontmatter)
  # BLESSING (Always)
  if PureBuilder.const_defined?(:BLESS) && Proc === PureBuilder::BLESS
    begin
      PureBuilder::BLESS.(frontmatter, self)
    rescue
      STDERR.puts "*** BLESSING PROC ERROR ***"
      raise
    end
  end

  # BLESSING (ACCS)
  if @accs && PureBuilder::ACCS.const_defined?(:BLESS) && Proc === PureBuilder::ACCS::BLESS
    begin
      PureBuilder::ACCS::BLESS.(frontmatter, self)
    rescue
      STDERR.puts "*** ACCS BLESSING PROC ERROR ***"
      raise
    end
  end

  # ACCS DEFINITIONS
  if @accs
    if Proc === PureBuilder::ACCS::DEFINITIONS[:next]
      i = PureBuilder::ACCS::DEFINITIONS[:next].call(frontmatter, self)
      frontmatter["next_article"] = i if i
    end
    if Proc === PureBuilder::ACCS::DEFINITIONS[:prev]
      i = PureBuilder::ACCS::DEFINITIONS[:prev].call(frontmatter, self)
      frontmatter["prev_article"] = i if i
    end
  end

  autobless(frontmatter)
end
check_modify(path, frontmatter) click to toggle source

Check is the article modified? (or force update?)

# File lib/pbsimply.rb, line 742
def check_modify(path, frontmatter)
  modify = true
  index = @indexes_orig[path[1]].dup || {}
  frontmatter = @db.cmp_obj(frontmatter)
  index.delete("_last_proced")
  frontmatter.delete("_last_proced")

  if index == frontmatter
    STDERR.puts "#{path[1]} is not modified."
    modify = false
  else
    STDERR.puts "#{path[1]} last modified at #{frontmatter["_mtime"]}, last processed at #{@indexes_orig[path[1]]&.[]("_last_proced") || 0}"
    frontmatter["last_update"] = @now.strftime("%Y-%m-%d %H:%M:%S")
  end

  if @refresh
    # Refresh (force update) mode.
    true
  else
    modify
  end
end
read_frontmatter(dir, filename) click to toggle source

Read Frontmatter from the document. This method returns frontmatter, pos. pos means position at end of Frontmatter on the file.

# File lib/pbsimply.rb, line 566
def read_frontmatter(dir, filename)
  frontmatter = nil
  pos = nil

  if File.exist? File.join(dir, ".meta." + filename)
    # Load standalone metadata YAML.
    frontmatter = Psych.unsafe_load(File.read(File.join(dir, (".meta." + filename))))
    pos = 0
  else

    case File.extname filename
    when ".md"

      # Load Markdown's YAML frontmatter.
      File.open(File.join(dir, filename)) do |f|
        l = f.gets
        next unless l && l.chomp == "---"

        lines = []

        while l = f.gets
          break if l.nil?

          break if  l.chomp == "---"
          lines.push l
        end

        next if f.eof?

        begin
          frontmatter = Psych.unsafe_load(lines.join)
        rescue => e
          STDERR.puts "!CRITICAL: Cannot parse frontmatter."
          raise e
        end

        pos = f.pos
      end

    when ".rst"
      # ReSTRUCTURED Text

      File.open(File.join(dir, filename)) do |f|
        l = f.gets
        if l =~ /:([A-Za-z_-]+): (.*)/ #docinfo
          frontmatter = { $1 => [$2.chomp] }
          last_key = $1

          # Read docinfo
          while(l = f.gets)
            break if l =~ /^\s*$/ # End of docinfo
            if l =~ /^\s+/ # Continuous line
              docinfo_lines.last.push($'.chomp)
            elsif l =~ /:([A-Za-z_-]+): (.*)/
              frontmatter[$1] = [$2.chomp]
              last_key = $1
            end
          end

          # Treat docinfo lines
          frontmatter.each do |k,v|
            v = v.join(" ")
            #if((k == "author" || k == "authors") && v.include?(";")) # Multiple authors.
            if(v.include?(";")) # Multiple element.
              v = v.split(/\s*;\s*/)

            elsif k == "date" # Date?
              # Datetime?
              if v =~ /[0-2][0-9]:[0-6][0-9]/
                v = DateTime.parse(v)
              else
                v = Date.parse(v)
              end
            elsif v == "yes" || v == "true"
              v = true
            else # Simple String.
              nil # keep v
            end

            frontmatter[k] = v
          end

        elsif l && l.chomp == ".." #YAML
          # Load ReST YAML that document begins comment and block is yaml.
          lines = []

          while(l = f.gets)
            if(l !~ /^\s*$/ .. l =~ /^\s*$/)
              if l=~ /^\s*$/
                break
              else
                lines.push l
              end
            end
          end
          next if f.eof?


          # Rescue for failed to read YAML.
          begin
            frontmatter = Psych.unsafe_load(lines.map {|i| i.sub(/^\s*/, "") }.join)
          rescue
            STDERR.puts "Error in parsing ReST YAML frontmatter (#{$!})"
            next
          end
        else
          next
        end

        pos = f.pos

      end
    end
  end

  abort "This document has no frontmatter" unless frontmatter
  abort "This document has no title." unless frontmatter["title"]


  ### Additional meta values. ###
  frontmatter["source_directory"] = dir # Source Directory
  frontmatter["source_filename"] = filename # Source Filename
  frontmatter["source_path"] = File.join(dir, filename) # Source Path
  # URL in site.
  this_url = (File.join(dir, filename)).sub(/^[\.\/]*/) { @config["self_url_prefix"] || "/" }.sub(/\.[a-zA-Z0-9]+$/, ".html")
  frontmatter["page_url"] = this_url
  # URL in site with URI encode.
  frontmatter["page_url_encoded"] = ERB::Util.url_encode(this_url)
  frontmatter["page_url_encoded_external"] = ERB::Util.url_encode((File.join(dir, filename)).sub(/^[\.\/]*/) { @config["self_url_external_prefix"] || "/" }.sub(/\.[a-zA-Z0-9]+$/, ".html"))
  frontmatter["page_html_escaped"] = ERB::Util.html_escape(this_url)
  frontmatter["page_html_escaped_external"] = ERB::Util.html_escape((File.join(dir, filename)).sub(/^[\.\/]*/) { @config["self_url_external_prefix"] || "/" }.sub(/\.[a-zA-Z0-9]+$/, ".html"))
  # Title with URL Encoded.
  frontmatter["title_encoded"] = ERB::Util.url_encode(frontmatter["title"])
  frontmatter["title_html_escaped"] = ERB::Util.html_escape(frontmatter["title"])
  fts = frontmatter["timestamp"]
  fts = fts.to_datetime if Time === fts
  if DateTime === fts
    frontmatter["timestamp_xmlschema"] = fts.xmlschema
    frontmatter["timestamp_jplocal"] = fts.strftime('%Y年%m月%d日 %H時%M分%S秒')
    frontmatter["timestamp_rubytimestr"] = fts.strftime('%a %b %d %H:%M:%S %Z %Y')
    frontmatter["timestamp_str"] = fts.strftime("%Y-%m-%d %H:%M:%S %Z")
  elsif Date === fts
    frontmatter["timestamp_xmlschema"] = fts.xmlschema
    frontmatter["timestamp_jplocal"] = fts.strftime('%Y年%m月%d日')
    frontmatter["timestamp_rubytimestr"] = fts.strftime('%a %b %d')
    frontmatter["timestamp_str"] = fts.strftime("%Y-%m-%d")
  elsif Date === frontmatter["Date"]
    fts = frontmatter["Date"]
    frontmatter["timestamp_xmlschema"] = fts.xmlschema
    frontmatter["timestamp_jplocal"] = fts.strftime('%Y年%m月%d日')
    frontmatter["timestamp_rubytimestr"] = fts.strftime('%a %b %d')
    frontmatter["timestamp_str"] = fts.strftime("%Y-%m-%d")
  end

  fsize = FileTest.size(File.join(dir, filename))
  mtime = File.mtime(File.join(dir, filename)).to_i

  frontmatter["_filename"] ||= filename
  frontmatter["pagetype"] ||= "post"

  frontmatter["_size"] = fsize
  frontmatter["_mtime"] = mtime
  frontmatter["_last_proced"] = @now.to_i

  if File.extname(filename) == ".md"
    frontmatter["_docformat"] = "Markdown"
  elsif File.extname(filename) == ".rst" || File.extname(filename) == ".rest"
    frontmatter["_docformat"] = "ReST"
  end

  frontmatter["date"] ||= @now.strftime("%Y-%m-%d %H:%M:%S")

  return frontmatter, pos
end