summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@sbcglobal.net>2017-01-06 19:50:45 -0500
committerLuke Shumaker <lukeshu@sbcglobal.net>2017-01-06 19:50:45 -0500
commit7edb003cd1c9b53ffdff11ef85532e39f08db16d (patch)
tree180db82a4af7720508ae2732393401bf4d27cadf /lib
parent7d875df65221d4da91953cf129a03e76fe8e5d29 (diff)
wip
Diffstat (limited to 'lib')
-rw-r--r--lib/.gitignore1
-rw-r--r--lib/config.rb48
-rw-r--r--lib/license.rb17
-rw-r--r--lib/page.rb83
-rw-r--r--lib/page_index.rb63
-rw-r--r--lib/page_local.rb114
-rw-r--r--lib/page_remote.rb56
-rw-r--r--lib/pandoc.rb96
-rw-r--r--lib/person.rb32
-rw-r--r--lib/sitegen.rb0
-rw-r--r--lib/tag.rb17
-rw-r--r--lib/util.rb23
12 files changed, 550 insertions, 0 deletions
diff --git a/lib/.gitignore b/lib/.gitignore
new file mode 100644
index 0000000..e8db6e5
--- /dev/null
+++ b/lib/.gitignore
@@ -0,0 +1 @@
+/page.rb.txt \ No newline at end of file
diff --git a/lib/config.rb b/lib/config.rb
new file mode 100644
index 0000000..b22eedf
--- /dev/null
+++ b/lib/config.rb
@@ -0,0 +1,48 @@
+# coding: utf-8
+require 'yaml'
+
+require 'uri'
+
+class Config
+ def self.get
+ return @config ||= Config::new('config.yaml')
+ end
+ def initialize(filename)
+ @data = YAML::load(File::read(filename))
+ end
+ def url
+ return @url ||= URI::parse(@data['url'])
+ end
+ def html_suffixes
+ return @data['html_suffixes']
+ end
+ # Licenses
+ def default_license
+ return @default_license ||= @data['default_license']
+ end
+ def license_uri(name)
+ str = @data['license_uris'][name]
+ if str.nil?
+ return nil
+ end
+ return URI::parse(str)
+ end
+ # People
+ def default_author
+ return @default_person ||= @data['default_author']
+ end
+ def person_uri(name)
+ str = @data['person_uris'][name]
+ if str.nil?
+ return nil
+ end
+ return URI::parse(str)
+ end
+ def person_email(name)
+ return @data['person_emails'][name]
+ end
+ # Tags
+ def tag_name(abbr)
+ return @data['tag_names'][abbr]
+ end
+end
diff --git a/lib/license.rb b/lib/license.rb
new file mode 100644
index 0000000..da68f30
--- /dev/null
+++ b/lib/license.rb
@@ -0,0 +1,17 @@
+# coding: utf-8
+require 'config'
+
+class License
+ def initialize(name)
+ @name = name
+ end
+ def name
+ @name
+ end
+ def url
+ Config::get.license_uri(@name)
+ end
+ def html
+ "<a href=\"#{url}\">#{name}</a>"
+ end
+end
diff --git a/lib/page.rb b/lib/page.rb
new file mode 100644
index 0000000..d0b18ef
--- /dev/null
+++ b/lib/page.rb
@@ -0,0 +1,83 @@
+# coding: utf-8
+require 'set'
+
+require 'tag'
+
+class Page
+ # Page is an abstract class.
+ #
+ # Implementors must implement several methods:
+ #
+ # def url => URI
+ # def title => String
+ # def author => Person
+ # def content => html | nil
+ # def rights => html | nil
+ #
+ # def _tags => String | Enumerable<String>
+ #
+ # def _published => DateTime | nil
+ # def _updated => DateTime | nil
+ # def _years => Enumerable<Fixnum>
+
+ def tags # => Enumerable<Tag>
+ if @tags.nil?
+ raw = _tags
+ if raw.is_a?(String)
+ raw = raw.split
+ end
+ @tags = raw.map{|tag|Tag.new(tag)}
+ end
+ @tags
+ end
+
+ def published # => DateTime | nil
+ if @published.nil?
+ unless _published.nil?
+ @published = _published
+ else
+ unless _updated.nil?
+ @published = _updated
+ end
+ end
+ # sanity check
+ unless _published.nil? or _updated.nil?
+ if _updated < _published
+ @published = _updated
+ end
+ end
+ end
+ @published
+ end
+
+ def updated # => DateTime | nil
+ if @updated.nil?
+ unless _updated.nil?
+ @updated = _updated
+ else
+ unless _published.nil?
+ @updated = _published
+ end
+ end
+ end
+ @updated
+ end
+
+ def years # => Enumerable<Fixnum>
+ if @years.nil?
+ if published.nil? || updated.nil?
+ @years = Set[]
+ else
+ first = published.year
+ last = updated.year
+
+ years = _years
+ years.add(first)
+ years.add(last)
+
+ @years = Set[*years.select{|i|i >= first && i <= last}]
+ end
+ end
+ @years
+ end
+end
diff --git a/lib/page_index.rb b/lib/page_index.rb
new file mode 100644
index 0000000..073537e
--- /dev/null
+++ b/lib/page_index.rb
@@ -0,0 +1,63 @@
+# coding: utf-8
+require 'erb'
+require 'set'
+require 'yaml'
+
+require 'page_local'
+require 'page_remote'
+require 'config'
+
+class IndexPage < LocalPage
+ def initialize(dirname)
+ super(dirname)
+ end
+
+ def _metadata
+ if @metadata.nil?
+ yamlfile = _infile+"/index.yaml"
+ if File::exist?(yamlfile)
+ @metadata = YAML::load(File::read(yamlfile))
+ else
+ @metadata = {}
+ end
+ end
+ @metadata
+ end
+ def _ls
+ @ls ||= Dir::entries(_infile)
+ .select{|fname|not fname.start_with?(".")}
+ .map{|fname|"#{_infile}/#{fname}"}
+ .select{|path|Dir::exist?(path) or Config::get.html_suffixes.include?(File::extname(path).gsub(/^[.]/, ''))}
+ end
+ def pages
+ if @pages.nil?
+ @pages = []
+ for path in _ls
+ if Dir::exist?(path)
+ page = IndexPage::new(path)
+ @pages.unshift(page)
+ @pages += page.pages
+ else
+ @pages.unshift(LocalPage::new(path))
+ end
+ end
+ for data in _metadata['external']
+ @pages.unshift(RemotePage::new(data))
+ end
+ end
+ @pages
+ end
+
+ def _published
+ return nil
+ end
+ def _updated
+ return nil
+ end
+ def _years
+ return Set[]
+ end
+end
+
+ERB::new(File::read("tmpl/index.atom.erb")).def_method(IndexPage, 'atom()', "tmpl/index.atom.erb")
+ERB::new(File::read("tmpl/index.md.erb")).def_method(IndexPage, '_input()', "tmpl/index.md.erb")
diff --git a/lib/page_local.rb b/lib/page_local.rb
new file mode 100644
index 0000000..1ca14f0
--- /dev/null
+++ b/lib/page_local.rb
@@ -0,0 +1,114 @@
+# coding: utf-8
+require 'date'
+require 'erb'
+require 'set'
+
+require 'config'
+require 'license'
+require 'page'
+require 'pandoc'
+require 'person'
+
+class LocalPage < Page
+ def initialize(infile)
+ @infile = infile
+ end
+
+ # Some of this code looks a little weird because it is
+ # super-aggressively lazy-evaluated and cached.
+
+ def _infile ; @infile ; end
+ def _input ; @input ||= File::read(_infile) ; end
+ def _pandoc
+ if @pandoc.nil?
+ types = {
+ 'md' => 'markdown'
+ }
+
+ ext = File::extname(_infile).gsub(/^[.]/, '')
+ type = types[ext] || ext
+ @pandoc = Pandoc::load(type, _input)
+
+ if @pandoc['pandoc_format']
+ @pandoc = Pandoc::load(@pandoc['pandoc_format'], _input)
+ end
+ end
+ @pandoc
+ end
+
+ # Query simple document metadata
+ def title ; @title ||= _pandoc['title'] || _input.split("\n",2).first ; end
+ def author ; @author ||= Person::new( _pandoc['author'] || Config::get.default_author) ; end
+ def license ; @license ||= License::new(_pandoc['license'] || Config::get.default_license); end
+ def head ; @head ||= _pandoc['html_head_extra'] ; end
+ def class ; @class ||= _pandoc['class'] ; end
+ def _tags ; @_tags ||= _pandoc['tags'] || [] ; end
+
+ def content
+ if @content.nil?
+ @content = ''
+ # Only insert the title if it came from Pandoc metadata;
+ # if the title was inferred from the the body content,
+ # then it is already in the page.
+ unless _pandoc['title'].nil?
+ @content += "<h1 class=title>#{title}</h1>\n"
+ end
+
+ # Insert the body
+ @content = _pandoc.to('html5 '+(_pandoc['pandoc_flags']||''))
+ end
+ @content
+ end
+
+ def rights
+ # TODO: simplify year spans
+ @rights ||= "<p>The content of this page is Copyright © #{years.sort.join(', ')} #{author.html}.</p>\n" +
+ "<p>This page is licensed under the #{license.html} license.</p>"
+ end
+
+ def _gitdates
+ @gitdates ||= `git log --format='%cI' -- #{_infile}`.split('\n').select{|s|!s.empty?}.map{|s|DateTime::iso8601(s)}
+ end
+
+ def _published
+ if @_published.nil?
+ raw = _pandoc['published']
+ @_published = Datetime::parse(raw) unless raw.nil?
+ end
+ if @_published.nil?
+ @_published = _gitdates.sort.first
+ end
+ @_published
+ end
+
+ def _updated
+ if @_updated.nil?
+ raw = _pandoc['updated']
+ @_updated = DateTime::parse(raw) unless raw.nil?
+ end
+ if @_updated.nil?
+ @updated = _gitdates.sort.last
+ end
+ @_updated
+ end
+
+ def _years
+ @years ||= Set[*_gitdates.map{|dt|dt.year}]
+ end
+
+ def abssrcpath
+ @srcpath ||= _infile.sub(/^(src|out)\//, '/')
+ end
+ def absoutpath
+ @outpath ||= abssrcpath.sub(/\.[^\/.]*$/, '.html').sub(/\/index[.]html$/, '')
+ end
+
+ def url
+ @url ||= Config::get.url + absoutpath
+ end
+ def srcurl
+ @srcurl ||= Config::get.url + abssrcpath
+ end
+end
+
+ERB::new(File::read("tmpl/page.html.erb")).def_method(LocalPage, 'html()', "tmpl/page.html.erb")
diff --git a/lib/page_remote.rb b/lib/page_remote.rb
new file mode 100644
index 0000000..a754af6
--- /dev/null
+++ b/lib/page_remote.rb
@@ -0,0 +1,56 @@
+# coding: utf-8
+require 'date'
+
+require 'config'
+require 'page'
+require 'tag'
+
+class RemotePage < Page
+ def initialize(metadata)
+ @metadata = metadata
+ end
+
+ def url
+ return Config::get.url + @metadata['url']
+ end
+
+ def title
+ @metadata['title']
+ end
+
+ def author
+ Person::new(@metadata['author'] || Config::get.default_author)
+ end
+
+ def content
+ return nil
+ end
+
+ def rights
+ return nil
+ end
+
+ def _tags
+ @metadata['tags'] || []
+ end
+
+ def _published
+ str = @metadata['published']
+ if str.nil?
+ return nil
+ end
+ return Date::parse(str)
+ end
+
+ def _updated
+ str = @metadata['updated']
+ if str.nil?
+ return nil
+ end
+ return Date::parse(str)
+ end
+
+ def _years
+ return []
+ end
+end
diff --git a/lib/pandoc.rb b/lib/pandoc.rb
new file mode 100644
index 0000000..f0bf3f6
--- /dev/null
+++ b/lib/pandoc.rb
@@ -0,0 +1,96 @@
+# coding: utf-8
+require 'open3'
+require 'json'
+
+module Pandoc
+ def self.prog
+ @prog ||= 'pandoc'
+ end
+ def self.prog=(val)
+ @prog = val
+ end
+ def self.load(fmt, input)
+ cmd = Pandoc::prog + " -t json"
+ unless fmt.nil?
+ cmd += " -f " + fmt
+ end
+ str = input
+ if str.respond_to? :read
+ str = str.read
+ end
+ json = ''
+ errors = ''
+ Open3::popen3(cmd) do |stdin, stdout, stderr|
+ stdin.puts(str)
+ stdin.close
+ json = stdout.read
+ errors = stderr.read
+ end
+ unless errors.empty?
+ raise errors
+ end
+ return Pandoc::AST::new(json)
+ end
+
+ class AST
+ def initialize(json)
+ @js = JSON::parse(json)
+ end
+
+ def [](key)
+ Pandoc::AST::js2sane(@js["meta"][key])
+ end
+
+ def js
+ @js
+ end
+
+ def to(format)
+ cmd = Pandoc::prog + " -f json -t " + format.to_s
+ output = ''
+ errors = ''
+ Open3::popen3(cmd) do |stdin, stdout, stderr|
+ stdin.puts @js.to_json
+ stdin.close
+ output = stdout.read
+ errors = stderr.read
+ end
+ unless errors.empty?
+ raise errors
+ end
+ return output
+ end
+
+ def self.js2sane(js)
+ if js.nil?
+ return js
+ end
+ case js["t"]
+ when "MetaMap"
+ Hash[js["c"].map{|k,v| [k, js2sane(v)]}]
+ when "MetaList"
+ js["c"].map{|c| js2sane(c)}
+ when "MetaBool"
+ js["c"]
+ when "MetaString"
+ js["c"]
+ when "MetaInlines"
+ js["c"].map{|c| js2sane(c)}.join()
+ when "MetaBlocks"
+ js["c"].map{|c| js2sane(c)}.join("\n")
+ when "Str"
+ js["c"]
+ when "Space"
+ " "
+ when "RawInline"
+ js["c"][1]
+ when "RawBlock"
+ js["c"][1]
+ when "Para"
+ js["c"].map{|c| js2sane(c)}.join()
+ else
+ throw js["t"]
+ end
+ end
+ end
+end
diff --git a/lib/person.rb b/lib/person.rb
new file mode 100644
index 0000000..6882dd2
--- /dev/null
+++ b/lib/person.rb
@@ -0,0 +1,32 @@
+# coding: utf-8
+require 'config'
+
+class Person
+ def initialize(name)
+ @name = name
+ end
+ def name
+ @name
+ end
+ def uri
+ Config::get.person_uri(@name)
+ end
+ def email
+ Config::get.person_email(@name)
+ end
+ def html
+ if not email.nil?
+ return "<a href=\"mailto:#{email}\">#{name}</a>"
+ elsif not uri.nil?
+ return "<a href=\"#{uri}\">#{name}</a>"
+ else
+ return @name
+ end
+ end
+ def atom
+ ret = ""
+ ret += "<name>#{name}</name>" unless name.nil?
+ ret += "<uri>#{uri}</uri>" unless uri.nil?
+ ret += "<email>#{email}</email>" unless email.nil?
+ end
+end
diff --git a/lib/sitegen.rb b/lib/sitegen.rb
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lib/sitegen.rb
diff --git a/lib/tag.rb b/lib/tag.rb
new file mode 100644
index 0000000..4009f67
--- /dev/null
+++ b/lib/tag.rb
@@ -0,0 +1,17 @@
+# coding: utf-8
+require 'config'
+
+class Tag
+ def initialize(abbr)
+ @abbr = abbr
+ end
+ def abbr
+ @abbr
+ end
+ def name
+ Config::get.tag_name(@abbr)
+ end
+ def html
+ return "<a class=\"tag #{abbr}\" href=\"/tags/#{abbr}.html\">#{name}</a>"
+ end
+end
diff --git a/lib/util.rb b/lib/util.rb
new file mode 100644
index 0000000..075ebb8
--- /dev/null
+++ b/lib/util.rb
@@ -0,0 +1,23 @@
+# coding: utf-8
+
+module Util
+ def self.html_escape(html)
+ html
+ .gsub('&', '&amp;')
+ .gsub('>', '&gt;')
+ .gsub('<', '&lt;')
+ end
+
+ def self.breadcrumbs(url)
+ # TODO
+ bc = []
+ u = url.path
+ u = "/" if u == ""
+ while u != "/"
+ bc.unshift("<a href=\"#{u}\">#{File::basename(u, File::extname(u))}</a>")
+ u = File::dirname(u)
+ end
+ bc.unshift("<a href=\"/\">Andrew D. Murrell</a>")
+ return bc.join(' » ')
+ end
+end