rain-doc 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2b22db23b6df6052719672b4f3e162ad0f38cd9b
4
+ data.tar.gz: 54b6d0d5844aece4dacebf9c72a94bad783287b4
5
+ SHA512:
6
+ metadata.gz: eb235ac38baa2fd3e2f916f0760a4417cecebecfab38f85ea5b17d85bcb382c07dd18df08de0f0af04dc3e77bf3867c1f3541537e1620440578f1ec1b7fd530a
7
+ data.tar.gz: a9adc8daec64d905ced4275e71294b8332d47a669c2e50cdeb4911fc49291581683fa4a4bd1bc4c958d81212a56a14799e18f8faa19794d8f7aa91464830cf10
data/bin/raindoc ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ path = __FILE__
4
+ while File.symlink?(path)
5
+ path = File.expand_path(File.readlink(path), File.dirname(path))
6
+ end
7
+ $:.unshift(File.join(File.dirname(File.expand_path(path)), '..', 'lib'))
8
+
9
+ require 'doc'
10
+ require 'rain'
11
+
12
+ Rain::CLI.start(ARGV)
data/lib/doc.rb ADDED
@@ -0,0 +1,154 @@
1
+ require_relative 'parser'
2
+ require_relative 'doc_part'
3
+ require 'logger'
4
+
5
+ module Rain
6
+
7
+ # used each time a file needs to be parsed.
8
+ # contains all documentation parts in the file
9
+ # as well as functionality to read through file
10
+ # contents line-by-line.
11
+ class Doc
12
+ attr_accessor :parser, :type, :parts, :current_part, :lines, :title,
13
+ :file_name, :file_contents, :file_ext
14
+
15
+ @@open_response = nil
16
+ @@open_response_id = nil
17
+ @@open_param = nil
18
+ @@open_param_type = nil
19
+ @@open_param_default = nil
20
+ @@log_lines = false
21
+
22
+ # sets up the doc using the file name and contents
23
+ # as a basis.
24
+ def initialize(file_name, file_contents, log_lines = false)
25
+
26
+ # set up basic options and defaults
27
+ self.file_name = file_name
28
+ self.file_contents = file_contents
29
+ self.file_ext = File.extname(file_name)
30
+ self.parts = []
31
+ self.lines = 0
32
+ @@log_lines = log_lines
33
+
34
+ # set the doc type based on extension
35
+ case self.file_ext
36
+ when '.rb'
37
+ self.type = :RUBY
38
+ when '.md', '.txt', '.markdown', '.mdown'
39
+ self.type = :MARKDOWN
40
+ end
41
+
42
+ # set parser with file type
43
+ self.parser = Rain::Parser.new(self.type)
44
+
45
+ # set the default title to the proper-case file name
46
+ # without the extension and underscores/dashes
47
+ self.title = self.file_name.sub(self.file_ext, '')
48
+ self.title.gsub!(/_|-/, ' ')
49
+
50
+ # very simple proper-caser
51
+ self.title.gsub!(/\w+/) { |word| word.capitalize }
52
+ end
53
+
54
+ # adds the current part to the parts array
55
+ # then sets a new current part up. a part is just
56
+ # a completed DocPart from a section of comments.
57
+ # for markdown files there will only be one doc part.
58
+ def new_part
59
+ self.parts << self.current_part if !self.current_part.nil?
60
+ self.current_part = Rain::DocPart.new
61
+ end
62
+
63
+ # parses the file by looping through all of the lines.
64
+ # defers to parser functionality to figure out if the
65
+ # current line is a tag and needs to be parsed into
66
+ # the docpart.
67
+ def parse
68
+ self.new_part
69
+ self.file_contents.each_line do |line|
70
+
71
+ # parse the current line
72
+ result = self.parser.parse(line)
73
+
74
+ # if there is no documentation for the result,
75
+ # create a new part and then set the current part to nil.
76
+ if result.nil?
77
+ self.new_part
78
+ self.current_part = nil
79
+ next
80
+ else
81
+
82
+ # if new doc block is found with the current part == nil,
83
+ # create a new part
84
+ self.new_part if self.current_part.nil?
85
+ end
86
+
87
+ # log the result if the logger is enabled
88
+ Logger.new(STDOUT).info(result) if @@log_lines
89
+
90
+ # figure out what to do based on the result type
91
+ case result[:tag]
92
+ when :title
93
+ self.title = result[:title]
94
+ when :route
95
+ self.current_part.set_route(result[:route])
96
+ when :method
97
+ self.current_part.set_method(result[:method])
98
+ when :response
99
+
100
+ # open the current response tag using the code as a key
101
+ if result[:open]
102
+ @@open_response = result[:code]
103
+ @@open_response_id = result[:id]
104
+ else
105
+ @@open_response = nil
106
+ end
107
+ when :param
108
+
109
+ # open the current param tag using the name as the key
110
+ if result[:open]
111
+ @@open_param = result[:name]
112
+ @@open_param_type = result[:type]
113
+ @@open_param_default = result[:default]
114
+ else
115
+ @@open_param = nil
116
+ end
117
+ when :doc
118
+
119
+ # figure out if the doc needs to be added to an
120
+ # open response or param tag
121
+ if !@@open_response.nil?
122
+ self.current_part.append_response(@@open_response.to_i, @@open_response_id, result[:text])
123
+ elsif !@@open_param.nil?
124
+ self.current_part.append_param(@@open_param, result[:text], @@open_param_type, @@open_param_default)
125
+ else
126
+ self.current_part.append_doc(result[:text])
127
+ end
128
+ end
129
+
130
+ self.lines += 1
131
+ end
132
+
133
+ # add the part and create a new one
134
+ self.new_part
135
+
136
+ # remove any empty parts (for ruby docs)
137
+ if self.type == :RUBY
138
+ self.parts = self.parts.select{ |part| part.route != "//" }
139
+ end
140
+ end
141
+
142
+ def to_hash
143
+ return {
144
+ file_name: self.file_name,
145
+ file_contents: self.file_contents,
146
+ file_ext: self.file_ext,
147
+ title: self.title,
148
+ lines: self.lines,
149
+ parts: self.parts.map { |part| part.to_hash },
150
+ type: self.type.to_s
151
+ }
152
+ end
153
+ end
154
+ end
data/lib/doc_part.rb ADDED
@@ -0,0 +1,138 @@
1
+ module Rain
2
+ class DocPart
3
+ attr_accessor :http_method, :responses, :route, :doc, :params, :headers
4
+
5
+ def initialize
6
+ self.responses = []
7
+ self.doc = []
8
+ self.route = '//'
9
+ self.params = []
10
+ self.headers = []
11
+ end
12
+
13
+ # add or append a response with the specified code
14
+ # and the line of text passed in
15
+ def append_response(code, id, text)
16
+
17
+ # check that the response code is a number
18
+ begin
19
+ code.to_i
20
+ rescue Exception => e
21
+ raise ArgumentError, 'You can only use integer codes for HTTP response examples.'
22
+ end
23
+
24
+ # try and find the current response and id in the array
25
+ current_response = self.responses.select { |resp| resp[:code] == code && resp[:id] == id }.first
26
+
27
+ # add to array if nil
28
+ if current_response.nil?
29
+ self.responses << {
30
+ code: code,
31
+ id: id,
32
+ text: [text]
33
+ }
34
+ else
35
+
36
+ # otherwise append to the current response
37
+ self.responses.each do |resp|
38
+ if resp[:code] == code && resp[:id] == id
39
+ resp[:text] << text
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ # gets a response part by code and id and
46
+ # joins the parts of the text
47
+ def get_response(code, id)
48
+ response = self.responses.select { |resp| resp[:code] == code && resp[:id] == id }.first
49
+
50
+ raise 'Response code and id reference does not exist.' if response.nil?
51
+
52
+ response[:text].join
53
+ end
54
+
55
+ # sets the http method for the
56
+ # documentation part
57
+ def set_method(method)
58
+
59
+ # capitalize and convert to a symbol if not already a symbol
60
+ method.upcase! if !method.kind_of? Symbol
61
+
62
+ # check if the http method is valid
63
+ valid_methods = [:GET, :PUT, :POST, :PATCH, :DELETE]
64
+ if !valid_methods.include?(method.to_sym)
65
+ raise ArgumentError, "HTTP method must be valid (#{valid_methods.join(', ')})"
66
+ end
67
+
68
+ self.http_method = method
69
+ end
70
+
71
+ # appends the text to the current documentation for the part
72
+ def append_doc(text)
73
+ self.doc << text
74
+ end
75
+
76
+ # joins all of the text in the doc property of
77
+ # the part with spaces
78
+ def get_doc
79
+ self.doc.join(' ')
80
+ end
81
+
82
+ # sets the current route for the doc part
83
+ def set_route(path)
84
+ self.route = path
85
+ end
86
+
87
+ # gets the current route for the doc part
88
+ def get_route
89
+ self.route
90
+ end
91
+
92
+ # adds a parameter with the specified type. also sets a
93
+ # default value if specified
94
+ def append_param(name, description, type, default = nil)
95
+ self.params << {
96
+ name: name,
97
+ text: description,
98
+ type: type,
99
+ default: default
100
+ }
101
+ end
102
+
103
+ # gets a parameter by name. will return nil if
104
+ # the parameter does not exist
105
+ def get_param(name)
106
+ self.params.select { |param| param[:name] == name }.first
107
+ end
108
+
109
+ # adds a http header with name and
110
+ # description of the header
111
+ def append_header(name, description)
112
+ self.headers << {
113
+ name: name,
114
+ text: description
115
+ }
116
+ end
117
+
118
+ # gets a header's description from the store
119
+ def get_header(name)
120
+ header = self.headers.select { |h| h[:name] == name }.first
121
+
122
+ return nil if header.nil?
123
+
124
+ return header[:text]
125
+ end
126
+
127
+ def to_hash
128
+ return {
129
+ route: self.route,
130
+ params: self.params,
131
+ headers: self.headers,
132
+ doc: self.doc,
133
+ responses: self.responses,
134
+ http_method: self.http_method
135
+ }
136
+ end
137
+ end
138
+ end
data/lib/parser.rb ADDED
@@ -0,0 +1,158 @@
1
+ module Rain
2
+ class Parser
3
+ attr_accessor :type, :current_line
4
+
5
+ @@title_regex = /{title (.*?)}/m
6
+ @@route_regex = /{route (.*?)}/m
7
+ @@response_regex = /{response (.*?) (.*?)}/m
8
+ @@param_regex = /{param (.*?) (.*?)}/m
9
+ @@param_regex_default = /{param (.*?) (.*?) (.*?)}/m
10
+ @@method_regex = /(GET|PUT|POST|DELETE|PATCH|HEAD)/
11
+
12
+ def initialize(type)
13
+ self.type = type
14
+ end
15
+
16
+ # parses the current line, determining which tag
17
+ # the line contains and returning the result
18
+ # accordingly in a hash with the tag type
19
+ def parse(line)
20
+ # return nil if there is no # on the line for ruby.
21
+ return nil if self.type == :RUBY && !line.strip().start_with?('#')
22
+
23
+ # strip blanks and # from the current line
24
+ line = strip_current_line(line)
25
+
26
+ # convert blank lines to new lines
27
+ if line == ""
28
+ line = "\n"
29
+ end
30
+
31
+ # title tag
32
+ if is_title?(line)
33
+ return {
34
+ tag: :title,
35
+ title: extract_title(line)
36
+ }
37
+ end
38
+
39
+ # method tag
40
+ if is_method?(line)
41
+ return {
42
+ tag: :method,
43
+ method: extract_method(line)
44
+ }
45
+ end
46
+
47
+ # route tag
48
+ if is_route?(line)
49
+ return {
50
+ tag: :route,
51
+ route: extract_route(line)
52
+ }
53
+ end
54
+
55
+ # response tag. must determine whether to open the tag
56
+ # for extra docs or close it
57
+ if is_response?(line)
58
+ open = line.start_with?('{/response') ? false : true
59
+ return {
60
+ tag: :response,
61
+ code: extract_response_code(line),
62
+ open: open,
63
+ id: extract_response_id(line)
64
+ }
65
+ end
66
+
67
+ # param tag. must determine whether to open the tag
68
+ # for extra docs or close it
69
+ if is_param?(line)
70
+ open = line.start_with?('{/param') ? false : true
71
+ return {
72
+ tag: :param,
73
+ name: extract_param_name(line),
74
+ type: extract_param_type(line),
75
+ default: extract_param_default(line),
76
+ open: open
77
+ }
78
+ end
79
+
80
+ # return simple doc line if no tags fit
81
+ return {
82
+ tag: :doc,
83
+ text: line
84
+ }
85
+ end
86
+
87
+ # remove any extra spaces from the current line
88
+ # and remove the comma # at the start of the
89
+ # line if the parser is for a ruby file
90
+ def strip_current_line(line)
91
+ line.strip!
92
+
93
+ # check the current type and if ruby, remove the #
94
+ if self.type == :RUBY
95
+ line.sub!('#', '')
96
+ line.strip!
97
+ end
98
+
99
+ return line
100
+ end
101
+
102
+ def is_title?(line)
103
+ line.start_with? '{title'
104
+ end
105
+
106
+ def extract_title(line)
107
+ line[@@title_regex, 1]
108
+ end
109
+
110
+ def is_method?(line)
111
+ line.start_with? '{method'
112
+ end
113
+
114
+ def extract_method(line)
115
+ line[@@method_regex, 1]
116
+ end
117
+
118
+ def is_route?(line)
119
+ line.start_with? '{route'
120
+ end
121
+
122
+ def extract_route(line)
123
+ line[@@route_regex, 1]
124
+ end
125
+
126
+ def is_response?(line)
127
+ line.start_with?('{response') || line.start_with?('{/response')
128
+ end
129
+
130
+ def extract_response_code(line)
131
+ line[@@response_regex, 1]
132
+ end
133
+
134
+ def extract_response_id(line)
135
+ line[@@response_regex, 2]
136
+ end
137
+
138
+ def is_param?(line)
139
+ line.start_with?('{param') || line.start_with?('{/param')
140
+ end
141
+
142
+ def extract_param_name(line)
143
+ line[@@param_regex, 1]
144
+ end
145
+
146
+ def extract_param_type(line)
147
+ if line[@@param_regex_default, 2].nil?
148
+ return line[@@param_regex, 2]
149
+ end
150
+
151
+ return line[@@param_regex_default, 2]
152
+ end
153
+
154
+ def extract_param_default(line)
155
+ line[@@param_regex_default, 3]
156
+ end
157
+ end
158
+ end
data/lib/rain.rb ADDED
@@ -0,0 +1,83 @@
1
+ require 'thor'
2
+ require 'erb'
3
+ require 'ostruct'
4
+ require 'fileutils'
5
+
6
+ class Rain::CLI < Thor
7
+ @@docs = []
8
+
9
+ desc "generate [file/sources/**]", "Generates the rain documentation"
10
+ method_option :log_parse, aliases: "--lp", desc: "Show the output of each line parse"
11
+ def generate(*sources)
12
+ print "Rain is parsing files in the directories #{sources} \n"
13
+
14
+ # loop through all of the file sources and generate docs
15
+ sources.each do |source|
16
+ print "Parsing #{source} \n"
17
+ @doc = Rain::Doc.new(source, File.read(Dir.pwd + "/#{source}"), options[:log_parse])
18
+ @doc.parse
19
+
20
+ @@docs << @doc
21
+ end
22
+
23
+ print "\nBuilding html output... \n"
24
+ build_html
25
+ end
26
+
27
+ # define the ascii art for the help command
28
+ desc "help", "Shows rain help documentation"
29
+ def help
30
+ print " _ \n"
31
+ print " _( )_ \n"
32
+ print " _( )_ \n"
33
+ print " (_________) \n"
34
+ print " \\ \\ \\ \\ \n"
35
+ print " \\ \\ \\ \\ \n"
36
+ print " \n"
37
+ print "---- RAIN ----\n"
38
+ print " \n"
39
+ print "basic usage:\n"
40
+ print " rain generate file/**/*.rb\n"
41
+ end
42
+
43
+ no_commands do
44
+ def build_html
45
+
46
+ # delete the old output files and create the dir
47
+ FileUtils.rm_rf('./rain_out')
48
+ Dir.mkdir('./rain_out')
49
+ Dir.mkdir('./rain_out/css')
50
+
51
+ # copy the template css to the output dir
52
+ FileUtils.cp_r './templates/css/.', './rain_out/css'
53
+
54
+ # load the templates
55
+ layout_template = File.read('./templates/layout.erb')
56
+ doc_template = File.read('./templates/doc.erb')
57
+
58
+ # load the doc properties and parts into the doc template
59
+ @@docs.each do |doc|
60
+
61
+ # create an openstruct from the current doc and render into the template
62
+ doc_os = OpenStruct.new(doc.to_hash)
63
+
64
+ doc_rendered = ERB.new(doc_template).result(doc_os.instance_eval {binding})
65
+
66
+ # create a struct with the rendered doc output then render into the layout
67
+ layout_os = OpenStruct.new({ doc_output: doc_rendered, title: doc_os.title })
68
+ html = ERB.new(layout_template).result(layout_os.instance_eval {binding})
69
+
70
+ # write the html to a file
71
+ output_html(doc, html)
72
+ end
73
+ end
74
+
75
+ def output_html(doc, html)
76
+ # replace file_name extenstions with .html
77
+ file_name = File.basename(doc.file_name, doc.file_ext) + '.html'
78
+
79
+ # write the output to the file
80
+ File.open("./rain_out/#{file_name}", 'w') { |file| file.write(html) }
81
+ end
82
+ end
83
+ end
data/templates/doc.erb ADDED
@@ -0,0 +1,45 @@
1
+ <!-- loop through all of the doc parts for the doc and render output -->
2
+ <% parts.each do |part| %>
3
+
4
+ <!-- HTTP method and route (if both specified) -->
5
+ <% if !part[:route].nil? && !part[:http_method].nil? %>
6
+ <p><strong><%= part[:http_method] %></strong> <%= part[:route] %></p>
7
+ <% end %>
8
+
9
+ <!-- display all of the docs together for the part -->
10
+ <% part[:doc].each do |doc| %>
11
+ <p><%= doc %></p>
12
+ <% end %>
13
+
14
+ <!-- show all of the params in a table -->
15
+ <% if part[:params].length > 0 %>
16
+ <h4>Parameters</h4>
17
+ <table class="u-full-width">
18
+ <thead>
19
+ <tr>
20
+ <th>Name</th>
21
+ <th>Type</th>
22
+ <th>Default</th>
23
+ <th>Description</th>
24
+ </tr>
25
+ </thead>
26
+ <% part[:params].each do |param| %>
27
+ <tr>
28
+ <td><%= param[:name] %></td>
29
+ <td><%= param[:type] %></td>
30
+ <td><%= param[:default].nil? ? 'N/A' : param[:default] %></td>
31
+ <td><%= param[:text] %></td>
32
+ </tr>
33
+ <% end %>
34
+ </table>
35
+ <% end %>
36
+
37
+ <!-- show all of the responses with examples -->
38
+ <% if part[:responses].length > 0 %>
39
+ <h4>Response Examples</h4>
40
+ <% part[:responses].each do |response| %>
41
+ <p><strong><%= response[:code] %></strong> <%= response[:id].upcase %></p>
42
+ <pre><code><%= response[:text].join("\n") %></code></pre>
43
+ <% end %>
44
+ <% end %>
45
+ <% end %>
@@ -0,0 +1,15 @@
1
+ <html>
2
+ <head>
3
+ <title>Rain | <%= title %></title>
4
+ <link rel="stylesheet" type="text/css" href="css/normalize.css" />
5
+ <link rel="stylesheet" type="text/css" href="css/skeleton.css" />
6
+ </head>
7
+ <body>
8
+ <div class="container">
9
+ <div class="twelve columns" style="padding: 20px 0">
10
+ <h2><%= title %></h2>
11
+ <%= doc_output %>
12
+ </div>
13
+ </div>
14
+ </body>
15
+ </html>
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rain-doc
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Martin Brennan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-12-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 3.1.0
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 3.1.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: guard-rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 4.5.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 4.5.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: thor
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '='
46
+ - !ruby/object:Gem::Version
47
+ version: 0.19.1
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '='
53
+ - !ruby/object:Gem::Version
54
+ version: 0.19.1
55
+ description: "The aim of rain is to generate beautiful API documentation from a ruby
56
+ comment syntax with markdown mixed in. \n The documentation can
57
+ be inline in .rb files, or separate .md or .txt files for overall architecture documentation.
58
+ \ Rain also allows a large amount of customization when it comes
59
+ to templating and appearance of the API documentation. Branding
60
+ and unity of documentation appearance is important and rain offers a simple ebr-based
61
+ template system."
62
+ email: mjrbrennan@gmail.com
63
+ executables:
64
+ - raindoc
65
+ extensions: []
66
+ extra_rdoc_files: []
67
+ files:
68
+ - bin/raindoc
69
+ - lib/doc.rb
70
+ - lib/doc_part.rb
71
+ - lib/parser.rb
72
+ - lib/rain.rb
73
+ - templates/doc.erb
74
+ - templates/layout.erb
75
+ homepage: http://martin-brennan.github.io/rain/
76
+ licenses:
77
+ - MIT
78
+ metadata: {}
79
+ post_install_message: Rain installed successfully! Run rain generate file/paths/*.rb
80
+ to get started.
81
+ rdoc_options: []
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ requirements: []
95
+ rubyforge_project:
96
+ rubygems_version: 2.4.2
97
+ signing_key:
98
+ specification_version: 4
99
+ summary: Rain is a gem to generate beautiful and customizable API documentation, inspired
100
+ by yard and rdoc.
101
+ test_files: []