emmett 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +74 -0
- data/Rakefile +2 -0
- data/emmett.gemspec +27 -0
- data/lib/emmett/configuration.rb +30 -0
- data/lib/emmett/document.rb +126 -0
- data/lib/emmett/document_manager.rb +89 -0
- data/lib/emmett/http_request_processor.rb +70 -0
- data/lib/emmett/railtie.rb +22 -0
- data/lib/emmett/rake_task.rb +47 -0
- data/lib/emmett/renderer.rb +68 -0
- data/lib/emmett/template.rb +81 -0
- data/lib/emmett/version.rb +3 -0
- data/lib/emmett.rb +6 -0
- data/templates/default/static/images/glyphicons-halflings-white.png +0 -0
- data/templates/default/static/images/glyphicons-halflings.png +0 -0
- data/templates/default/static/javascripts/bootstrap.min.js +6 -0
- data/templates/default/static/javascripts/documentation.js +0 -0
- data/templates/default/static/javascripts/jquery-1.7.2.min.js +4 -0
- data/templates/default/static/stylesheets/bootstrap.min.css +9 -0
- data/templates/default/static/stylesheets/documentation.css +146 -0
- data/templates/default/templates/_footer.handlebars +8 -0
- data/templates/default/templates/_head.handlebars +5 -0
- data/templates/default/templates/_navigation.handlebars +19 -0
- data/templates/default/templates/index.handlebars +34 -0
- data/templates/default/templates/section.handlebars +15 -0
- metadata +201 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Darcy Laycock
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Emmett
|
2
|
+
|
3
|
+
Emmett is a tool named after Dr Emmett Brown from Back to the Future.
|
4
|
+
|
5
|
+
It's purpose is simple - given an index page and a bunch of API documents, it'll take
|
6
|
+
them and generate a nice, usable website people can use to consume the documentation.
|
7
|
+
|
8
|
+
It doesn't automate the docs or the like - it just does the simplest thing possible.
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add this line to your application's Gemfile:
|
13
|
+
|
14
|
+
gem 'emmett'
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
$ bundle
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
|
22
|
+
$ gem install emmett
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
Emmett is primarily intended to be used as a rake task.
|
27
|
+
|
28
|
+
### Emmett + Rails
|
29
|
+
|
30
|
+
Want to generate the documentation directly in your application?
|
31
|
+
|
32
|
+
To configure it, the following options are available - simply put them in
|
33
|
+
`config/application.rb` and change them as fit:
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
config.emmett.name = "Your App"
|
37
|
+
config.emmett.index_page = "doc/api.md" # Relative to doc/
|
38
|
+
config.emmett.section_dir = "doc/api" # Relative to doc/
|
39
|
+
config.emmett.output_dir = "doc/generated-api"
|
40
|
+
config.emmett.template = :default
|
41
|
+
```
|
42
|
+
|
43
|
+
It will use sane defaults (all being the same as above except the name, which is
|
44
|
+
the rails root dir titleize. I do suggest changing this). In Rails, it will
|
45
|
+
be available via `rake doc:api`.
|
46
|
+
|
47
|
+
### Emmett on it's own.
|
48
|
+
|
49
|
+
Likewise, you can use emmett inside any application thanks to the Rake Task.
|
50
|
+
|
51
|
+
In your Rakefile, simply add:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
require 'emmett/rake_task'
|
55
|
+
|
56
|
+
Emmett::RakeTask.new :docs do |t|
|
57
|
+
t.name = "Your App"
|
58
|
+
t.index_page = "api.md"
|
59
|
+
t.section_dir = "api"
|
60
|
+
t.output_dir = "output"
|
61
|
+
t.template = :default
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
Like the rails version, this will use the values above as output,
|
66
|
+
with the name being based on the current directory name.
|
67
|
+
|
68
|
+
## Contributing
|
69
|
+
|
70
|
+
1. Fork it
|
71
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
72
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
73
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
74
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/emmett.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/emmett/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Darcy Laycock"]
|
6
|
+
gem.email = ["darcy@filtersquad.com"]
|
7
|
+
gem.description = %q{Tools to make building API docs simpler.}
|
8
|
+
gem.summary = %q{Tools to make building API docs simpler.}
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "emmett"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Emmett::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency 'github-markdown'
|
19
|
+
gem.add_dependency 'github-markup'
|
20
|
+
gem.add_dependency 'nokogiri'
|
21
|
+
gem.add_dependency 'pygments.rb'
|
22
|
+
gem.add_dependency 'handlebars'
|
23
|
+
gem.add_dependency 'rake'
|
24
|
+
gem.add_dependency 'oj'
|
25
|
+
gem.add_dependency 'http_parser.rb'
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'emmett/template'
|
2
|
+
|
3
|
+
module Emmett
|
4
|
+
class Configuration
|
5
|
+
|
6
|
+
class Error < StandardError; end
|
7
|
+
|
8
|
+
attr_accessor :name, :template, :index_page, :section_dir, :output_dir
|
9
|
+
|
10
|
+
def verify!
|
11
|
+
errors = []
|
12
|
+
errors << "You must set the name attribute for emmett" if !name
|
13
|
+
errors << "You must set the template attribute for emmett" if !template
|
14
|
+
errors << "The index_page file must exist" unless index_page && File.exist?(index_page)
|
15
|
+
errors << "The section_dir directory must exist" unless section_dir && File.directory?(section_dir)
|
16
|
+
errors << "The output_dir must be set" unless output_dir
|
17
|
+
errors << "The specified template does not exist" unless to_template
|
18
|
+
if errors.any?
|
19
|
+
message = "Your configuration is invalid:\n"
|
20
|
+
errors.each { |e| message << "* #{e}\n" }
|
21
|
+
raise Error.new(message)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_template
|
26
|
+
@template_instance ||= Template[template]
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'pygments'
|
2
|
+
require 'github/markdown'
|
3
|
+
require 'github/markup'
|
4
|
+
require 'nokogiri'
|
5
|
+
|
6
|
+
require 'emmett/http_request_processor'
|
7
|
+
|
8
|
+
module Emmett
|
9
|
+
class Document < Struct.new(:file_name, :content, :type)
|
10
|
+
|
11
|
+
def self.from_path(path, type = :normal)
|
12
|
+
Document.new path, GitHub::Markup.render(path, File.read(path)), type
|
13
|
+
end
|
14
|
+
|
15
|
+
def short_name
|
16
|
+
@short_name ||= begin
|
17
|
+
if type == :index
|
18
|
+
"index"
|
19
|
+
else
|
20
|
+
File.basename(file_name).split(".")[0..-2].join(".")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def document
|
26
|
+
@document ||= Nokogiri::HTML(content)
|
27
|
+
end
|
28
|
+
|
29
|
+
def sections
|
30
|
+
@sections ||= document.css('h2').map(&:text)
|
31
|
+
end
|
32
|
+
|
33
|
+
def section_mapping
|
34
|
+
@section_mapping ||= sections.inject({}) do |acc, current|
|
35
|
+
acc[current] = current.strip.downcase.gsub(/\W+/, '-').gsub(/-+/, '-').gsub(/(^-|-$)/, '')
|
36
|
+
acc
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def title
|
41
|
+
@title ||= document.at_css('h1').text
|
42
|
+
end
|
43
|
+
|
44
|
+
def highlighted_html
|
45
|
+
@highlighted_html ||= begin
|
46
|
+
doc = document.clone
|
47
|
+
doc.css('pre[lang]').each do |block|
|
48
|
+
inner = block.at_css('code')
|
49
|
+
highlighted = Pygments.highlight(inner.inner_html, options: {encoding: 'utf-8'}, lexer: block[:lang])
|
50
|
+
highlighted_fragment = Nokogiri::HTML::DocumentFragment.parse highlighted
|
51
|
+
highlighted_fragment["data-code-lang"] = block[:lang]
|
52
|
+
block.replace highlighted_fragment
|
53
|
+
end
|
54
|
+
|
55
|
+
mapping = section_mapping
|
56
|
+
doc.css('h2').each do |header|
|
57
|
+
if (identifier = mapping[header.text])
|
58
|
+
header[:id] = identifier
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
unless short_name == 'index'
|
63
|
+
# Now, insert an endpoints content before the start of it.
|
64
|
+
toc = Nokogiri::HTML::DocumentFragment.parse toc_html
|
65
|
+
doc.at_css('h2').add_previous_sibling toc
|
66
|
+
end
|
67
|
+
|
68
|
+
doc.css('body').inner_html
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def toc_html
|
73
|
+
[].tap do |html|
|
74
|
+
html << "<h2>Endpoints</h2>"
|
75
|
+
html << "<ul id='endpoints'>"
|
76
|
+
|
77
|
+
section_mapping.each_pair do |section, slug|
|
78
|
+
html << "<li><a href='##{slug}'>#{section}</a></li>"
|
79
|
+
end
|
80
|
+
html << "</ul>"
|
81
|
+
end.join("")
|
82
|
+
end
|
83
|
+
|
84
|
+
def iterable_section_mapping
|
85
|
+
section_mapping.map { |(n,v)| {name: n, hash: v} }
|
86
|
+
end
|
87
|
+
|
88
|
+
def to_path_name
|
89
|
+
"#{short_name}.html"
|
90
|
+
end
|
91
|
+
|
92
|
+
def code_blocks
|
93
|
+
@code_blocks ||= begin
|
94
|
+
last_header = nil
|
95
|
+
blocks = []
|
96
|
+
document.css('h2, pre[lang]').each do |d|
|
97
|
+
if d.name == 'h2'
|
98
|
+
last_header = d.text
|
99
|
+
else
|
100
|
+
blocks << [d[:lang], d.at_css('code').text, last_header]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
blocks
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def http_blocks
|
108
|
+
@http_blocks ||= code_blocks.select { |r| r.first == "http" }.map { |r| r[1..-1] }
|
109
|
+
end
|
110
|
+
|
111
|
+
def http_requests
|
112
|
+
@http_requests ||= http_blocks.select do |cb|
|
113
|
+
first_line = cb[0].lines.first.strip
|
114
|
+
first_line =~ /\A[A-Z]+ (\S+) HTTP\/1\.1\Z/
|
115
|
+
end.map { |r| HTTPRequestProcessor.new(*r) }
|
116
|
+
end
|
117
|
+
|
118
|
+
def http_responses
|
119
|
+
@http_responses ||= http_blocks.select do |cb|
|
120
|
+
first_line = cb.lines.first.strip
|
121
|
+
first_line =~ /\AHTTP\/1\.1 (\d+) (\w+)\Z/
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'emmett/document'
|
2
|
+
require 'emmett/renderer'
|
3
|
+
|
4
|
+
module Emmett
|
5
|
+
class DocumentManager
|
6
|
+
|
7
|
+
def self.render!(*args)
|
8
|
+
new(*args).render!
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :configuration
|
12
|
+
|
13
|
+
def initialize(configuration)
|
14
|
+
@configuration = configuration
|
15
|
+
end
|
16
|
+
|
17
|
+
def index_document
|
18
|
+
@index_document ||= render_path(configuration.index_page, :index)
|
19
|
+
end
|
20
|
+
|
21
|
+
def inner_documents
|
22
|
+
@inner_documents ||= begin
|
23
|
+
Dir[File.join(configuration.section_dir, "**/*.md")].map do |path|
|
24
|
+
render_path path
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def inner_links
|
30
|
+
@inner_links ||= inner_documents.map do |doc|
|
31
|
+
{
|
32
|
+
doc: doc,
|
33
|
+
title: doc.title,
|
34
|
+
short: doc.short_name,
|
35
|
+
link: "./#{doc.short_name}.html",
|
36
|
+
sections: doc.iterable_section_mapping
|
37
|
+
}
|
38
|
+
end.sort_by { |r| r[:title].downcase }
|
39
|
+
end
|
40
|
+
|
41
|
+
def render_index(renderer)
|
42
|
+
render_document renderer, :index, index_document
|
43
|
+
end
|
44
|
+
|
45
|
+
def render_documents(renderer)
|
46
|
+
inner_documents.each do |document|
|
47
|
+
render_document renderer, :section, document
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def render(renderer)
|
52
|
+
render_index renderer
|
53
|
+
render_documents renderer
|
54
|
+
end
|
55
|
+
|
56
|
+
def render!
|
57
|
+
Renderer.new(configuration).tap do |renderer|
|
58
|
+
renderer.prepare_output
|
59
|
+
renderer.global_context = {
|
60
|
+
links: inner_links,
|
61
|
+
site_name: configuration.name
|
62
|
+
}
|
63
|
+
render renderer
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def all_urls
|
68
|
+
out = inner_documents.inject({}) do |acc, current|
|
69
|
+
acc[current.title] = current.http_requests.inject({}) do |ia, req|
|
70
|
+
ia[req.section] ||= []
|
71
|
+
ia[req.section] << req.request_line
|
72
|
+
ia
|
73
|
+
end
|
74
|
+
acc
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def render_document(renderer, template_name, document, context = {})
|
81
|
+
renderer.render_to document.to_path_name, template_name, context.merge(content: document.highlighted_html)
|
82
|
+
end
|
83
|
+
|
84
|
+
def render_path(path, type = :normal)
|
85
|
+
Document.from_path path, type
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'http/parser'
|
2
|
+
require 'oj'
|
3
|
+
|
4
|
+
module Emmett
|
5
|
+
class HTTPRequestProcessor
|
6
|
+
|
7
|
+
attr_reader :headers, :body, :method, :url, :http_version, :section
|
8
|
+
|
9
|
+
def initialize(request, section)
|
10
|
+
@raw_request = request.gsub(/\r?\n/m, "\r\n")
|
11
|
+
@body = ""
|
12
|
+
@section = section
|
13
|
+
parse!
|
14
|
+
end
|
15
|
+
|
16
|
+
def parse!
|
17
|
+
parser = Http::Parser.new
|
18
|
+
|
19
|
+
parser.on_headers_complete = proc do
|
20
|
+
@http_version = parser.http_version
|
21
|
+
@method = parser.http_method
|
22
|
+
@url = parser.request_url
|
23
|
+
@headers = parser.headers
|
24
|
+
end
|
25
|
+
|
26
|
+
parser.on_body = proc do |chunk|
|
27
|
+
# One chunk of the body
|
28
|
+
@body << chunk
|
29
|
+
end
|
30
|
+
|
31
|
+
parser.on_message_complete = proc do |env|
|
32
|
+
@parsed = true
|
33
|
+
end
|
34
|
+
|
35
|
+
@parsed = false
|
36
|
+
parser << @raw_request
|
37
|
+
parser << "\r\n" until @parsed
|
38
|
+
end
|
39
|
+
|
40
|
+
def has_body?
|
41
|
+
@body.strip.length > 0
|
42
|
+
end
|
43
|
+
|
44
|
+
def authenticated?
|
45
|
+
headers['Authorization'] && headers['Authorization'] =~ /bearer/i
|
46
|
+
end
|
47
|
+
|
48
|
+
def request_line
|
49
|
+
"#{method} #{url}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def json?
|
53
|
+
headers['Content-Type'] && headers['Content-Type'].include?("application/json")
|
54
|
+
end
|
55
|
+
|
56
|
+
def has_valid_json?
|
57
|
+
return @has_valid_json if instance_variable_defined?(:@has_valid_json)
|
58
|
+
|
59
|
+
begin
|
60
|
+
Oj.load body
|
61
|
+
@has_valid_json = true
|
62
|
+
rescue SyntaxError
|
63
|
+
@has_valid_json = false
|
64
|
+
end
|
65
|
+
|
66
|
+
@has_valid_json
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'emmett/configuration'
|
2
|
+
|
3
|
+
module Emmett
|
4
|
+
class Railtie < ::Rails::Railtie
|
5
|
+
R = ::Rails
|
6
|
+
|
7
|
+
config.emmett = Emmett::Configuration.new
|
8
|
+
config.emmett.name = File.basename(Dir.pwd).titleize
|
9
|
+
config.emmett.index_page = "doc/api.md"
|
10
|
+
config.emmett.section_dir = "doc/api"
|
11
|
+
config.emmett.output_dir = "doc/generated-api"
|
12
|
+
config.emmett.template = :default
|
13
|
+
|
14
|
+
rake_tasks do
|
15
|
+
require 'emmett/rake_task'
|
16
|
+
namespace :doc do
|
17
|
+
Emmett::RakeTask.new(:api) { |t| t.configuration = Rails.application.config.emmett }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/tasklib'
|
3
|
+
require 'emmett'
|
4
|
+
|
5
|
+
module Emmett
|
6
|
+
class RakeTask < ::Rake::TaskLib
|
7
|
+
include ::Rake::DSL if defined?(::Rake::DSL)
|
8
|
+
|
9
|
+
def configuration
|
10
|
+
@configuration ||= build_default_configuration
|
11
|
+
end
|
12
|
+
|
13
|
+
# Proxy each of the configuration options.
|
14
|
+
def name=(value); configuration.name = value; end
|
15
|
+
def index_page=(value); configuration.index_page = value; end
|
16
|
+
def section_dir=(value); configuration.section_dir = value; end
|
17
|
+
def output_dir=(value); configuration.output_dir = value; end
|
18
|
+
def template=(value); configuration.template = value; end
|
19
|
+
|
20
|
+
attr_accessor :task_name
|
21
|
+
attr_writer :configuration
|
22
|
+
|
23
|
+
def initialize(*args)
|
24
|
+
@task_name = args.shift || :emmett
|
25
|
+
yield self if block_given?
|
26
|
+
desc "Generates api documentation using emmett" unless ::Rake.application.last_comment
|
27
|
+
task task_name do
|
28
|
+
configuration.verify!
|
29
|
+
Emmett::DocumentManager.render! configuration
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def build_default_configuration
|
37
|
+
c = Configuration.new
|
38
|
+
c.name = File.basename(Dir.pwd)
|
39
|
+
c.index_page = "api.md"
|
40
|
+
c.section_dir = "api"
|
41
|
+
c.output_dir = "output"
|
42
|
+
c.template = :default
|
43
|
+
c
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'handlebars'
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
module Emmett
|
6
|
+
class Renderer
|
7
|
+
|
8
|
+
attr_reader :handlebars, :global_context, :configuration, :templates
|
9
|
+
attr_writer :global_context
|
10
|
+
|
11
|
+
def initialize(configuration)
|
12
|
+
@configuration = configuration
|
13
|
+
@templates = configuration.to_template
|
14
|
+
@handlebars = Handlebars::Context.new
|
15
|
+
@cache = {}
|
16
|
+
@global_context = {}
|
17
|
+
configure_handlebars
|
18
|
+
end
|
19
|
+
|
20
|
+
def render(template, context = {})
|
21
|
+
load_template(template).call global_context.merge(context)
|
22
|
+
end
|
23
|
+
|
24
|
+
def render_to(output, name, context = {})
|
25
|
+
out = File.join(output_path, output)
|
26
|
+
File.open(out, 'w+') do |f|
|
27
|
+
f.write render(name, context)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def prepare_output
|
32
|
+
FileUtils.rm_rf output_path
|
33
|
+
FileUtils.mkdir_p output_path
|
34
|
+
copy_static
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def output_path
|
40
|
+
@output_path ||= configuration.output_dir
|
41
|
+
end
|
42
|
+
|
43
|
+
def copy_static
|
44
|
+
templates.each_static_file do |file_name, path|
|
45
|
+
destination = File.join(output_path, file_name)
|
46
|
+
FileUtils.mkdir_p File.dirname(destination)
|
47
|
+
FileUtils.cp path, destination
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def configure_handlebars
|
52
|
+
handlebars.partial_missing do |name|
|
53
|
+
template = load_template "_#{name}"
|
54
|
+
lambda do |this, context, options|
|
55
|
+
template.call context
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def load_template(name)
|
61
|
+
@cache[name.to_s] ||= begin
|
62
|
+
path = templates.template_file_path("#{name}.handlebars")
|
63
|
+
path && handlebars.compile(File.read(path))
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Emmett
|
2
|
+
class Template
|
3
|
+
|
4
|
+
class Error < StandardError; end
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
def registry
|
9
|
+
@registry ||= {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def register(name, value)
|
13
|
+
registry[name.to_sym] = value
|
14
|
+
end
|
15
|
+
|
16
|
+
def [](name)
|
17
|
+
registry.fetch(name.to_sym) { raise "Emmett does not know a template by the name '#{name}'" }
|
18
|
+
end
|
19
|
+
|
20
|
+
def add(name, path)
|
21
|
+
new(name, path).tap do |template|
|
22
|
+
template.verify!
|
23
|
+
template.register
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
attr_reader :name, :root
|
30
|
+
|
31
|
+
def initialize(name, root)
|
32
|
+
@name = name && name.to_sym
|
33
|
+
@root = root.to_s
|
34
|
+
end
|
35
|
+
|
36
|
+
def verify!
|
37
|
+
errors = []
|
38
|
+
errors << "Ensure the root directory exists" unless File.directory?(root)
|
39
|
+
errors << "Ensure the name is set" unless name
|
40
|
+
errors << "Ensure the template has a templates subdirectory" unless File.directory?(template_path)
|
41
|
+
errors << "Ensure the template static path is a directory if present" if File.exist?(static_path) && !File.directory?(static_path)
|
42
|
+
if errors.any?
|
43
|
+
message = "The following errors occured trying to add your template:\n"
|
44
|
+
errors.each { |e| message << "* #{message}\n" }
|
45
|
+
raise Error.new(mesage)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def static_path
|
50
|
+
@static_path ||= File.join(root, 'static')
|
51
|
+
end
|
52
|
+
|
53
|
+
def template_path
|
54
|
+
@template_path ||= File.join(root, 'templates')
|
55
|
+
end
|
56
|
+
|
57
|
+
def template_file_path(name)
|
58
|
+
path = File.join(template_path, name)
|
59
|
+
File.exist?(path) ? path : nil
|
60
|
+
end
|
61
|
+
|
62
|
+
def has_template?(name)
|
63
|
+
File.exist?
|
64
|
+
end
|
65
|
+
|
66
|
+
def each_static_file
|
67
|
+
Dir[File.join(static_path, '**/*')].select { |f| File.file?(f) }.each do |file|
|
68
|
+
relative_name = file.gsub(static_path, "")
|
69
|
+
yield relative_name, file
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def register
|
74
|
+
self.class.register name, self
|
75
|
+
end
|
76
|
+
|
77
|
+
# Add the default template, located within the gem.
|
78
|
+
add :default, File.expand_path('../../../templates/default', __FILE__)
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|