boondoc 0.1.0 → 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 +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +3 -1
- data/exe/boondoc +1 -1
- data/lib/boondoc/assets.rb +10 -10
- data/lib/boondoc/cli.rb +34 -32
- data/lib/boondoc/config.rb +22 -20
- data/lib/boondoc/containment.rb +3 -3
- data/lib/boondoc/context.rb +9 -2
- data/lib/boondoc/core_constants.rb +154 -14
- data/lib/boondoc/docstring_parser.rb +17 -11
- data/lib/boondoc/frontend/comments.rb +3 -3
- data/lib/boondoc/frontend/prism_parser.rb +1 -1
- data/lib/boondoc/frontend/visitor.rb +11 -11
- data/lib/boondoc/generator.rb +15 -9
- data/lib/boondoc/linker.rb +8 -8
- data/lib/boondoc/markdown.rb +6 -6
- data/lib/boondoc/model/code_object.rb +7 -7
- data/lib/boondoc/model/docstring.rb +16 -7
- data/lib/boondoc/model/registry.rb +5 -5
- data/lib/boondoc/path_map.rb +8 -8
- data/lib/boondoc/preview.rb +11 -11
- data/lib/boondoc/rake_task.rb +5 -5
- data/lib/boondoc/references.rb +1 -1
- data/lib/boondoc/renderer/base.rb +3 -3
- data/lib/boondoc/renderer/docstring.rb +33 -27
- data/lib/boondoc/renderer/index.rb +3 -3
- data/lib/boondoc/renderer/method_section.rb +6 -6
- data/lib/boondoc/renderer/namespace_page.rb +30 -20
- data/lib/boondoc/stitcher.rb +4 -4
- data/lib/boondoc/type_parser.rb +6 -6
- data/lib/boondoc/version.rb +1 -1
- data/lib/boondoc/writer.rb +12 -9
- data/lib/boondoc/yard_bridge.rb +13 -8
- data/lib/boondoc/yardopts.rb +51 -24
- data/lib/boondoc.rb +34 -34
- metadata +12 -6
|
@@ -20,7 +20,7 @@ module Boondoc
|
|
|
20
20
|
}.freeze
|
|
21
21
|
ALWAYS_PRIVATE = %w[initialize_copy initialize_clone initialize_dup respond_to_missing?].freeze
|
|
22
22
|
ATTR_READERS_ONLY = %i[attr attr_reader].freeze
|
|
23
|
-
ANONYMOUS = {
|
|
23
|
+
ANONYMOUS = { "Struct" => :new, "Data" => :define, "Class" => :new, "Module" => :new }.freeze
|
|
24
24
|
|
|
25
25
|
# @param registry [Model::Registry]
|
|
26
26
|
# @param file [String] file name recorded on each object
|
|
@@ -126,7 +126,7 @@ module Boondoc
|
|
|
126
126
|
end
|
|
127
127
|
|
|
128
128
|
def ensure_namespace(name, kind)
|
|
129
|
-
parts = name.delete_prefix(
|
|
129
|
+
parts = name.delete_prefix("::").split("::")
|
|
130
130
|
parent = namespace_base(name, parts)
|
|
131
131
|
parts.each_with_index do |part, index|
|
|
132
132
|
last = index == parts.size - 1
|
|
@@ -139,7 +139,7 @@ module Boondoc
|
|
|
139
139
|
# the namespace that already holds the first segment for `Foo::Bar`, and
|
|
140
140
|
# the current namespace otherwise.
|
|
141
141
|
def namespace_base(name, parts)
|
|
142
|
-
return @registry.root if name.start_with?(
|
|
142
|
+
return @registry.root if name.start_with?("::")
|
|
143
143
|
return frame.namespace if parts.size == 1
|
|
144
144
|
|
|
145
145
|
@registry.resolve(frame.namespace, parts.first)&.namespace || frame.namespace
|
|
@@ -158,13 +158,13 @@ module Boondoc
|
|
|
158
158
|
|
|
159
159
|
def anonymous_namespace?(value)
|
|
160
160
|
value.is_a?(::Prism::CallNode) && value.receiver &&
|
|
161
|
-
ANONYMOUS[value.receiver.slice.delete_prefix(
|
|
161
|
+
ANONYMOUS[value.receiver.slice.delete_prefix("::")] == value.name
|
|
162
162
|
end
|
|
163
163
|
|
|
164
164
|
# `Foo = Struct.new(:a)`, `Foo = Data.define(:a)`, `Foo = Class.new(Base)`
|
|
165
165
|
def define_anonymous(node, value)
|
|
166
|
-
receiver = value.receiver.slice.delete_prefix(
|
|
167
|
-
namespace = ensure_namespace(node.name.to_s, receiver ==
|
|
166
|
+
receiver = value.receiver.slice.delete_prefix("::")
|
|
167
|
+
namespace = ensure_namespace(node.name.to_s, (receiver == "Module") ? :module : :class)
|
|
168
168
|
namespace.superclass ||= anonymous_superclass(receiver, value)
|
|
169
169
|
document(namespace, line(node))
|
|
170
170
|
with_frame(namespace) do
|
|
@@ -175,7 +175,7 @@ module Boondoc
|
|
|
175
175
|
|
|
176
176
|
def anonymous_superclass(receiver, value)
|
|
177
177
|
return receiver if %w[Struct Data].include?(receiver)
|
|
178
|
-
return nil unless receiver ==
|
|
178
|
+
return nil unless receiver == "Class"
|
|
179
179
|
|
|
180
180
|
first = arguments(value).first
|
|
181
181
|
first.slice if first.is_a?(::Prism::ConstantReadNode) || first.is_a?(::Prism::ConstantPathNode)
|
|
@@ -186,7 +186,7 @@ module Boondoc
|
|
|
186
186
|
|
|
187
187
|
arguments(value).grep(::Prism::SymbolNode).each do |member|
|
|
188
188
|
add_method(member.unescaped, :instance, line, attribute: :reader, documented: false)
|
|
189
|
-
next if receiver ==
|
|
189
|
+
next if receiver == "Data"
|
|
190
190
|
|
|
191
191
|
add_method("#{member.unescaped}=", :instance, line, attribute: :writer, documented: false)
|
|
192
192
|
end
|
|
@@ -196,7 +196,7 @@ module Boondoc
|
|
|
196
196
|
|
|
197
197
|
def def_scope(node)
|
|
198
198
|
case node.receiver
|
|
199
|
-
when nil then frame.singleton || frame.module_function ? :class : :instance
|
|
199
|
+
when nil then (frame.singleton || frame.module_function) ? :class : :instance
|
|
200
200
|
when ::Prism::SelfNode then :class
|
|
201
201
|
else :class if [frame.namespace.name, frame.namespace.path].include?(node.receiver.slice)
|
|
202
202
|
end
|
|
@@ -258,7 +258,7 @@ module Boondoc
|
|
|
258
258
|
end
|
|
259
259
|
|
|
260
260
|
def on_class_visibility(node)
|
|
261
|
-
visibility = node.name == :private_class_method ? :private : :public
|
|
261
|
+
visibility = (node.name == :private_class_method) ? :private : :public
|
|
262
262
|
apply_visibility(arguments(node), visibility, :class)
|
|
263
263
|
end
|
|
264
264
|
|
|
@@ -323,7 +323,7 @@ module Boondoc
|
|
|
323
323
|
end
|
|
324
324
|
|
|
325
325
|
def on_mixin(node)
|
|
326
|
-
kind = frame.singleton && node.name == :include ? :extend : node.name
|
|
326
|
+
kind = (frame.singleton && node.name == :include) ? :extend : node.name
|
|
327
327
|
arguments(node).each do |arg|
|
|
328
328
|
next unless arg.is_a?(::Prism::ConstantReadNode) || arg.is_a?(::Prism::ConstantPathNode)
|
|
329
329
|
|
data/lib/boondoc/generator.rb
CHANGED
|
@@ -25,7 +25,7 @@ module Boondoc
|
|
|
25
25
|
def run
|
|
26
26
|
report_config
|
|
27
27
|
files = @config.source_files
|
|
28
|
-
raise Error, "no Ruby files matched #{Array(@config.sources).join(
|
|
28
|
+
raise Error, "no Ruby files matched #{Array(@config.sources).join(", ")}" if files.empty?
|
|
29
29
|
|
|
30
30
|
parse(files)
|
|
31
31
|
pages = render
|
|
@@ -71,8 +71,10 @@ module Boondoc
|
|
|
71
71
|
merge_yard(files & @registry.directive_files)
|
|
72
72
|
elsif @registry.directive_sites.any?
|
|
73
73
|
sites = @registry.directive_sites
|
|
74
|
-
@reporter.warn(
|
|
75
|
-
|
|
74
|
+
@reporter.warn(
|
|
75
|
+
"#{sites.size} comment(s) use YARD directives (@!macro, @!method, ...), which boondoc " \
|
|
76
|
+
"does not expand on its own; rerun with --yard-macros. First: #{sites.first(3).join(", ")}"
|
|
77
|
+
)
|
|
76
78
|
end
|
|
77
79
|
end
|
|
78
80
|
|
|
@@ -81,8 +83,10 @@ module Boondoc
|
|
|
81
83
|
@reporter.info("yard: added #{added} object(s) from macros and directives")
|
|
82
84
|
return unless yard_files.empty?
|
|
83
85
|
|
|
84
|
-
@reporter.warn(
|
|
85
|
-
|
|
86
|
+
@reporter.warn(
|
|
87
|
+
"yard: no YARD macros or directives were found; disable yard_macros " \
|
|
88
|
+
"(--yard-macros) for this project"
|
|
89
|
+
)
|
|
86
90
|
end
|
|
87
91
|
|
|
88
92
|
def pages_for(context)
|
|
@@ -100,14 +104,16 @@ module Boondoc
|
|
|
100
104
|
def namespace_pages(context)
|
|
101
105
|
namespaces = @registry.grep(Model::NamespaceObject).select { |namespace| context.filter.visible?(namespace) }
|
|
102
106
|
namespaces.to_h { |namespace| [namespace, Renderer::NamespacePage.new(context, namespace)] }
|
|
103
|
-
|
|
107
|
+
.select { |_, page| page.content? }
|
|
104
108
|
end
|
|
105
109
|
|
|
106
110
|
def finish(pages, assets, removed)
|
|
107
111
|
warnings = @reporter.warnings
|
|
108
|
-
@reporter.info(
|
|
109
|
-
|
|
110
|
-
|
|
112
|
+
@reporter.info(
|
|
113
|
+
"boondoc: wrote #{pages.size} file(s) and #{assets.size} asset(s) to #{@config.output}" \
|
|
114
|
+
"#{", removed #{removed.size} stale" if removed.any?}" \
|
|
115
|
+
"#{", #{warnings.size} warning(s)" if warnings.any?}"
|
|
116
|
+
)
|
|
111
117
|
raise Error, "#{warnings.size} warning(s) in strict mode" if @config.strict && warnings.any?
|
|
112
118
|
|
|
113
119
|
Result.new(pages: pages.keys.sort, assets: assets.keys.sort, removed: removed, warnings: warnings)
|
data/lib/boondoc/linker.rb
CHANGED
|
@@ -6,7 +6,7 @@ module Boondoc
|
|
|
6
6
|
# Handles `{Foo#bar}`, `{Foo#bar custom text}`, `{https://example.com text}`,
|
|
7
7
|
# `{file:GUIDE.md}`, and the include forms `{include:Foo}` and
|
|
8
8
|
# `{include:file:path.md}`. Code blocks and code spans are left untouched.
|
|
9
|
-
class Linker
|
|
9
|
+
class Linker
|
|
10
10
|
REFERENCE = /(?<escape>\\)?\{(?<target>[^{}\s]+)(?:\s+(?<title>[^{}]+?))?\}/
|
|
11
11
|
INCLUDE = /\{include:(?<file>file:)?(?<target>[^{}\s]+)\}/
|
|
12
12
|
URL = %r{\A(?:[a-z][a-z0-9+.-]*://|mailto:)}i
|
|
@@ -69,13 +69,13 @@ module Boondoc
|
|
|
69
69
|
def types(types, object:, from:)
|
|
70
70
|
return nil if types.nil? || types.empty?
|
|
71
71
|
|
|
72
|
-
types.map { |type| type_markup(type, object, from) }.join(
|
|
72
|
+
types.map { |type| type_markup(type, object, from) }.join(", ")
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
private
|
|
76
76
|
|
|
77
77
|
def expand_includes(text, object, depth)
|
|
78
|
-
return text if depth > MAX_INCLUDE_DEPTH || !text.include?(
|
|
78
|
+
return text if depth > MAX_INCLUDE_DEPTH || !text.include?("{include:")
|
|
79
79
|
|
|
80
80
|
Markdown.map_prose(text) do |prose|
|
|
81
81
|
prose.gsub(INCLUDE) { included(Regexp.last_match, object, depth) }
|
|
@@ -101,10 +101,10 @@ module Boondoc
|
|
|
101
101
|
def reference(match, object, from)
|
|
102
102
|
target = match[:target]
|
|
103
103
|
title = match[:title]&.strip
|
|
104
|
-
return match[0].delete_prefix(
|
|
105
|
-
return match[0] if target.start_with?(
|
|
104
|
+
return match[0].delete_prefix("\\") if match[:escape]
|
|
105
|
+
return match[0] if target.start_with?("include:")
|
|
106
106
|
return "[#{title || target}](#{target})" if URL.match?(target)
|
|
107
|
-
return file_reference(match, target.delete_prefix(
|
|
107
|
+
return file_reference(match, target.delete_prefix("file:"), title, from) if target.start_with?("file:")
|
|
108
108
|
|
|
109
109
|
object_reference(match, title, object, from)
|
|
110
110
|
end
|
|
@@ -147,11 +147,11 @@ module Boondoc
|
|
|
147
147
|
|
|
148
148
|
# References to Ruby's own classes cannot be linked, but are not mistakes.
|
|
149
149
|
def core?(target)
|
|
150
|
-
CORE_CONSTANTS.include?(target.delete_prefix(
|
|
150
|
+
CORE_CONSTANTS.include?(target.delete_prefix("::")[/\A[A-Z]\w*/])
|
|
151
151
|
end
|
|
152
152
|
|
|
153
153
|
def location(object)
|
|
154
|
-
object.root? ?
|
|
154
|
+
object.root? ? "docs" : object.path
|
|
155
155
|
end
|
|
156
156
|
|
|
157
157
|
def type_markup(type, object, from)
|
data/lib/boondoc/markdown.rb
CHANGED
|
@@ -19,13 +19,13 @@ module Boondoc
|
|
|
19
19
|
# @return [String]
|
|
20
20
|
def map_prose(text, &)
|
|
21
21
|
state = { fence: nil, indented: false, blank: true, list: false }
|
|
22
|
-
output = +
|
|
23
|
-
prose = +
|
|
22
|
+
output = +""
|
|
23
|
+
prose = +""
|
|
24
24
|
text.each_line do |line|
|
|
25
25
|
next prose << line unless code_line?(line, state)
|
|
26
26
|
|
|
27
27
|
output << map_inline(prose, &) << line
|
|
28
|
-
prose = +
|
|
28
|
+
prose = +""
|
|
29
29
|
end
|
|
30
30
|
output << map_inline(prose, &)
|
|
31
31
|
end
|
|
@@ -33,7 +33,7 @@ module Boondoc
|
|
|
33
33
|
# @param text [String] a single table cell
|
|
34
34
|
# @return [String] the text on one line with pipes escaped
|
|
35
35
|
def table_cell(text)
|
|
36
|
-
text.gsub(/\s*\n\s*/,
|
|
36
|
+
text.gsub(/\s*\n\s*/, " ").gsub("|", '\\|').strip
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
# @api private
|
|
@@ -58,7 +58,7 @@ module Boondoc
|
|
|
58
58
|
|
|
59
59
|
# @api private
|
|
60
60
|
def in_list?(line, blank, state)
|
|
61
|
-
LIST_ITEM.match?(line) || (state[:list] && (blank || line.start_with?(
|
|
61
|
+
LIST_ITEM.match?(line) || (state[:list] && (blank || line.start_with?(" ", "\t")))
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
# @api private
|
|
@@ -77,7 +77,7 @@ module Boondoc
|
|
|
77
77
|
def map_inline(prose)
|
|
78
78
|
return prose if prose.empty?
|
|
79
79
|
|
|
80
|
-
output = +
|
|
80
|
+
output = +""
|
|
81
81
|
position = 0
|
|
82
82
|
prose.scan(INLINE_CODE) do
|
|
83
83
|
match = Regexp.last_match
|
|
@@ -27,7 +27,7 @@ module Boondoc
|
|
|
27
27
|
|
|
28
28
|
# @return [String] the fully qualified path, such as `Foo::Bar#baz`
|
|
29
29
|
def path
|
|
30
|
-
namespace.nil? || namespace.root? ? name : "#{namespace.path}::#{name}"
|
|
30
|
+
(namespace.nil? || namespace.root?) ? name : "#{namespace.path}::#{name}"
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
# @return [Boolean]
|
|
@@ -46,7 +46,7 @@ module Boondoc
|
|
|
46
46
|
|
|
47
47
|
# @return [Boolean] true when tagged `@private` or `@api private`
|
|
48
48
|
def private_api?
|
|
49
|
-
docstring.tag?(
|
|
49
|
+
docstring.tag?("private") || docstring.tags_named("api").any? { |tag| tag.text.strip == "private" }
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
|
@@ -73,7 +73,7 @@ module Boondoc
|
|
|
73
73
|
|
|
74
74
|
# @return [String] empty for the root
|
|
75
75
|
def path
|
|
76
|
-
root? ?
|
|
76
|
+
root? ? "" : super
|
|
77
77
|
end
|
|
78
78
|
|
|
79
79
|
# @return [Boolean]
|
|
@@ -95,7 +95,7 @@ module Boondoc
|
|
|
95
95
|
# @return [Array<MethodObject>] methods in that scope, `initialize` first, then by name
|
|
96
96
|
def meths(scope)
|
|
97
97
|
children.grep(MethodObject).select { |meth| meth.scope == scope }
|
|
98
|
-
|
|
98
|
+
.sort_by { |meth| [(meth.name == "initialize") ? 0 : 1, meth.name] }
|
|
99
99
|
end
|
|
100
100
|
end
|
|
101
101
|
|
|
@@ -129,12 +129,12 @@ module Boondoc
|
|
|
129
129
|
# @param name [String]
|
|
130
130
|
# @return [String] the path a method with that name and scope would have
|
|
131
131
|
def self.path_for(namespace, scope, name)
|
|
132
|
-
"#{namespace.path}#{scope == :class ?
|
|
132
|
+
"#{namespace.path}#{(scope == :class) ? "." : "#"}#{name}"
|
|
133
133
|
end
|
|
134
134
|
|
|
135
135
|
# @return [String] `#` for instance methods, `.` for class methods
|
|
136
136
|
def separator
|
|
137
|
-
scope == :class ?
|
|
137
|
+
(scope == :class) ? "." : "#"
|
|
138
138
|
end
|
|
139
139
|
|
|
140
140
|
# @return [String]
|
|
@@ -156,7 +156,7 @@ module Boondoc
|
|
|
156
156
|
# @param name [String]
|
|
157
157
|
# @param namespace [NamespaceObject]
|
|
158
158
|
# @param value [String]
|
|
159
|
-
def initialize(name, namespace, value:
|
|
159
|
+
def initialize(name, namespace, value: "")
|
|
160
160
|
super(name, namespace)
|
|
161
161
|
@value = value
|
|
162
162
|
end
|
|
@@ -7,11 +7,20 @@ module Boondoc
|
|
|
7
7
|
# Which fields are set depends on the tag: `@param` uses `name`, `types` and
|
|
8
8
|
# `text`; `@example` uses `name` as its title; `@option` adds `key` and
|
|
9
9
|
# `default`; `@overload` carries a `signature` and a nested `docstring`.
|
|
10
|
-
Tag = Struct.new(
|
|
11
|
-
|
|
10
|
+
Tag = Struct.new(
|
|
11
|
+
:tag_name,
|
|
12
|
+
:name,
|
|
13
|
+
:types,
|
|
14
|
+
:text,
|
|
15
|
+
:key,
|
|
16
|
+
:default,
|
|
17
|
+
:signature,
|
|
18
|
+
:docstring,
|
|
19
|
+
keyword_init: true
|
|
20
|
+
) do
|
|
12
21
|
def initialize(**)
|
|
13
22
|
super
|
|
14
|
-
self.text ||=
|
|
23
|
+
self.text ||= ""
|
|
15
24
|
end
|
|
16
25
|
end
|
|
17
26
|
|
|
@@ -27,7 +36,7 @@ module Boondoc
|
|
|
27
36
|
# @param text [String]
|
|
28
37
|
# @param tags [Array<Tag>]
|
|
29
38
|
# @param directives [Array<String>]
|
|
30
|
-
def initialize(text =
|
|
39
|
+
def initialize(text = "", tags = [], directives = [])
|
|
31
40
|
@text = text
|
|
32
41
|
@tags = tags
|
|
33
42
|
@directives = directives
|
|
@@ -59,11 +68,11 @@ module Boondoc
|
|
|
59
68
|
# @return [String] the first sentence of the first paragraph, on one line;
|
|
60
69
|
# falls back to the `@return` description when there is no body
|
|
61
70
|
def summary
|
|
62
|
-
paragraph = text.strip.split(/\n\s*\n/).first.to_s.gsub(/\s*\n\s*/,
|
|
71
|
+
paragraph = text.strip.split(/\n\s*\n/).first.to_s.gsub(/\s*\n\s*/, " ")
|
|
63
72
|
return paragraph[/\A.*?[.!?](?=\s|\z)/] || paragraph unless paragraph.empty?
|
|
64
73
|
|
|
65
|
-
returned = tag(
|
|
66
|
-
returned.empty? ?
|
|
74
|
+
returned = tag("return")&.text.to_s.lines.first.to_s.strip
|
|
75
|
+
returned.empty? ? "" : "Returns #{returned}"
|
|
67
76
|
end
|
|
68
77
|
end
|
|
69
78
|
end
|
|
@@ -14,14 +14,14 @@ module Boondoc
|
|
|
14
14
|
attr_reader :directive_sites
|
|
15
15
|
|
|
16
16
|
def initialize
|
|
17
|
-
@root = NamespaceObject.new(
|
|
18
|
-
@objects = {
|
|
17
|
+
@root = NamespaceObject.new("", nil, kind: :root)
|
|
18
|
+
@objects = { "" => @root }
|
|
19
19
|
@directive_sites = []
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
# @return [Array<String>] the files that contain YARD directives, in the order they were parsed
|
|
23
23
|
def directive_files
|
|
24
|
-
@directive_sites.map { |site| site.rpartition(
|
|
24
|
+
@directive_sites.map { |site| site.rpartition(":").first }.uniq
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
# @param path [String]
|
|
@@ -119,7 +119,7 @@ module Boondoc
|
|
|
119
119
|
end
|
|
120
120
|
|
|
121
121
|
def constant_candidates(start, const)
|
|
122
|
-
return [const.delete_prefix(
|
|
122
|
+
return [const.delete_prefix("::")] if const.start_with?("::")
|
|
123
123
|
|
|
124
124
|
lexical(start).map { |scope| scope.root? ? const : "#{scope.path}::#{const}" }
|
|
125
125
|
end
|
|
@@ -127,7 +127,7 @@ module Boondoc
|
|
|
127
127
|
def find_method(namespace, sep, meth)
|
|
128
128
|
return nil unless namespace.is_a?(NamespaceObject)
|
|
129
129
|
|
|
130
|
-
separators = {
|
|
130
|
+
separators = { "" => %w[# .], "#" => ["#"] }.fetch(sep, ["."])
|
|
131
131
|
ancestors(namespace).each do |ancestor|
|
|
132
132
|
separators.each do |separator|
|
|
133
133
|
found = self["#{ancestor.path}#{separator}#{meth}"]
|
data/lib/boondoc/path_map.rb
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "pathname"
|
|
4
4
|
|
|
5
5
|
module Boondoc
|
|
6
6
|
# Maps objects to output files and anchors, and computes relative links.
|
|
7
7
|
class PathMap
|
|
8
|
-
TOP_LEVEL =
|
|
8
|
+
TOP_LEVEL = "top-level.md"
|
|
9
9
|
|
|
10
10
|
# @param config [Config]
|
|
11
11
|
# @param registry [Model::Registry] consulted for classes and modules whose
|
|
@@ -30,7 +30,7 @@ module Boondoc
|
|
|
30
30
|
# @return [String, nil] the anchor inside its file; nil for namespaces
|
|
31
31
|
def anchor_for(object)
|
|
32
32
|
case object
|
|
33
|
-
when Model::MethodObject then "method-#{object.scope == :class ?
|
|
33
|
+
when Model::MethodObject then "method-#{(object.scope == :class) ? "c" : "i"}-#{escape(object.name)}"
|
|
34
34
|
when Model::ConstantObject then "constant-#{escape(object.name)}"
|
|
35
35
|
end
|
|
36
36
|
end
|
|
@@ -43,7 +43,7 @@ module Boondoc
|
|
|
43
43
|
file = file_for(object)
|
|
44
44
|
return "##{anchor}" if anchor && file == from
|
|
45
45
|
|
|
46
|
-
[relative(file, from), anchor].compact.join(
|
|
46
|
+
[relative(file, from), anchor].compact.join("#")
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
# @param file [String] output file to link to
|
|
@@ -71,7 +71,7 @@ module Boondoc
|
|
|
71
71
|
parts.unshift(segments[namespace])
|
|
72
72
|
namespace = namespace.namespace
|
|
73
73
|
end
|
|
74
|
-
parts.join(
|
|
74
|
+
parts.join("/")
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
# The file or directory name of every class and module, worked out once
|
|
@@ -93,16 +93,16 @@ module Boondoc
|
|
|
93
93
|
seen = Hash.new(0)
|
|
94
94
|
group.each do |child|
|
|
95
95
|
count = (seen[child.kind] += 1)
|
|
96
|
-
names[child] = ["#{child.name}.#{child.kind}", (count if count > 1)].compact.join(
|
|
96
|
+
names[child] = ["#{child.name}.#{child.kind}", (count if count > 1)].compact.join("-")
|
|
97
97
|
end
|
|
98
98
|
end
|
|
99
99
|
|
|
100
100
|
def escape(name)
|
|
101
|
-
name.gsub(/[^A-Za-z0-9_]/) { |char| format(
|
|
101
|
+
name.gsub(/[^A-Za-z0-9_]/) { |char| format("-%02X", char.ord) }
|
|
102
102
|
end
|
|
103
103
|
|
|
104
104
|
def encode(path)
|
|
105
|
-
path.gsub(
|
|
105
|
+
path.gsub(" ", "%20")
|
|
106
106
|
end
|
|
107
107
|
end
|
|
108
108
|
end
|
data/lib/boondoc/preview.rb
CHANGED
|
@@ -7,8 +7,8 @@ module Boondoc
|
|
|
7
7
|
# The previewer resolves links relative to the directory it was started in,
|
|
8
8
|
# so it is always started from inside the output directory.
|
|
9
9
|
class Preview
|
|
10
|
-
EXTENSION =
|
|
11
|
-
DEFAULT_HOST =
|
|
10
|
+
EXTENSION = "yusukebe/gh-markdown-preview"
|
|
11
|
+
DEFAULT_HOST = "127.0.0.1"
|
|
12
12
|
LOOPBACK_HOSTS = %w[::1 localhost].freeze
|
|
13
13
|
SILENT = { out: File::NULL, err: File::NULL }.freeze
|
|
14
14
|
|
|
@@ -30,7 +30,7 @@ module Boondoc
|
|
|
30
30
|
# @raise [Error] when the docs, the GitHub CLI, or the extension are missing
|
|
31
31
|
def start
|
|
32
32
|
raise Error, "#{@directory} does not exist; run `boondoc` first to generate the docs" unless docs?
|
|
33
|
-
raise Error,
|
|
33
|
+
raise Error, "serve needs the GitHub CLI (gh); see https://cli.github.com" unless gh?
|
|
34
34
|
|
|
35
35
|
install unless extension?
|
|
36
36
|
warn_exposed unless loopback?
|
|
@@ -40,7 +40,7 @@ module Boondoc
|
|
|
40
40
|
private
|
|
41
41
|
|
|
42
42
|
def loopback?
|
|
43
|
-
LOOPBACK_HOSTS.include?(@host) || @host.start_with?(
|
|
43
|
+
LOOPBACK_HOSTS.include?(@host) || @host.start_with?("127.")
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
# The extension answers `/__/md?path=` for any path, not only the docs, and
|
|
@@ -48,8 +48,8 @@ module Boondoc
|
|
|
48
48
|
# every file the current user can.
|
|
49
49
|
def warn_exposed
|
|
50
50
|
warn "boondoc: serving on #{@host} exposes the preview to the network. gh markdown-preview will " \
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
"return any file the current user can read, not only the docs. Use the default 127.0.0.1 " \
|
|
52
|
+
"unless everyone who can reach this machine is trusted."
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
def docs?
|
|
@@ -57,22 +57,22 @@ module Boondoc
|
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
def gh?
|
|
60
|
-
system(
|
|
60
|
+
system("gh", "--version", **SILENT) == true
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
def extension?
|
|
64
|
-
system(
|
|
64
|
+
system("gh", "markdown-preview", "--version", **SILENT) == true
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
def install
|
|
68
68
|
manual = "install it with `gh extension install #{EXTENSION}`"
|
|
69
69
|
raise Error, "serve needs the gh markdown-preview extension; #{manual}" unless @confirm_install&.call
|
|
70
|
-
raise Error, "could not install the extension; #{manual}" unless system(
|
|
70
|
+
raise Error, "could not install the extension; #{manual}" unless system("gh", "extension", "install", EXTENSION)
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
def serve
|
|
74
|
-
command = [
|
|
75
|
-
command.push(
|
|
74
|
+
command = ["gh", "markdown-preview", "--host", @host]
|
|
75
|
+
command.push("--port", @port.to_s) if @port
|
|
76
76
|
system(*command, chdir: @directory)
|
|
77
77
|
rescue Interrupt
|
|
78
78
|
nil
|
data/lib/boondoc/rake_task.rb
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
3
|
+
require "rake"
|
|
4
|
+
require "rake/tasklib"
|
|
5
|
+
require "boondoc"
|
|
6
6
|
|
|
7
7
|
module Boondoc
|
|
8
8
|
# Defines a Rake task that generates the documentation.
|
|
@@ -25,14 +25,14 @@ module Boondoc
|
|
|
25
25
|
super()
|
|
26
26
|
@name = name
|
|
27
27
|
@options = {}
|
|
28
|
-
yield
|
|
28
|
+
yield(self) if block_given?
|
|
29
29
|
define
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
private
|
|
33
33
|
|
|
34
34
|
def define
|
|
35
|
-
desc
|
|
35
|
+
desc("Generate markdown documentation with boondoc")
|
|
36
36
|
task(name) { Generator.new(Config.load(options)).run }
|
|
37
37
|
end
|
|
38
38
|
end
|
data/lib/boondoc/references.rb
CHANGED
|
@@ -22,7 +22,7 @@ module Boondoc
|
|
|
22
22
|
# Targets are expanded before they are copied, so chains such as
|
|
23
23
|
# `(see B)` -> `(see C)` work in any order. A cycle stops at the object
|
|
24
24
|
# already being expanded.
|
|
25
|
-
def expand_object(object)
|
|
25
|
+
def expand_object(object)
|
|
26
26
|
return if @visited.key?(object)
|
|
27
27
|
|
|
28
28
|
@visited[object] = true
|
|
@@ -26,19 +26,19 @@ module Boondoc
|
|
|
26
26
|
# A fenced Ruby block whose fence is longer than any fence inside the source.
|
|
27
27
|
def ruby(source)
|
|
28
28
|
longest = source.scan(/^\s*(`{3,})/).flatten.map(&:size).max || 2
|
|
29
|
-
fence =
|
|
29
|
+
fence = "`" * [3, longest + 1].max
|
|
30
30
|
"#{fence}ruby\n#{source}\n#{fence}"
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
# Joins blocks into a document with exactly one blank line between them.
|
|
34
34
|
def document(blocks)
|
|
35
|
-
blocks.flatten.compact.map { |block| block.sub(/\A\n+/,
|
|
35
|
+
blocks.flatten.compact.map { |block| block.sub(/\A\n+/, "").rstrip }.reject(&:empty?).join("\n\n") << "\n"
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
# A one-line summary of an object, with references resolved.
|
|
39
39
|
def summary(object, from)
|
|
40
40
|
text = linker.text(object.docstring.summary, object: object, from: from)
|
|
41
|
-
text = "**Deprecated.** #{text}".strip if object.docstring.tag?(
|
|
41
|
+
text = "**Deprecated.** #{text}".strip if object.docstring.tag?("deprecated")
|
|
42
42
|
Markdown.table_cell(text)
|
|
43
43
|
end
|
|
44
44
|
|