From 10038e58efe3aa3c1725e2b5b0a0b7b2ce060df7 Mon Sep 17 00:00:00 2001 From: elijah Date: Wed, 20 Feb 2013 00:39:58 -0800 Subject: added support for pandoc and page properties in static markup. --- lib/menu.rb | 4 + lib/property_set.rb | 23 ++++- lib/static_page.rb | 274 ++++++++++++++++++++++++++++++++++++++++++++++------ lib/template.html | 9 ++ 4 files changed, 279 insertions(+), 31 deletions(-) create mode 100644 lib/template.html (limited to 'lib') diff --git a/lib/menu.rb b/lib/menu.rb index 4da8441..b586151 100644 --- a/lib/menu.rb +++ b/lib/menu.rb @@ -50,6 +50,10 @@ class Menu end end + def path_str + @path_str ||= path.join('/') + end + def each(&block) children.each(&block) end diff --git a/lib/property_set.rb b/lib/property_set.rb index 64982c9..05aa46a 100644 --- a/lib/property_set.rb +++ b/lib/property_set.rb @@ -11,6 +11,7 @@ # getting the property # # page.props.title +# page.props.locale('en').title # require 'i18n' @@ -39,15 +40,17 @@ class PropertySet def textile(str) RedCloth.new(str).to_html end - def get(var_name) + def get(var_name, inheritance=true) value = instance_variable_get("@#{var_name}") if value.nil? if @_locale != DEFAULT_LOCALE # try value from default locale @_ps.get_var(var_name) - else + elsif inheritance # try inherited value @_ps.get_inherited_var(var_name, @_locale) + else + nil end else value @@ -66,7 +69,9 @@ class PropertySet # # evaluate the template_string, and load the variables defined into an AttrObject. # - def eval(locale, template_string) + def eval(template_string, locale) + locale ||= DEFAULT_LOCALE + # render to the template to get the instance variables attrs = AttrObject.new(self, locale) begin @@ -135,6 +140,18 @@ class PropertySet end end + # + # like get_var, but forbits inheritance + # + def get_var_without_inheritance(var_name, locale=I18n.locale) + attrs = locale(locale) + if attrs + attrs.get(var_name, false) + else + nil + end + end + # # tries to get the value of an inherited variable # diff --git a/lib/static_page.rb b/lib/static_page.rb index eb32319..b4bc40d 100644 --- a/lib/static_page.rb +++ b/lib/static_page.rb @@ -1,4 +1,4 @@ -# + # # class StaticPage # # represents a static website page. @@ -8,9 +8,11 @@ require 'property_set' require 'i18n' require 'pathname' +require 'RedCloth' +require 'pandoc-ruby' class StaticPage - attr_accessor :path, :children, :name, :file_path, :props, :parent, :mount_point + attr_accessor :path, :children, :name, :file_path, :props, :parent, :mount_point, :locales ## ## CLASS METHODS @@ -31,7 +33,8 @@ class StaticPage end # - # loads a directory, creating StaticPages from the directory structure + # loads a directory, creating StaticPages from the directory structure, + # yielding each StaticPage as it is created. # def scan(&block) Dir.chdir(file_path) do @@ -41,7 +44,7 @@ class StaticPage yield child child.scan(&block) elsif is_simple_page?(child_name) - child = StaticPage.new(self, file_without_suffix(child_name)) + child = StaticPage.new(self, child_name) yield child end end @@ -54,7 +57,17 @@ class StaticPage def initialize(parent, name, file_path=nil) @children = [] - @name = name + + # set the @name and @suffix + @suffix = File.extname(name) + if @suffix.chars.any? + @name = file_without_suffix(name) + else + @name = name + @suffix = nil + end + + # set @parent & @path if parent @parent = parent @mount_point = @parent.mount_point @@ -63,6 +76,8 @@ class StaticPage else @path = [] end + + # set the @file_path if file_path @file_path = file_path elsif @parent && @parent.file_path @@ -70,8 +85,13 @@ class StaticPage else raise 'file path must be specified or in parent' end + + # discover supported locales @simple_page = !File.directory?(@file_path) - @props = load_properties + #@locales = find_locales() + + # eval the property headers, if any + @props = load_properties() end def add_child(page) @@ -100,15 +120,69 @@ class StaticPage end end + # + # e.g. pages/about-us/contact/en + # + def absolute_template_path(locale=I18n.locale) + if @simple_page + "#{@mount_point.directory}/#{@path.join('/')}" + else + "#{@mount_point.directory}/#{@path.join('/')}/#{locale}" + end + end + + # + # e.g. pages/about-us/contact/en.haml + # + #def source_path(locale=I18n.locale) + # if @simple_page + # + # else + # + # end + #end + def inspect "<'#{@path.join('/')}' #{children.inspect}>" end - def title + # + # title is tricky + # + # * for nav_title, default to title, then try to inherit. + # * for title, default to nav_title, then try to inherit. + # + def title(locale=I18n.locale) + title = props.get_var_without_inheritance(:title, locale) + title ||= props.get_var_without_inheritance(:nav_title, locale) + title ||= props.get_var(:title, locale) + title ||= props.get_var(:nav_title, locale) + title ||= I18n.t('pages.'+@name, :raise => false, :default => "Untitled", :locale => locale) + return title + end + + def nav_title(locale=I18n.locale) + return_value = I18n.t('pages.'+@name, :raise => false, :default => "Untitled", :locale => locale) + if return_value == "Untitled" + property_title = props.get_var_without_inheritance(:nav_title, locale) + property_title ||= props.get_var_without_inheritance(:title, locale) + property_title ||= props.get_var(:nav_title, locale) + property_title ||= props.get_var(:title, locale) + return_value = property_title || return_value + end + return_value + end + + def render_to_string(renderer) begin - I18n.t!('pages.' + @name, :raise => true) - rescue I18n::MissingTranslationData - props.title + render_locale(renderer, I18n.locale) + rescue ActionView::MissingTemplate, MissingTemplate => exc + begin + render_locale(renderer, DEFAULT_LOCALE) + rescue + Rails.logger.error "ERROR: could not file template path #{self.template_path}" + raise exc + end end end @@ -124,30 +198,49 @@ class StaticPage private - #def self.relative_to_rails_view_root(absolute_path) - # if Rails.root - # absolute = Pathname.new(absolute_path) - # rails_view_root = Pathname.new(Rails.root + 'app/views') - # absolute.relative_path_from(rails_view_root).to_s - # end - #end + ## + ## PROPERTIES + ## + # + # scans the source content files for property headers in the form: + # + # @variable = 'x' + # - @variable = 'x' + # + # (with or without leading hypen works) + # + # this text is extracted and evaluated as ruby to set properties. + # def load_properties props = PropertySet.new(self) - Dir.glob(file_path + '/*.haml') do |content_file_path| - locale = File.basename(content_file_path).sub(File.extname(content_file_path),'') - #variable_header = "" - #File.open(content_file_path) do |f| - # while (line = f.gets) =~ /^- @/ - # variable_header << line - # end - #end - props.eval(locale, File.read(content_file_path)) + content_files.each do |content_file, locale| + if File.extname(content_file) == '.haml' + props.eval(File.read(content_file), locale) + else + headers = [] + File.open(content_file) do |f| + while (line = f.gets) =~ /^(- |)@\w/ + if line !~ /^-/ + line = '- ' + line + end + headers << line + end + end + props.eval(headers.join("\n"), locale) + end end return props end - SUFFIXES = '(haml|md)' + ## + ## CONTENT FILES + ## + + SUFFIXES = '(haml|md|markdown|txt|textile|rst|latex|pandoc|html)' + + # e.g. en.haml or es.md + LOCALE_FILE_MATCH = /^(#{AVAILABLE_LANGUAGES.join('|')})\.#{SUFFIXES}$/ # # returns true if the name of a file could be a 'simple' static page @@ -158,12 +251,137 @@ class StaticPage # * we exclude file names that are locales. # def is_simple_page?(name) - name =~ /\.#{SUFFIXES}$/ && name !~ /^(#{AVAILABLE_LANGUAGES.join('|')})\.#{SUFFIXES}$/ + name =~ /\.#{SUFFIXES}$/ && name !~ LOCALE_FILE_MATCH end def file_without_suffix(name) name.sub(/^(.*?)\.#{SUFFIXES}$/, "\\1") end + + # + # returns an array like so: + # + # [ + # ['/path/to/page/en.haml', 'en'] + # ['/path/to/page/es.haml', 'es'] + # ] + # + # Or this, if page is simple: + # + # [ + # ['/path/to/page.haml', nil] + # ] + # + # + def content_files + if @simple_page + [[@file_path + @suffix,nil]] + elsif File.directory?(@file_path) + Dir.foreach(@file_path).collect { |file| + if file && file =~ LOCALE_FILE_MATCH + [File.join(@file_path, file), $1] + end + }.compact + end + end + + #def self.relative_to_rails_view_root(absolute_path) + # if Rails.root + # absolute = Pathname.new(absolute_path) + # rails_view_root = Pathname.new(Rails.root + 'app/views') + # absolute.relative_path_from(rails_view_root).to_s + # end + #end + + ## + ## RENDERING + ## + + PROPERTY_HEADER = /^\s*(^(|- )@\w[^\n]*?\n)*/m + + class MissingTemplate < StandardError + end + + def render_locale(renderer, locale) + if is_haml_template?(locale) + renderer.render_to_string(:template => self.template_path(locale), :layout => false).html_safe + else + render_static_locale(locale).html_safe + end + end + + def render_static_locale(locale) + content_files.each do |content_file, file_locale| + if file_locale.nil? || locale == file_locale + return render_content_file(content_file, locale) + end + end + raise MissingTemplate.new(template_path(locale)) + end + + # + # todo: maybe use RDiscount for markdown instead? + # + def render_content_file(content_file, locale) + content = File.read(content_file).sub(PROPERTY_HEADER, '') + suffix = File.extname(content_file) + if PANDOC_FORMATS[suffix] + render_pandoc(content, suffix, locale) + elsif REDCLOTH_FORMATS[suffix] + render_redcloth(content, suffix, locale) + else + "sorry, i don't understand how to render #{suffix}" + end + end + + def is_haml_template?(locale) + @suffix == '.haml' || File.exists?(self.absolute_template_path(locale) + '.haml') + end + + PANDOC_FORMATS = { + '.md' => :markdown, + '.markdown' => :markdown, + #'.txt' => :textile, + #'.textile' => :textile, + '.rst' => :rst, + '.latex' => :latex, + '.pandoc' => :pandoc, + } + + def render_pandoc(string, suffix, locale) + args = [string, {:from => PANDOC_FORMATS[suffix], :to => :html5}, "smart"] + if props.locale(locale).toc != false + args << "table_of_contents" + args << {"template" => "'#{File.dirname(__FILE__) + '/template.html'}'"} + end + unless (title = explicit_title(locale)).nil? + args << {"variable" => "title:'#{title}'"} + end + renderer = PandocRuby.new(*args) + renderer.convert + end + + # + # pandoc can do textile, but it does it HORRIBLY + # + REDCLOTH_FORMATS = { + '.txt' => :textile, + '.textile' => :textile, + } + + def render_redcloth(string, suffix, locale) + unless (title = explicit_title(locale)).nil? + string = "h1. #{title}\n\n" + string + end + RedCloth.new(string).to_html + end + + # + # returns title iff explicitly set. + # + def explicit_title(locale) + props.get_var_without_inheritance(:title, locale) + end end diff --git a/lib/template.html b/lib/template.html new file mode 100644 index 0000000..379a602 --- /dev/null +++ b/lib/template.html @@ -0,0 +1,9 @@ +$if(title)$ +

$title$

+$endif$ +$if(toc)$ +
+$toc$ +
+$endif$ +$body$ \ No newline at end of file -- cgit v1.2.3