rtfdoc 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6cf4dfb3f18bf5958f797ccf1ee055c96befecb7cf2df7a1b7256412f254ff5f
4
+ data.tar.gz: 39bb4ecdb1f6279084be35ed1c6dc41ac73e596c7a442ed3f37a9817858387f5
5
+ SHA512:
6
+ metadata.gz: a343b4d4783ff8e42b53298de89340a9472dcb6ded881d69bf34dee7c7fdfd5aa6879795a26c38ea5e535313ea7718082a99be2b77c41958529b35271674b4ae
7
+ data.tar.gz: 2c7cdf91e981af582f8c14569d86c3a74ef91bc3f9070e1c640db828d4ada8d47224451b31833f44017e6515c748109d910f09ad8555822816e3841ef205dff7
@@ -0,0 +1,6 @@
1
+ .sass-cache/
2
+ .byebug_history
3
+ node_modules/
4
+ yarn.lock
5
+ dist/
6
+ build/
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.6.5
7
+ before_install: gem install bundler -v 1.17.3
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at cocchi.c@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem 'benchmark-ips'
7
+ gem 'byebug'
8
+ gem 'irb'
9
+ end
@@ -0,0 +1,40 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rtfdoc (0.1.0)
5
+ erubi (~> 1.9.0)
6
+ redcarpet (~> 3.5.0)
7
+ rouge (~> 3.20.0)
8
+ thor (~> 1.0.1)
9
+
10
+ GEM
11
+ remote: https://rubygems.org/
12
+ specs:
13
+ benchmark-ips (2.8.2)
14
+ byebug (11.1.3)
15
+ erubi (1.9.0)
16
+ io-console (0.5.6)
17
+ irb (1.2.4)
18
+ reline (>= 0.0.1)
19
+ minitest (5.14.1)
20
+ rake (10.5.0)
21
+ redcarpet (3.5.0)
22
+ reline (0.1.4)
23
+ io-console (~> 0.5)
24
+ rouge (3.20.0)
25
+ thor (1.0.1)
26
+
27
+ PLATFORMS
28
+ ruby
29
+
30
+ DEPENDENCIES
31
+ benchmark-ips
32
+ bundler (~> 1.17)
33
+ byebug
34
+ irb
35
+ minitest (~> 5.0)
36
+ rake (~> 10.0)
37
+ rtfdoc!
38
+
39
+ BUNDLED WITH
40
+ 1.17.3
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 ccocchi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,35 @@
1
+ # RTFDoc
2
+
3
+ Generate beautiful static documentation for your APIs.
4
+
5
+ ## Installation
6
+
7
+ You can install the gem globally using the following command. It will install the `rtfdoc` binary for generating new projects from scratch.
8
+
9
+ ```
10
+ $ gem install rtfdoc
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ You can scaffold a new project using `rtfdoc bootstrap <project_name>`. It will create a skeleton for your project, and generate needed configuration files.
16
+
17
+ Once in your project directory, you can install ruby dependencies using `bundle install` and javascript dependencies using `yarn install`.
18
+
19
+ By convention, you should put your documentation content under the `content/` directory. If you don't follow this convention, don't forget to modify the configuration file as well.
20
+
21
+ When you're done writing your documentation, you can define the order they will appear on your page using the ` config.yml` file (see `examples/`).
22
+
23
+ Finally, you can use `yarn run build` to generate the HTML/CSS/JS files.
24
+
25
+ ## Contributing
26
+
27
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ccocchi/rtfdoc. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
28
+
29
+ ## License
30
+
31
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
32
+
33
+ ## Code of Conduct
34
+
35
+ Everyone interacting in the RTFDoc project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/ccocchi/rtfdoc/blob/master/CODE_OF_CONDUCT.md).
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/rtfdoc'
4
+ require 'rtfdoc/cli'
5
+
6
+ RTFDoc::CLI.start
@@ -0,0 +1,354 @@
1
+ require 'erubi'
2
+ require 'rouge'
3
+ require 'redcarpet'
4
+ require 'tmpdir'
5
+
6
+ module RTFDoc
7
+ class AttributesComponent
8
+ def initialize(raw_attrs, title)
9
+ @attributes = YAML.load(raw_attrs)
10
+ @title = title
11
+ end
12
+
13
+ template = Erubi::Engine.new(File.read(File.expand_path('../src/attributes.erb', __dir__)))
14
+ class_eval <<-RUBY
15
+ define_method(:output) { #{template.src} }
16
+ RUBY
17
+ end
18
+
19
+ class Renderer < Redcarpet::Render::Base
20
+ attr_reader :rouge_formatter, :rouge_lexer
21
+
22
+ def initialize(*args)
23
+ super
24
+ @rouge_formatter = Rouge::Formatters::HTML.new
25
+ @rouge_lexer = Rouge::Lexers::JSON.new
26
+ end
27
+
28
+ def emphasis(text)
29
+ "<em>#{text}</em>"
30
+ end
31
+
32
+ def double_emphasis(text)
33
+ "<strong>#{text}</strong>"
34
+ end
35
+
36
+ def paragraph(text)
37
+ "<p>#{text}</p>"
38
+ end
39
+
40
+ def header(text, level)
41
+ if level == 4
42
+ %(<div class="header-table">#{text}</div>)
43
+ else
44
+ "<h#{level}>#{text}</h#{level}>"
45
+ end
46
+ end
47
+
48
+ def table(header, body)
49
+ <<-HTML
50
+ <div class="table-wrapper">
51
+ <div class="header-table">#{@table_title}</div>
52
+ <table>
53
+ <thead></thead>
54
+ <tbody>#{body}</tbody>
55
+ </table>
56
+ </div>
57
+ HTML
58
+ ensure
59
+ @table_title = nil
60
+ end
61
+
62
+ def table_row(content)
63
+ content.empty? ? nil : "<tr>#{content}</tr>"
64
+ end
65
+
66
+ def table_cell(content, alignment)
67
+ if !alignment
68
+ @table_title = content unless content.empty?
69
+ return
70
+ end
71
+
72
+ c = case alignment
73
+ when 'left' then 'definition'.freeze
74
+ when 'right' then 'property'.freeze
75
+ end
76
+
77
+ "<td class=\"cell-#{c}\">#{content}</td>"
78
+ end
79
+
80
+ def block_html(raw_html)
81
+ raw_html
82
+ end
83
+
84
+ def codespan(code)
85
+ "<code>#{code}</code>"
86
+ end
87
+
88
+ def block_code(code, language)
89
+ if language == 'attributes' || language == 'parameters'
90
+ AttributesComponent.new(code, language).output
91
+ elsif language == 'response'
92
+ <<-HTML
93
+ <div class="section-response">
94
+ <div class="response-topbar">RESPONSE</div>
95
+ <pre><code>#{rouge_formatter.format(rouge_lexer.lex(code.strip))}</code></pre>
96
+ </div>
97
+ HTML
98
+ end
99
+ end
100
+ end
101
+
102
+ class Template
103
+ def initialize(sections)
104
+ @content = sections.map(&:output).join
105
+ @menu_content = sections.map(&:menu_output).join
106
+ end
107
+
108
+ def output
109
+ template = Erubi::Engine.new(File.read(File.expand_path('../src/index.html.erb', __dir__)))
110
+ eval(template.src)
111
+ end
112
+ end
113
+
114
+ module RenderAsSection
115
+ def self.included(other)
116
+ other.attr_accessor(:include_show_button)
117
+ end
118
+
119
+ template = Erubi::Engine.new(File.read(File.expand_path('../src/section.erb', __dir__)))
120
+ module_eval <<-RUBY
121
+ define_method(:output) { #{template.src} }
122
+ RUBY
123
+
124
+ def content_to_html
125
+ RTFDoc.markdown_to_html(@content)
126
+ end
127
+
128
+ def example_to_html
129
+ @example ? RTFDoc.markdown_to_html(@example) : nil
130
+ end
131
+ end
132
+
133
+ module Anchorable
134
+ def anchor(content, class_list: nil)
135
+ %(<a href="##{anchor_id}") <<
136
+ (class_list ? %( class="#{class_list}") : '') <<
137
+ "><span>#{content}</span></a>"
138
+ end
139
+ end
140
+
141
+ class Section
142
+ include RenderAsSection
143
+ include Anchorable
144
+
145
+ attr_reader :name, :method, :path
146
+
147
+ def initialize(name, raw_content, resource: nil)
148
+ @name = name
149
+ @resource = resource
150
+ metadata = nil
151
+
152
+ if raw_content.start_with?('---')
153
+ idx = raw_content.index('---', 4)
154
+ raise 'bad format' unless idx
155
+ parse_metadata(YAML.load(raw_content.slice!(0, idx + 3)))
156
+ end
157
+
158
+ raise 'missing metadata' if resource && !@path && !@method
159
+
160
+ @content, @example = raw_content.split('$$$')
161
+ end
162
+
163
+ def id
164
+ @id ||= name
165
+ end
166
+
167
+ def anchor_id
168
+ @resource ? "#{@resource}-#{id}" : id
169
+ end
170
+
171
+ def resource_name
172
+ @resource
173
+ end
174
+
175
+ def menu_output
176
+ "<li>#{anchor(menu_title)}</li>"
177
+ end
178
+
179
+ def signature
180
+ sig = <<-HTML.strip!
181
+ <div class="endpoint-def">
182
+ <div class="method method__#{method.downcase}">#{method.upcase}</div>
183
+ <div class="path">#{path}</div>
184
+ </div>
185
+ HTML
186
+
187
+ anchor(sig)
188
+ end
189
+
190
+ private
191
+
192
+ def menu_title
193
+ @menu_title || name.capitalize
194
+ end
195
+
196
+ def parse_metadata(hash)
197
+ @id = hash['id']
198
+ @menu_title = hash['menu_title']
199
+ @path = hash['path']
200
+ @method = hash['method']
201
+ end
202
+ end
203
+
204
+ class ResourceDesc
205
+ include RenderAsSection
206
+ include Anchorable
207
+
208
+ attr_reader :resource_name
209
+
210
+ def initialize(resource_name, content)
211
+ @resource_name = resource_name
212
+ @content = content
213
+ end
214
+
215
+ def name
216
+ 'desc'
217
+ end
218
+
219
+ def anchor_id
220
+ "#{resource_name}-desc"
221
+ end
222
+
223
+ def generate_example(sections)
224
+ endpoints = sections.reject { |s| s.name == 'desc' || s.name == 'object' }
225
+ signatures = endpoints.each_with_object("") do |e, res|
226
+ res << %(<div class="resource-sig">#{e.signature}</div>)
227
+ end
228
+
229
+ @example = <<-HTML
230
+ <div class="section-response">
231
+ <div class="response-topbar">ENDPOINTS</div>
232
+ <div class="section-endpoints">#{signatures}</div>
233
+ </div>
234
+ HTML
235
+ end
236
+
237
+ def example_to_html
238
+ @example
239
+ end
240
+ end
241
+
242
+ class Resource
243
+ DEFAULT = %w[desc object index show create update destroy]
244
+
245
+ def self.build(name, paths, endpoints: nil)
246
+ endpoints ||= DEFAULT
247
+ desc = nil
248
+
249
+ sections = endpoints.each_with_object([]) do |endpoint, res|
250
+ filename = paths[endpoint]
251
+ next unless filename
252
+
253
+ content = File.read(filename)
254
+
255
+ if endpoint == 'desc'
256
+ desc = ResourceDesc.new(name, content)
257
+ res << desc
258
+ else
259
+ res << Section.new(endpoint, content, resource: name)
260
+ end
261
+ end
262
+
263
+ desc&.generate_example(sections)
264
+ Resource.new(name, sections)
265
+ end
266
+
267
+ attr_reader :name, :sections
268
+
269
+ def initialize(name, sections)
270
+ @name, @sections = name, sections
271
+ end
272
+
273
+ def output
274
+ head, *tail = sections
275
+ head.include_show_button = true
276
+
277
+ inner = sections.map(&:output).join("\n")
278
+ %(<section class="head-section">#{inner}</section>)
279
+ end
280
+
281
+ def menu_output
282
+ head, *tail = sections
283
+ <<-HTML
284
+ <li data-anchor="#{name}">
285
+ #{head.anchor(name.capitalize, class_list: 'expandable')}
286
+ <ul>#{tail.map(&:menu_output).join}</ul>
287
+ </li>
288
+ HTML
289
+ end
290
+ end
291
+
292
+ class Generator
293
+ attr_reader :renderer, :config
294
+
295
+ def initialize(config_path)
296
+ @config = YAML.load_file(config_path)
297
+ @content_dir = @config['content_dir']
298
+ @parts = {}
299
+ end
300
+
301
+ def run
302
+ tree = build_content_tree
303
+
304
+ nodes = config['resources'].map do |rs|
305
+ if rs.is_a?(Hash)
306
+ name, endpoints = rs.each_pair.first
307
+ paths = tree[name]
308
+ Resource.build(name, paths, endpoints: endpoints)
309
+ else
310
+ paths = tree[rs]
311
+ paths.is_a?(Hash) ? Resource.build(rs, paths) : Section.new(rs, File.read(paths))
312
+ end
313
+ end
314
+
315
+ out = File.new("#{Dir.tmpdir}/rtfdoc_output.html", 'w')
316
+ out.write(Template.new(nodes).output)
317
+ out.close
318
+ end
319
+
320
+ private
321
+
322
+ def build_content_tree
323
+ tree = {}
324
+ slicer = (@content_dir.length + 1)..-1
325
+ ext_slicer = -3..-1
326
+
327
+ Dir.glob("#{@content_dir}/**/*.md").each do |path|
328
+ str = path.slice(slicer)
329
+ parts = str.split('/')
330
+ filename = parts.pop
331
+ filename.slice!(ext_slicer)
332
+
333
+ leaf = parts.reduce(tree) { |h, part| h[part] || h[part] = {} }
334
+ leaf[filename] = path
335
+ end
336
+
337
+ tree
338
+ end
339
+ end
340
+
341
+ def self.renderer
342
+ @renderer ||= Redcarpet::Markdown.new(Renderer, {
343
+ underline: true,
344
+ space_after_headers: true,
345
+ fenced_code_blocks: true,
346
+ no_intra_emphasis: true,
347
+ tables: true
348
+ })
349
+ end
350
+
351
+ def self.markdown_to_html(text)
352
+ renderer.render(text)
353
+ end
354
+ end