docrb 0.2.0
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 +7 -0
- data/.editorconfig +21 -0
- data/.rspec +3 -0
- data/.rubocop.yml +70 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +79 -0
- data/Rakefile +12 -0
- data/bin/console +16 -0
- data/bin/json +16 -0
- data/bin/md +8 -0
- data/bin/setup +8 -0
- data/docrb.gemspec +33 -0
- data/exe/docrb +205 -0
- data/lib/docrb/comment_parser/code_example_block.rb +36 -0
- data/lib/docrb/comment_parser/code_example_parser.rb +29 -0
- data/lib/docrb/comment_parser/field_block.rb +18 -0
- data/lib/docrb/comment_parser/field_list_parser.rb +90 -0
- data/lib/docrb/comment_parser/text_block.rb +43 -0
- data/lib/docrb/comment_parser.rb +272 -0
- data/lib/docrb/doc_compiler/base_container/computations.rb +178 -0
- data/lib/docrb/doc_compiler/base_container.rb +123 -0
- data/lib/docrb/doc_compiler/doc_attribute.rb +58 -0
- data/lib/docrb/doc_compiler/doc_blocks.rb +111 -0
- data/lib/docrb/doc_compiler/doc_class.rb +43 -0
- data/lib/docrb/doc_compiler/doc_method.rb +66 -0
- data/lib/docrb/doc_compiler/doc_module.rb +9 -0
- data/lib/docrb/doc_compiler/file_ref.rb +41 -0
- data/lib/docrb/doc_compiler/object_container.rb +68 -0
- data/lib/docrb/doc_compiler.rb +55 -0
- data/lib/docrb/markdown.rb +62 -0
- data/lib/docrb/module_extensions.rb +13 -0
- data/lib/docrb/resolvable.rb +178 -0
- data/lib/docrb/ruby_parser.rb +630 -0
- data/lib/docrb/spec.rb +31 -0
- data/lib/docrb/version.rb +5 -0
- data/lib/docrb.rb +71 -0
- metadata +139 -0
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
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
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
data/bin/setup
ADDED
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
|