emmett 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|