Skip to content

Commit

Permalink
Add bidirectional front matter <-> header AsciiDoc mapping
Browse files Browse the repository at this point in the history
This allows Awstruct to consume AsciiDoc's Headers as
Page variables and AsciiDoc to use Awestuct's Page variables
as Header values.
  • Loading branch information
aslakknutsen committed Mar 8, 2013
1 parent 5b41653 commit 0666cf6
Show file tree
Hide file tree
Showing 11 changed files with 228 additions and 12 deletions.
2 changes: 1 addition & 1 deletion Gemfile
Expand Up @@ -12,7 +12,7 @@ group :test do
gem 'rack-test'
gem 'tilt', '~> 1.3.4'
gem 'coffee-script', '~> 2.2.0'
gem 'asciidoctor', '>= 0.1.0'
gem 'asciidoctor', '>= 0.1.1' # need 0.1.1 for Header support
gem 'haml', '~> 4.0.0'
gem 'slim', '~> 1.3.6'
gem 'kramdown', '~> 0.14.2'
Expand Down
6 changes: 4 additions & 2 deletions lib/awestruct/config/default-site.yml
Expand Up @@ -7,5 +7,7 @@ content_syntax:
ad: asciidoc

asciidoctor:
attributes:
backend: html5
:backend: html5
:attributes:
imagesdir: '/images'
stylesdir: '/stylesheets'
2 changes: 2 additions & 0 deletions lib/awestruct/handler_chains.rb
Expand Up @@ -3,6 +3,7 @@
require 'awestruct/handlers/css_tilt_handler'
require 'awestruct/handlers/restructuredtext_handler'
require 'awestruct/handlers/javascript_handler'
require 'awestruct/handlers/asciidoctor_handler'
require 'awestruct/handlers/redirect_handler'
require 'awestruct/handlers/tilt_handler'

Expand All @@ -15,6 +16,7 @@ class HandlerChains
Awestruct::Handlers::RedirectHandler::CHAIN,
Awestruct::Handlers::RestructuredtextHandler::CHAIN,
Awestruct::Handlers::JavascriptHandler::CHAIN,
Awestruct::Handlers::AsciidoctorHandler::CHAIN,
Awestruct::Handlers::TiltHandler::NON_INTERPOLATION_CHAIN,
Awestruct::Handlers::TiltHandler::INTERPOLATION_CHAIN,
HandlerChain.new( /.*/, Awestruct::Handlers::FileHandler )
Expand Down
154 changes: 154 additions & 0 deletions lib/awestruct/handlers/asciidoctor_handler.rb
@@ -0,0 +1,154 @@

require 'awestruct/handler_chain'
require 'awestruct/handlers/base_tilt_handler'
require 'awestruct/handlers/file_handler'
require 'awestruct/handlers/layout_handler'
require 'awestruct/handlers/template/asciidoc'
require 'yaml'

require 'tilt'

module Awestruct
module Handlers

class AsciidoctorMatcher
def match(path)
engine = Tilt[path]
engine == Tilt::AsciidoctorTemplate
end
end

class AsciidoctorHandler < BaseTiltHandler

CHAIN = Awestruct::HandlerChain.new( Awestruct::Handlers::AsciidoctorMatcher.new(),
Awestruct::Handlers::FileHandler,
Awestruct::Handlers::AsciidoctorHandler,
Awestruct::Handlers::LayoutHandler
)

def initialize(site, delegate)
super( site, delegate )

@front_matter = {}
end


def front_matter
parse_header()
@front_matter
end

def raw_content
parse_header()
super
end

def rendered_content(context, with_layouts)
parse_header()
types = [String, Numeric, TrueClass, FalseClass, Array]
@front_matter.merge!(context.page.inject({}) do |hash, (k,v)|
hash[k.to_s] = v if not k.to_s.start_with?("__") and types.detect { |t| v.kind_of? t}
hash
end)
super
end

def options
opts = super
if opts[:attributes].nil?
opts[:attributes] = @front_matter
else
opts[:attributes] = opts[:attributes].merge @front_matter
end
opts[:attributes]["awestruct"] = true
opts[:attributes]["awestruct-version"] = Awestruct::VERSION
opts
end

def content_line_offset
parse_header()
@content_line_offset
end

def inherit_front_matter(page)
parse_header()
page.inherit_front_matter_from(@front_matter)
end

def parse_header
return if @parsed_parts

parse_front_matter
if content_line_offset == 0
content = delegate.raw_content
unless content.nil?
@front_matter = parse_document_attributes(content)
@parsed_parts = true
end
end
end

def parse_document_attributes(content)
template = Tilt::new(delegate.path.to_s, delegate.content_line_offset + 1, options)
template.parse_headers(content, /^awestruct\-/).inject({}) do |hash, (k,v)|
unless v.nil?
hash[k] = v.empty? ? v : YAML.load(v)
end
hash
end
end

def parse_front_matter
return if ( @parsed_parts && ! delegate.stale? )

full_content = delegate.raw_content
full_content.force_encoding(site.encoding) if site.encoding
yaml_content = ''

dash_lines = 0
mode = :yaml

@raw_content = ''
@content_line_offset = 0

full_content.each_line do |line|
if ( line.strip == '---' )
dash_lines = dash_lines +1
end
if ( mode == :yaml )
@content_line_offset += 1
yaml_content << line
else
@raw_content << line
end
if ( dash_lines == 2 )
mode = :page
end
end

if ( dash_lines == 0 )
@raw_content = yaml_content
yaml_content = ''
@content_line_offset = 0
elsif ( mode == :yaml )
@raw_content = nil
@content_line_offset = -1
end

begin
@front_matter = YAML.load( yaml_content ) || {}
rescue => e
puts "could not parse #{relative_source_path}"
raise e
end

@parsed_parts = true

end
end

end
end

require 'awestruct/handlers/template/asciidoc'
Tilt::register Tilt::AsciidoctorTemplate, '.ad', '.adoc', '.asciidoc'
14 changes: 14 additions & 0 deletions lib/awestruct/handlers/template/asciidoc.rb
Expand Up @@ -23,5 +23,19 @@ def evaluate(scope, locals, &block)
def allows_script?
false
end

def parse_headers(content, filter = /.*/)
doc = Asciidoctor.load(content, {:parse_header_only => true})
filtered = doc.attributes.select{|k,v| k =~ filter}.inject({}) do |hash, (k,v)|
hash[k.gsub(filter, '')] = v
hash
end

filtered["doctitle"] = doc.doctitle
filtered["date"] ||= doc.attributes["revdate"] unless doc.attributes["revdate"].nil?
filtered["author"] = doc.attributes["author"] unless doc.attributes["author"].nil?

filtered
end
end
end
5 changes: 1 addition & 4 deletions lib/awestruct/handlers/tilt_handler.rb
Expand Up @@ -61,9 +61,6 @@ def initialize(site, delegate)
require 'awestruct/handlers/template/mustache'
Tilt::register Tilt::MustacheTemplate, '.mustache'

require 'awestruct/handlers/template/asciidoc'
Tilt::register Tilt::AsciidoctorTemplate, '.ad', '.adoc', '.asciidoc'

# As of Haml 4.0.0, Textile is no longer registered by default
# Monkeypatch the Tilt templates to force Textile to be registered
class Tilt::HamlTemplate
Expand All @@ -73,4 +70,4 @@ def initialize_engine
Haml::Filters.register_tilt_filter 'Textile'
end
end
end
end
34 changes: 32 additions & 2 deletions spec/asciidoc_handler_spec.rb
@@ -1,4 +1,5 @@
require 'spec_helper'
require 'rspec/matchers.rb'

verify = lambda { |output|
output.gsub(/(^\s*\n|^\s*)/, '').should =~ %r(<div id="preamble">
Expand All @@ -10,6 +11,21 @@
</div>)
}

verify_front_matter = lambda { |output, page|
page.title.should == 'AwestructAsciiDoc'
}

verify_headers = lambda { |output, page|
extend RSpec::Matchers

page.name.should == 'Awestruct'
page.tags.should be_a_kind_of(Array)
page.tags.should == %w(a b c)
page.date.should be_a_kind_of(Date)
output.should =~ %r(This is <strong>AsciiDoc</strong> in Awestruct.)
output.should =~ %r(#{Awestruct::VERSION})
}

theories =
[
{
Expand All @@ -32,11 +48,25 @@
:syntax => :asciidoc,
:extension => '.html',
:matcher => verify
},
{
:page => "asciidoctor_with_front_matter.ad",
:simple_name => "asciidoctor_with_front_matter",
:syntax => :asciidoc,
:extension => '.html' ,
:matcher => verify_front_matter
},
{
:page => "asciidoctor_with_headers.ad",
:simple_name => "asciidoctor_with_headers",
:syntax => :asciidoc,
:extension => '.html',
:matcher => verify_headers
}
]

describe Awestruct::Handlers::TiltHandler.to_s + "-AsciiDoc" do

describe Awestruct::Handlers::AsciidoctorHandler do
let(:additional_config_page) { {:name => 'Awestruct', :test => 10} }
it_should_behave_like "a handler", theories

end
2 changes: 1 addition & 1 deletion spec/engine_spec.rb
Expand Up @@ -11,7 +11,7 @@
engine = Awestruct::Engine.new(config)
engine.load_default_site_yaml

engine.site.asciidoctor.attributes.backend.should == 'html5'
engine.site.asciidoctor.backend.should == 'html5'
end

it "should be able to override default with site" do
Expand Down
7 changes: 5 additions & 2 deletions spec/support/shared_handler_example.rb
Expand Up @@ -67,10 +67,13 @@ def create_handler(page)
it "should render page '#{theory[:page]}'" do
if theory[:unless].nil? or !theory[:unless][:exp].call()
handler = create_handler theory[:page]
output = handler.rendered_content( create_context )
handler.merge! additional_config_page if respond_to?("additional_config_page")

output = handler.rendered_content( handler.create_context )
output.should_not be_nil

theory[:matcher].call(output)
theory[:matcher].call(output, handler) if theory[:matcher].arity == 2
theory[:matcher].call(output) if theory[:matcher].arity == 1
else
pending theory[:unless][:message]
end
Expand Down
6 changes: 6 additions & 0 deletions spec/test-data/handlers/asciidoctor_with_front_matter.ad
@@ -0,0 +1,6 @@
---
title: AwestructAsciiDoc
---
= AsciiDoc

This is *AsciiDoc* in Awestruct.
8 changes: 8 additions & 0 deletions spec/test-data/handlers/asciidoctor_with_headers.ad
@@ -0,0 +1,8 @@
= AsciiDoc
Stuart Rackham
2013-02-06
:awestruct-tags: [a, b, c]
:name: NOT_HANDLED

This is *AsciiDoc* in {name}.
{awestruct-version}

0 comments on commit 0666cf6

Please sign in to comment.