docrb 0.2.0

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
+ SHA256:
3
+ metadata.gz: 74814d0dca672ec2099fcb9092660f198b93ccb3b454979201ad18be4db5fb25
4
+ data.tar.gz: 34660996e9148eebe8a1757f04f0be4c2f19300d9de1fd43f793e3d7e97502c6
5
+ SHA512:
6
+ metadata.gz: 6f53696d60c6bf4634dd2c2fe093c24a786b1f6cd9e87432a73e8ca4c5298d2f9c868cd8b8640dbd65bfc67b130272d2708ea6e7cdd01da142da33278dae0625
7
+ data.tar.gz: a98ca47316bdc564de37a6144dfa8613f49717313d7dee5424ce5feca10f6e91f80353a0078233461d3f6dd4202ce0ecb3635ef6641326b3e325f28b6234562c
data/.editorconfig ADDED
@@ -0,0 +1,21 @@
1
+ root = true
2
+
3
+ [*]
4
+ end_of_line = lf
5
+ indent_size = 4
6
+ indent_style = space
7
+ insert_final_newline = true
8
+ tab_width = 8
9
+ trim_trailing_whitespace = true
10
+
11
+ [*.gemspec]
12
+ indent_size = 2
13
+
14
+ [*.rb]
15
+ indent_size = 2
16
+
17
+ [*.yml]
18
+ indent_size = 2
19
+
20
+ [./bin/*]
21
+ indent_size = 2
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,70 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.2
3
+ NewCops: enable
4
+ Exclude:
5
+ - spec/**/*
6
+
7
+ Style/StringLiterals:
8
+ Enabled: true
9
+ EnforcedStyle: double_quotes
10
+
11
+ Style/StringLiteralsInInterpolation:
12
+ Enabled: true
13
+ EnforcedStyle: double_quotes
14
+
15
+ Style/Documentation:
16
+ Exclude:
17
+ - lib/docrb/module_extensions.rb
18
+
19
+ Layout/LineLength:
20
+ Max: 120
21
+ Exclude:
22
+ - exe/*
23
+ - lib/docrb/comment_parser.rb
24
+ - lib/docrb/ruby_parser.rb
25
+
26
+ Metrics/ClassLength:
27
+ Enabled: false
28
+
29
+ Metrics/MethodLength:
30
+ Enabled: false
31
+
32
+ Naming/MethodParameterName:
33
+ Exclude:
34
+ - spec/**/*
35
+
36
+ Metrics/BlockLength:
37
+ Exclude:
38
+ - exe/*
39
+ - lib/docrb/ruby_parser.rb
40
+
41
+ Metrics/PerceivedComplexity:
42
+ Exclude:
43
+ - exe/*
44
+ - lib/docrb/doc_compiler/base_container.rb
45
+ - lib/docrb/doc_compiler/base_container/computations.rb
46
+ - lib/docrb/doc_compiler/object_container.rb
47
+ - lib/docrb/resolvable.rb
48
+ - lib/docrb/ruby_parser.rb
49
+
50
+ Metrics/CyclomaticComplexity:
51
+ Exclude:
52
+ - exe/*
53
+ - lib/docrb/doc_compiler/base_container.rb
54
+ - lib/docrb/doc_compiler/base_container/computations.rb
55
+ - lib/docrb/doc_compiler/object_container.rb
56
+ - lib/docrb/resolvable.rb
57
+ - lib/docrb/ruby_parser.rb
58
+
59
+ Lint/ShadowingOuterLocalVariable:
60
+ Exclude:
61
+ - exe/*
62
+
63
+ Metrics/AbcSize:
64
+ Enabled: false
65
+
66
+ Metrics/ModuleLength:
67
+ Enabled: false
68
+
69
+ Lint/BooleanSymbol:
70
+ Enabled: false
data/Gemfile ADDED
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in docrb.gemspec
6
+ gemspec
7
+
8
+ group :development do
9
+ gem "rake", "~> 13.0"
10
+
11
+ gem "rspec", "~> 3.0"
12
+
13
+ gem "rubocop", "~> 1.8"
14
+
15
+ gem "byebug"
16
+
17
+ gem "awesome_print"
18
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,79 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ docrb (0.2.0)
5
+ docrb-html (~> 0.2)
6
+ parser (~> 3.2)
7
+ redcarpet (~> 3.6)
8
+ rouge (~> 4.1)
9
+
10
+ GEM
11
+ remote: https://rubygems.org/
12
+ specs:
13
+ ast (2.4.2)
14
+ awesome_print (1.9.2)
15
+ byebug (11.1.3)
16
+ diff-lcs (1.5.0)
17
+ docrb-html (0.2.5)
18
+ nokogiri (~> 1.14)
19
+ sassc (~> 2.4)
20
+ ffi (1.15.5)
21
+ json (2.6.3)
22
+ nokogiri (1.14.2-arm64-darwin)
23
+ racc (~> 1.4)
24
+ nokogiri (1.14.2-x86_64-darwin)
25
+ racc (~> 1.4)
26
+ parallel (1.22.1)
27
+ parser (3.2.1.0)
28
+ ast (~> 2.4.1)
29
+ racc (1.6.2)
30
+ rainbow (3.1.1)
31
+ rake (13.0.6)
32
+ redcarpet (3.6.0)
33
+ regexp_parser (2.7.0)
34
+ rexml (3.2.5)
35
+ rouge (4.1.0)
36
+ rspec (3.12.0)
37
+ rspec-core (~> 3.12.0)
38
+ rspec-expectations (~> 3.12.0)
39
+ rspec-mocks (~> 3.12.0)
40
+ rspec-core (3.12.1)
41
+ rspec-support (~> 3.12.0)
42
+ rspec-expectations (3.12.2)
43
+ diff-lcs (>= 1.2.0, < 2.0)
44
+ rspec-support (~> 3.12.0)
45
+ rspec-mocks (3.12.3)
46
+ diff-lcs (>= 1.2.0, < 2.0)
47
+ rspec-support (~> 3.12.0)
48
+ rspec-support (3.12.0)
49
+ rubocop (1.45.1)
50
+ json (~> 2.3)
51
+ parallel (~> 1.10)
52
+ parser (>= 3.2.0.0)
53
+ rainbow (>= 2.2.2, < 4.0)
54
+ regexp_parser (>= 1.8, < 3.0)
55
+ rexml (>= 3.2.5, < 4.0)
56
+ rubocop-ast (>= 1.24.1, < 2.0)
57
+ ruby-progressbar (~> 1.7)
58
+ unicode-display_width (>= 2.4.0, < 3.0)
59
+ rubocop-ast (1.26.0)
60
+ parser (>= 3.2.1.0)
61
+ ruby-progressbar (1.11.0)
62
+ sassc (2.4.0)
63
+ ffi (~> 1.9)
64
+ unicode-display_width (2.4.2)
65
+
66
+ PLATFORMS
67
+ arm64-darwin-22
68
+ x86_64-darwin-20
69
+
70
+ DEPENDENCIES
71
+ awesome_print
72
+ byebug
73
+ docrb!
74
+ rake (~> 13.0)
75
+ rspec (~> 3.0)
76
+ rubocop (~> 1.8)
77
+
78
+ BUNDLED WITH
79
+ 2.2.22
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
data/bin/console ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "docrb"
6
+ require "byebug"
7
+
8
+ # You can add fixtures and/or initialization code here to make experimenting
9
+ # with your gem easier. You can also use a different console, if you like.
10
+
11
+ # (If you use this, don't forget to add pry to your Gemfile!)
12
+ # require "pry"
13
+ # Pry.start
14
+
15
+ require "irb"
16
+ IRB.start(__FILE__)
data/bin/json ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "docrb"
6
+ require "byebug"
7
+ require "json"
8
+
9
+ def run
10
+ data = Docrb.parse_folder(ARGV[0])
11
+ compiler = Docrb::DocCompiler.new
12
+ data.each { |f| compiler.append(f) }
13
+ puts compiler.to_h.to_json
14
+ end
15
+
16
+ run
data/bin/md ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "docrb"
6
+
7
+ data = File.read(ARGV[0]).to_s
8
+ puts Docrb::Markdown.render(data)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/docrb.gemspec ADDED
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/docrb/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "docrb"
7
+ spec.version = Docrb::VERSION
8
+ spec.authors = ["Victor Gama"]
9
+ spec.email = ["hey@vito.io"]
10
+
11
+ spec.summary = "An opinionated documentation parser"
12
+ spec.description = spec.summary
13
+ spec.homepage = "https://github.com/heyvito/docrb"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 3.2"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = "#{spec.homepage}/tree/trunk/lib/docrb"
19
+ spec.metadata["changelog_uri"] = spec.homepage
20
+
21
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
22
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
23
+ end
24
+ spec.bindir = "exe"
25
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
26
+ spec.require_paths = ["lib"]
27
+
28
+ spec.add_dependency "docrb-html", "~> 0.2"
29
+ spec.add_dependency "parser", "~> 3.2"
30
+ spec.add_dependency "redcarpet", "~> 3.6"
31
+ spec.add_dependency "rouge", "~> 4.1"
32
+ spec.metadata["rubygems_mfa_required"] = "true"
33
+ end
data/exe/docrb ADDED
@@ -0,0 +1,205 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "json"
6
+ require "fileutils"
7
+ require "optparse"
8
+ require "open3"
9
+ require "tmpdir"
10
+ require "renderer"
11
+
12
+ require "docrb"
13
+
14
+ def exec(cmd, *args, **kwargs)
15
+ _, stdout_and_err, wait_thread = Open3.popen2e(cmd, *args, **kwargs)
16
+ status = wait_thread.value
17
+ {
18
+ code: status.exitstatus,
19
+ output: stdout_and_err.read.chomp
20
+ }
21
+ end
22
+
23
+ def git(*args, **kwargs)
24
+ exec("git", *args, **kwargs)
25
+ end
26
+
27
+ def render_html(*args, **kwargs)
28
+ exec("docrb-html", *args, **kwargs)
29
+ end
30
+
31
+ def run
32
+ options = {}
33
+ opts = OptionParser.new do |opts|
34
+ opts.banner = "Usage: bin/docrb [options] [input directory] [output directory]"
35
+ gemspec_info = "When omitted, Docrb attempts to extract information from a .gemspec file in the provided input directory."
36
+
37
+ opts.on("--help", "Prints this help") do
38
+ puts opts
39
+ exit
40
+ end
41
+
42
+ opts.on("-bPATH", "--base=PATH",
43
+ "Base directory to search for source files. Defaults to the provided input directory.") do |b|
44
+ options[:base] = b
45
+ end
46
+
47
+ opts.on("-rPATH", "--readme=PATH",
48
+ "Path for README.md file. When omitted, Docrb searches for a README.md file in the provided input directory.") do |r|
49
+ options[:readme] = r
50
+ end
51
+
52
+ opts.on("-nNAME", "--name=NAME", "Name of the project being documented. #{gemspec_info}") do |n|
53
+ options[:name] = n
54
+ end
55
+
56
+ opts.on("-sSUMMARY", "--summary=SUMMARY", "Short summary of the project being documented. #{gemspec_info}") do |d|
57
+ options[:summary] = d
58
+ end
59
+
60
+ opts.on("-hURL", "--host=URL", "URL for the gem's hosted URL. #{gemspec_info}") do |u|
61
+ options[:host_url] = u
62
+ end
63
+
64
+ opts.on("-gURL", "--git-repo=URL",
65
+ "URL for the repository containing the documented project. When omitted, Docrb attempts to extract this information from the .git directory present in the provided input directory, if any.") do |u|
66
+ options[:git_url] = u
67
+ end
68
+
69
+ opts.on("--authors a,b,c", "List of name of project authors. #{gemspec_info}") do |list|
70
+ options[:authors] = list
71
+ end
72
+
73
+ opts.on("-lLICENSE", "--license=LICENSE", "The project's license. #{gemspec_info}") do |license|
74
+ options[:license] = license
75
+ end
76
+ end
77
+
78
+ opts.parse!
79
+
80
+ if ARGV.length != 2
81
+ puts opts
82
+ exit(1)
83
+ end
84
+
85
+ input = ARGV[0]
86
+ output = ARGV[1]
87
+
88
+ unless File.exist? input
89
+ puts "#{input}: Does not exist"
90
+ exit(1)
91
+ end
92
+
93
+ if File.exist? output
94
+ unless File.directory? output
95
+ puts "#{output}: Exists and is not a directory."
96
+ exit(1)
97
+ end
98
+ else
99
+ begin
100
+ FileUtils.mkdir_p output
101
+ rescue StandardError => e
102
+ puts e
103
+ exit(1)
104
+ end
105
+ end
106
+
107
+ tmp_output = Dir.mktmpdir
108
+ data_path = File.join(tmp_output, "data.json")
109
+ markdown_path = File.join(tmp_output, "readme.html")
110
+ metadata_path = File.join(tmp_output, "metadata.json")
111
+ readme_path = File.join(input, "README.md")
112
+ base_path = input
113
+
114
+ if options[:readme]
115
+ readme_path = options[:readme]
116
+ unless File.exist? readme_path
117
+ puts "--readme was provided, but #{readme_path} could not be found."
118
+ exit(1)
119
+ end
120
+ end
121
+
122
+ spec = Docrb::Spec.parse_folder(input) || {}
123
+
124
+ %i[name summary host_url git_url authors license].each do |k|
125
+ spec[k] = options[k] if options.key? k
126
+ end
127
+
128
+ if spec[:name] == "" || spec[:name].nil?
129
+ puts "Docrb could not detect the project name. Please check your .gemspec, or provide one manually using --name."
130
+ exit(1)
131
+ end
132
+
133
+ if spec[:summary] == "" || spec[:summary].nil?
134
+ puts "Docrb could not detect the project's summary. Please check your .gemspec, or provide one manually using --summary."
135
+ exit(1)
136
+ end
137
+
138
+ if options[:base]
139
+ path = File.join(input, options[:base])
140
+ unless File.exist? path
141
+ puts "--base was provided, but #{path} does not exist."
142
+ exit(1)
143
+ end
144
+ base_path = path
145
+ end
146
+
147
+ git_tip = nil
148
+ git_root = nil
149
+ git_status = git("status", "--porcelain", chdir: input)
150
+ if (git_status[:code]).zero?
151
+ if git_status[:output].length.positive?
152
+ puts "WARNING: Your local git copy seems to be dirty. Consider commiting your changes before generating docs."
153
+ end
154
+ tip_data = git("rev-parse", "HEAD", chdir: input)
155
+ if tip_data[:code] != 0
156
+ puts "ERROR: Acquiring repository information failed:"
157
+ puts tip_data[:output]
158
+ puts "------- 8< cut here"
159
+ puts "Aborting."
160
+ exit 1
161
+ end
162
+ toplevel = git("rev-parse", "--show-toplevel", chdir: input)
163
+ if toplevel[:code] != 0
164
+ puts "ERROR: Acquiring repository toplevel failed:"
165
+ puts tip_data[:output]
166
+ puts "------- 8< cut here"
167
+ puts "Aborting."
168
+ exit 1
169
+ end
170
+
171
+ git_tip = tip_data[:output]
172
+ git_root = toplevel[:output]
173
+ else
174
+ puts "git status returned status #{git_status[:code]}; avoiding git..."
175
+ end
176
+
177
+ spec[:git_tip] = git_tip if git_tip
178
+ spec[:git_root] = git_root if git_root
179
+ spec[:timestamp] = Time.now.utc.strftime("%A, %d %b %Y %l:%M %p GMT")
180
+ spec[:version] = Docrb::VERSION
181
+
182
+ data = Docrb.parse_folder(base_path)
183
+ compiler = Docrb::DocCompiler.new
184
+ data.each { |f| compiler.append(f) }
185
+
186
+ File.write(data_path, compiler.to_h.to_json)
187
+ puts "Created: #{data_path}"
188
+
189
+ if File.exist? readme_path
190
+ puts "Found #{readme_path}"
191
+ md = File.read(readme_path).to_s
192
+ result = Docrb::Markdown.render(md)
193
+ File.write(markdown_path, result)
194
+ puts "Created #{markdown_path}"
195
+ else
196
+ puts "#{readme_path} does not exist. Skipping..."
197
+ end
198
+ File.write(metadata_path, spec.to_json)
199
+ puts "Created #{metadata_path}"
200
+
201
+ puts "Generating HTML into #{output}"
202
+ Renderer.new(tmp_output, output).render
203
+ end
204
+
205
+ run
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docrb
4
+ class CommentParser
5
+ # CodeExampleBlock represents a list of characters of a code example
6
+ class CodeExampleBlock
7
+ attr_reader :code
8
+
9
+ def initialize
10
+ @code = []
11
+ end
12
+
13
+ def <<(text_block)
14
+ @code << "\n" unless empty?
15
+ @code << text_block.text
16
+ end
17
+
18
+ def empty?
19
+ @code.empty?
20
+ end
21
+
22
+ def normalize
23
+ @code
24
+ .map { |txt| txt.split("\n") }
25
+ .map { |item| item.empty? ? "" : item }
26
+ .flatten
27
+ .map { |line| line.gsub(/^\s{2}/, "") }
28
+ .join("\n")
29
+ end
30
+
31
+ def to_h
32
+ { type: :code_example, contents: normalize }
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docrb
4
+ class CommentParser
5
+ # CodeExampleParser attempts to extract code examples from a documentation
6
+ # block.
7
+ class CodeExampleParser
8
+ def self.process(components)
9
+ new_components = []
10
+ code_example_group = CodeExampleBlock.new
11
+ components.each do |c|
12
+ if !c.is_a?(TextBlock) || !c.text.start_with?(" ")
13
+ unless code_example_group.empty?
14
+ new_components << code_example_group
15
+ code_example_group = CodeExampleBlock.new
16
+ end
17
+ new_components << c
18
+ next
19
+ end
20
+
21
+ code_example_group << c
22
+ end
23
+
24
+ new_components << code_example_group unless code_example_group.empty?
25
+ new_components
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docrb
4
+ class CommentParser
5
+ # FieldBlock represents a list of fields obtained by FieldListParser
6
+ class FieldBlock
7
+ attr_reader :fields
8
+
9
+ def initialize(fields)
10
+ @fields = fields
11
+ end
12
+
13
+ def to_h
14
+ { type: :field_block, contents: fields }
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docrb
4
+ class CommentParser
5
+ # FieldListParser parses a field block (representing arguments of an method,
6
+ # for instance) into a specialised structure.
7
+ class FieldListParser
8
+ FIELD_FORMAT_REGEXP = /^([a-z_][0-9a-z_]*:?)\s+- /i
9
+
10
+ def initialize(text)
11
+ @text = text
12
+ @data = []
13
+ @current = []
14
+ @result = {}
15
+ @dash_index = nil
16
+ end
17
+
18
+ def detect
19
+ @text.each_char do |c|
20
+ next @current << c unless c == LINE_BREAK
21
+ return false unless handle_linebreak
22
+ end
23
+ return false unless handle_linebreak
24
+
25
+ flush_current_field!
26
+ true
27
+ end
28
+
29
+ def handle_linebreak
30
+ return true if @current.empty?
31
+
32
+ # Here's a linebreak. Handle it as needed.
33
+ @current = @current.join
34
+ if infer_field_alignment(@current)
35
+ flush_current_field!
36
+ @data << @current
37
+ else
38
+ # This is not a field. May be a continuation.
39
+ # Can it be a continuation?
40
+ return false if @data.empty?
41
+
42
+ # Yep. It may be. Is it?
43
+ return false unless continuation?(@current)
44
+
45
+ # It is. Append to data.
46
+ @data << @current.strip
47
+ end
48
+ @current = []
49
+ true
50
+ end
51
+
52
+ def flush_current_field!
53
+ return if @data.empty?
54
+
55
+ data = @data.join(" ")
56
+ @data = []
57
+ field_name = FIELD_FORMAT_REGEXP.match(data)[1]
58
+ text = data.slice(@dash_index + 1...).strip
59
+ @result[field_name] = text
60
+ end
61
+
62
+ def continuation?(line)
63
+ return false unless @dash_index
64
+
65
+ # until dash_index, we should have spaces
66
+ return false if line.length < @dash_index
67
+
68
+ return false unless line.slice(0..@dash_index + 1).strip.empty?
69
+
70
+ true
71
+ end
72
+
73
+ def infer_field_alignment(line)
74
+ # is the line a field?
75
+ return false unless FIELD_FORMAT_REGEXP.match?(line)
76
+
77
+ if @dash_index
78
+ # does it match the same alignment?
79
+ return false if line[@dash_index] != DASH
80
+ else
81
+ @dash_index = line.index(DASH)
82
+ end
83
+
84
+ true
85
+ end
86
+
87
+ attr_reader :result
88
+ end
89
+ end
90
+ end