From 7edb003cd1c9b53ffdff11ef85532e39f08db16d Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Fri, 6 Jan 2017 19:50:45 -0500 Subject: wip --- lib/.gitignore | 1 + lib/config.rb | 48 ++++++++++++++++++++++ lib/license.rb | 17 ++++++++ lib/page.rb | 83 ++++++++++++++++++++++++++++++++++++++ lib/page_index.rb | 63 +++++++++++++++++++++++++++++ lib/page_local.rb | 114 +++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/page_remote.rb | 56 ++++++++++++++++++++++++++ lib/pandoc.rb | 96 ++++++++++++++++++++++++++++++++++++++++++++ lib/person.rb | 32 +++++++++++++++ lib/sitegen.rb | 0 lib/tag.rb | 17 ++++++++ lib/util.rb | 23 +++++++++++ 12 files changed, 550 insertions(+) create mode 100644 lib/.gitignore create mode 100644 lib/config.rb create mode 100644 lib/license.rb create mode 100644 lib/page.rb create mode 100644 lib/page_index.rb create mode 100644 lib/page_local.rb create mode 100644 lib/page_remote.rb create mode 100644 lib/pandoc.rb create mode 100644 lib/person.rb create mode 100644 lib/sitegen.rb create mode 100644 lib/tag.rb create mode 100644 lib/util.rb (limited to 'lib') 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 + "#{name}" + 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 + # + # def _published => DateTime | nil + # def _updated => DateTime | nil + # def _years => Enumerable + + def tags # => Enumerable + 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 + 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 += "

#{title}

\n" + end + + # Insert the body + @content = _pandoc.to('html5 '+(_pandoc['pandoc_flags']||'')) + end + @content + end + + def rights + # TODO: simplify year spans + @rights ||= "

The content of this page is Copyright © #{years.sort.join(', ')} #{author.html}.

\n" + + "

This page is licensed under the #{license.html} license.

" + 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 "#{name}" + elsif not uri.nil? + return "#{name}" + else + return @name + end + end + def atom + ret = "" + ret += "#{name}" unless name.nil? + ret += "#{uri}" unless uri.nil? + ret += "#{email}" unless email.nil? + end +end diff --git a/lib/sitegen.rb b/lib/sitegen.rb new file mode 100644 index 0000000..e69de29 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 "#{name}" + 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('&', '&') + .gsub('>', '>') + .gsub('<', '<') + end + + def self.breadcrumbs(url) + # TODO + bc = [] + u = url.path + u = "/" if u == "" + while u != "/" + bc.unshift("#{File::basename(u, File::extname(u))}") + u = File::dirname(u) + end + bc.unshift("Andrew D. Murrell") + return bc.join(' » ') + end +end -- cgit v1.2.3