emmett 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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