emmett 0.0.1 → 0.0.2

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/LICENSE CHANGED
@@ -1,22 +1,19 @@
1
- Copyright (c) 2012 Darcy Laycock
1
+ Copyright (c) 2012 Filter Squad
2
2
 
3
- MIT License
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
4
9
 
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:
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
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.
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md CHANGED
@@ -7,6 +7,39 @@ them and generate a nice, usable website people can use to consume the documenta
7
7
 
8
8
  It doesn't automate the docs or the like - it just does the simplest thing possible.
9
9
 
10
+ ## Doc Generation
11
+
12
+ Emmett is simply a pipeline for building documentation. It expects very little, namely:
13
+
14
+ * There is a specific api index page.
15
+ * There is a directory holding all API section information as markdown files
16
+ * Each page has a h1 with the section name
17
+ * Each page has one or more h2's with an api endpoint described inside a single one.
18
+
19
+ From this, it will generate compiled HTML from a template (the default is built on bootstrap)
20
+ with:
21
+
22
+ * Markdown processed much like on GitHub.
23
+ * A nav bar with your api name + drop downs of section titles.
24
+ * An index of endpoints in each section before any of the endpoints are described.
25
+ * An api index on the home page.
26
+
27
+ It'll automatically use Pygments for code highlighting, so for http examples we encourage the syntax like:
28
+
29
+ ```http
30
+ GET /1/your/endpoint HTTP/1.1
31
+ Authorization: Bearer TOKEN
32
+ ``
33
+
34
+ ```http
35
+ HTTP/1.1 200 OK
36
+ Content-Type: text/plain
37
+
38
+ Alrighty then!
39
+ ```
40
+
41
+ AKA, Calls described in the form of a simplified request lifecycle.
42
+
10
43
  ## Installation
11
44
 
12
45
  Add this line to your application's Gemfile:
@@ -72,3 +105,7 @@ with the name being based on the current directory name.
72
105
  3. Commit your changes (`git commit -am 'Added some feature'`)
73
106
  4. Push to the branch (`git push origin my-new-feature`)
74
107
  5. Create new Pull Request
108
+
109
+ ## License
110
+
111
+ Emmett is released under the MIT License (see the [license file](https://github.com/filtersquad/emmett/blob/master/LICENSE)) and is copyright Filter Squad, 2012.
@@ -1,14 +1,21 @@
1
1
  require 'emmett/template'
2
+ require 'json'
2
3
 
3
4
  module Emmett
4
5
  class Configuration
5
6
 
6
7
  class Error < StandardError; end
7
8
 
8
- attr_accessor :name, :template, :index_page, :section_dir, :output_dir
9
+ attr_accessor :name, :template, :index_page, :section_dir, :output_dir, :json_path
9
10
 
10
11
  def verify!
11
12
  errors = []
13
+ if json_path && File.exist?(json_path)
14
+ title = from_json['title']
15
+ self.name = title if title
16
+ else
17
+ errors << "The json path does not exist (Given #{json_path.inspect})"
18
+ end
12
19
  errors << "You must set the name attribute for emmett" if !name
13
20
  errors << "You must set the template attribute for emmett" if !template
14
21
  errors << "The index_page file must exist" unless index_page && File.exist?(index_page)
@@ -22,6 +29,10 @@ module Emmett
22
29
  end
23
30
  end
24
31
 
32
+ def from_json
33
+ @json_config ||= JSON.parse(File.read(json_path))
34
+ end
35
+
25
36
  def to_template
26
37
  @template_instance ||= Template[template]
27
38
  end
@@ -6,12 +6,128 @@ require 'nokogiri'
6
6
  require 'emmett/http_request_processor'
7
7
 
8
8
  module Emmett
9
+
10
+ def self.normalize_name(text)
11
+ text.strip.downcase.gsub(/\W+/, '-').gsub(/-+/, '-').gsub(/(^-|-$)/, '')
12
+ end
13
+
14
+ class Section < Struct.new(:name)
15
+
16
+ def groups
17
+ @groups ||= []
18
+ end
19
+
20
+ def groups=(value)
21
+ @groups = Array value
22
+ end
23
+
24
+ def singular?
25
+ @groups.one?
26
+ end
27
+
28
+ def ==(other)
29
+ other.is_a?(self.class) && other.name == name
30
+ end
31
+
32
+ def to_hash
33
+ {
34
+ :name => name,
35
+ :singular => singular?,
36
+ :groups => groups.map(&:to_hash)
37
+ }.tap do |result|
38
+ if singular?
39
+ group = groups.first.to_hash
40
+ result[:endpoints] = group[:endpoints]
41
+ result[:url] = group[:url]
42
+ end
43
+ end
44
+ end
45
+
46
+ end
47
+
48
+ class Group < Struct.new(:name)
49
+
50
+ attr_reader :slug, :document
51
+
52
+ def ==(other)
53
+ other.is_a?(self.class) && other.name == name
54
+ end
55
+
56
+ def initialize(name, document)
57
+ @slug = Emmett.normalize_name name
58
+ @document = document
59
+ super name
60
+ end
61
+
62
+ def to_hash
63
+ {
64
+ :name => name,
65
+ :slug => slug,
66
+ :endpoints => endpoints.map(&:to_hash),
67
+ :url => document.to_path_name
68
+ }
69
+ end
70
+
71
+ def endpoints
72
+ @endpoints ||= []
73
+ end
74
+
75
+ def endpoints=(value)
76
+ @endpoints = Array value
77
+ end
78
+
79
+ end
80
+
81
+ class Endpoint < Struct.new(:name)
82
+
83
+ def ==(other)
84
+ other.is_a?(self.class) && other.name == name
85
+ end
86
+
87
+ attr_reader :slug, :document
88
+
89
+ def initialize(name, document)
90
+ @slug = Emmett.normalize_name name
91
+ @document = document
92
+ super name
93
+ end
94
+
95
+ def to_hash
96
+ {
97
+ :name => name,
98
+ :slug => slug,
99
+ :url => "#{document.to_path_name}##{slug}"
100
+ }
101
+ end
102
+
103
+ end
104
+
9
105
  class Document < Struct.new(:file_name, :content, :type)
10
106
 
11
107
  def self.from_path(path, type = :normal)
12
108
  Document.new path, GitHub::Markup.render(path, File.read(path)), type
13
109
  end
14
110
 
111
+ def group_names
112
+ @group_name ||= document.css('h1').map(&:text)
113
+ end
114
+
115
+ def endpoint_names
116
+ @endpoint_names ||= document.css('h2').map(&:text)
117
+ end
118
+
119
+ def groups
120
+ @groups ||= group_names.map do |name|
121
+ group = Group.new(name, self)
122
+ group.endpoints = endpoints
123
+ group
124
+ end
125
+ end
126
+
127
+ def endpoints
128
+ @endpoints ||= endpoint_names.map { |name| Endpoint.new(name, self) }
129
+ end
130
+
15
131
  def short_name
16
132
  @short_name ||= begin
17
133
  if type == :index
@@ -26,19 +142,8 @@ module Emmett
26
142
  @document ||= Nokogiri::HTML(content)
27
143
  end
28
144
 
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
145
  def title
41
- @title ||= document.at_css('h1').text
146
+ @title ||= group_names.first
42
147
  end
43
148
 
44
149
  def highlighted_html
@@ -52,7 +157,7 @@ module Emmett
52
157
  block.replace highlighted_fragment
53
158
  end
54
159
 
55
- mapping = section_mapping
160
+ mapping = endpoints.inject({}) { |acc, e| acc[e.name] = e.slug; acc }
56
161
  doc.css('h2').each do |header|
57
162
  if (identifier = mapping[header.text])
58
163
  header[:id] = identifier
@@ -74,17 +179,13 @@ module Emmett
74
179
  html << "<h2>Endpoints</h2>"
75
180
  html << "<ul id='endpoints'>"
76
181
 
77
- section_mapping.each_pair do |section, slug|
78
- html << "<li><a href='##{slug}'>#{section}</a></li>"
182
+ endpoints.each do |endpoint|
183
+ html << "<li><a href='##{endpoint.slug}'>#{endpoint.name}</a></li>"
79
184
  end
80
185
  html << "</ul>"
81
186
  end.join("")
82
187
  end
83
188
 
84
- def iterable_section_mapping
85
- section_mapping.map { |(n,v)| {name: n, hash: v} }
86
- end
87
-
88
189
  def to_path_name
89
190
  "#{short_name}.html"
90
191
  end
@@ -104,6 +205,8 @@ module Emmett
104
205
  end
105
206
  end
106
207
 
208
+ # Extra / process HTTP blocks to get extra information.
209
+
107
210
  def http_blocks
108
211
  @http_blocks ||= code_blocks.select { |r| r.first == "http" }.map { |r| r[1..-1] }
109
212
  end
@@ -1,5 +1,6 @@
1
1
  require 'emmett/document'
2
2
  require 'emmett/renderer'
3
+ require 'set'
3
4
 
4
5
  module Emmett
5
6
  class DocumentManager
@@ -26,16 +27,15 @@ module Emmett
26
27
  end
27
28
  end
28
29
 
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 }
30
+ def indexed_documents
31
+ @indexed_documents ||= inner_documents.inject({}) do |acc, document|
32
+ acc[document.short_name] = document
33
+ acc
34
+ end
35
+ end
36
+
37
+ def sections
38
+ @sections ||= Hash.new.tap { |s| process_sections s }.values
39
39
  end
40
40
 
41
41
  def render_index(renderer)
@@ -49,6 +49,7 @@ module Emmett
49
49
  end
50
50
 
51
51
  def render(renderer)
52
+ process_sections
52
53
  render_index renderer
53
54
  render_documents renderer
54
55
  end
@@ -56,8 +57,10 @@ module Emmett
56
57
  def render!
57
58
  Renderer.new(configuration).tap do |renderer|
58
59
  renderer.prepare_output
60
+ all_groups = sections.map(&:groups).flatten.sort_by(&:name).uniq
59
61
  renderer.global_context = {
60
- links: inner_links,
62
+ sections: sections.map(&:to_hash),
63
+ groups: all_groups.map(&:to_hash),
61
64
  site_name: configuration.name
62
65
  }
63
66
  render renderer
@@ -77,6 +80,25 @@ module Emmett
77
80
 
78
81
  private
79
82
 
83
+ def process_sections(into = {})
84
+ configuration_sections = Array(configuration.from_json['sections'])
85
+ seen = Set.new
86
+ configuration_sections.each do |s|
87
+ name = s['name']
88
+ section = (into[name] ||= Section.new(name))
89
+ entries = Array(s['children']).map { |n| indexed_documents[n] }.compact.map(&:groups).flatten.uniq
90
+ section.groups += entries
91
+ seen += entries
92
+ end
93
+ missing = (inner_documents.map(&:groups).flatten.uniq - seen.to_a)
94
+ missing.each do |group|
95
+ # This document doesn't have a section, so we need to specify it.
96
+ section = (into[group.name] ||= Section.new(group.name))
97
+ section.groups << group
98
+ end
99
+ into
100
+ end
101
+
80
102
  def render_document(renderer, template_name, document, context = {})
81
103
  renderer.render_to document.to_path_name, template_name, context.merge(content: document.highlighted_html)
82
104
  end
@@ -9,6 +9,7 @@ module Emmett
9
9
  config.emmett.index_page = "doc/api.md"
10
10
  config.emmett.section_dir = "doc/api"
11
11
  config.emmett.output_dir = "doc/generated-api"
12
+ config.emmett.json_path = "doc/api.json"
12
13
  config.emmett.template = :default
13
14
 
14
15
  rake_tasks do
@@ -59,7 +59,7 @@ module Emmett
59
59
 
60
60
  def load_template(name)
61
61
  @cache[name.to_s] ||= begin
62
- path = templates.template_file_path("#{name}.handlebars")
62
+ path = templates.template_file_path name
63
63
  path && handlebars.compile(File.read(path))
64
64
  end
65
65
  end
@@ -1,8 +1,9 @@
1
1
  module Emmett
2
2
  class Template
3
-
4
3
  class Error < StandardError; end
5
4
 
5
+ REQUIRED_TEMPLATES = %w(index section)
6
+
6
7
  class << self
7
8
 
8
9
  def registry
@@ -33,16 +34,25 @@ module Emmett
33
34
  @root = root.to_s
34
35
  end
35
36
 
37
+ def template_format; "handlebars"; end
38
+
36
39
  def verify!
37
40
  errors = []
38
41
  errors << "Ensure the root directory exists" unless File.directory?(root)
39
42
  errors << "Ensure the name is set" unless name
40
43
  errors << "Ensure the template has a templates subdirectory" unless File.directory?(template_path)
41
44
  errors << "Ensure the template static path is a directory if present" if File.exist?(static_path) && !File.directory?(static_path)
45
+ errors << "Missing the following files in your template: #{missing_templates.join(", ")}" if missing_templates.any?
42
46
  if errors.any?
43
47
  message = "The following errors occured trying to add your template:\n"
44
- errors.each { |e| message << "* #{message}\n" }
45
- raise Error.new(mesage)
48
+ errors.each { |e| message << "* #{e}\n" }
49
+ raise Error.new(message)
50
+ end
51
+ end
52
+
53
+ def missing_templates
54
+ @missing_templates ||= REQUIRED_TEMPLATES.select do |template|
55
+ template_file_path(template).nil?
46
56
  end
47
57
  end
48
58
 
@@ -55,7 +65,7 @@ module Emmett
55
65
  end
56
66
 
57
67
  def template_file_path(name)
58
- path = File.join(template_path, name)
68
+ path = File.join(template_path, "#{name}.#{template_format}")
59
69
  File.exist?(path) ? path : nil
60
70
  end
61
71
 
@@ -1,3 +1,3 @@
1
1
  module Emmett
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -135,12 +135,27 @@ footer.footer {
135
135
  color: #444;
136
136
  }
137
137
 
138
- #document-index .sections li {
138
+ #document-index .endpoints li {
139
139
  line-height: 1.8em;
140
140
  }
141
141
 
142
- #document-index .sections a {
142
+ #document-index .endpoints a {
143
143
  font-size: 1em;
144
144
  font-weight: normal;
145
145
  color: #666;
146
+ }
147
+
148
+ #page-navigation .dropdown .group-subtitle {
149
+ font-weight: bold;
150
+ }
151
+
152
+ #page-navigation .dropdown ul.endpoints {
153
+ list-style: none;
154
+ margin: 0;
155
+ padding: 0;
156
+ color: #333;
157
+ }
158
+
159
+ #page-navigation .dropdown ul.endpoints li a {
160
+ padding-left: 25px;
146
161
  }
@@ -0,0 +1,3 @@
1
+ {{#endpoints}}
2
+ <li class='endpoints'><a href="{{url}}">{{name}}</a></li>
3
+ {{/endpoints}}
@@ -3,16 +3,27 @@
3
3
  <div class='container'>
4
4
  <a href="./index.html" class="brand">{{site_name}}</a>
5
5
  <ul class='nav pull-right'>
6
- {{#links}}
7
- <li class='dropdown'>
8
- <a href='{{link}}' class='dropdown-toggle{{#is_current_doc}} current-document{{/is_current_doc}}' data-toggle='dropdown'>{{title}} <b class="caret"></b></a>
9
- <ul class="dropdown-menu">
10
- {{#sections}}
11
- <li><a href="{{../link}}#{{hash}}">{{name}}</a></li>
12
- {{/sections}}
6
+ {{#sections}}
7
+ <li class='dropdown'>
8
+ {{#if singular}}
9
+ {{#groups}}
10
+ <a {{#url}}href='{{url}}'{{/url}} class='dropdown-toggle' data-toggle='dropdown'>{{name}} <b class="caret"></b></a>
11
+ <ul class="dropdown-menu singular endpoints">{{>endpoints}}</ul>
12
+ {{/groups}}
13
+ {{/if}}
14
+ {{#unless singular}}
15
+ <a class='dropdown-toggle' data-toggle='dropdown'>{{name}} <b class="caret"></b></a>
16
+ <ul class="dropdown-menu multiple">
17
+ {{#groups}}
18
+ <li class='group'>
19
+ <a href='{{url}}' class='group-subtitle'>{{name}}</a>
20
+ <ul class='endpoints'>{{>endpoints}}</ul>
21
+ </li>
22
+ {{/groups}}
13
23
  </ul>
14
- </li>
15
- {{/links}}
24
+ {{/unless}}
25
+ </li>
26
+ {{/sections}}
16
27
  </ul>
17
28
  </div>
18
29
  </div>
@@ -14,16 +14,12 @@
14
14
  <h2>API Index</h2>
15
15
  <div id='inner-index'>
16
16
  <ul>
17
- {{#links}}
17
+ {{#groups}}
18
18
  <li>
19
- <h3><a href="{{link}}">{{title}}</a></h4>
20
- <ul class="sections">
21
- {{#sections}}
22
- <li><a href="{{../link}}#{{hash}}">{{name}}</a></li>
23
- {{/sections}}
24
- </ul>
19
+ <h3><a href="{{url}}">{{name}}</a></h4>
20
+ <ul class="endpoints">{{>endpoints}}</ul>
25
21
  </li>
26
- {{/links}}
22
+ {{/groups}}
27
23
  </ul>
28
24
  </div>
29
25
  </section>
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: emmett
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-07-06 00:00:00.000000000 Z
12
+ date: 2012-09-24 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: github-markdown
@@ -169,6 +169,7 @@ files:
169
169
  - templates/default/static/javascripts/jquery-1.7.2.min.js
170
170
  - templates/default/static/stylesheets/bootstrap.min.css
171
171
  - templates/default/static/stylesheets/documentation.css
172
+ - templates/default/templates/_endpoints.handlebars
172
173
  - templates/default/templates/_footer.handlebars
173
174
  - templates/default/templates/_head.handlebars
174
175
  - templates/default/templates/_navigation.handlebars
@@ -186,12 +187,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
186
187
  - - ! '>='
187
188
  - !ruby/object:Gem::Version
188
189
  version: '0'
190
+ segments:
191
+ - 0
192
+ hash: 1631976447622256131
189
193
  required_rubygems_version: !ruby/object:Gem::Requirement
190
194
  none: false
191
195
  requirements:
192
196
  - - ! '>='
193
197
  - !ruby/object:Gem::Version
194
198
  version: '0'
199
+ segments:
200
+ - 0
201
+ hash: 1631976447622256131
195
202
  requirements: []
196
203
  rubyforge_project:
197
204
  rubygems_version: 1.8.24