rain-doc 0.0.1

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