manager 0.0.0 → 0.1.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/CHART.html +1270 -0
- data/MANUAL.html +1252 -0
- data/bin/manager +43 -0
- data/examples/array/CHART.html +1376 -0
- data/examples/array/MANUAL.html +1126 -0
- data/examples/array/spec +3438 -0
- data/lib/manager.rb +528 -0
- data/lib/manager/annotation +96 -0
- data/lib/manager/input +189 -0
- data/lib/manager/js +257 -0
- data/lib/manager/refine_module +142 -0
- data/lib/manager/refine_object_mapping +143 -0
- data/lib/manager/refine_test +97 -0
- data/lib/manager/render +1228 -0
- data/lib/manager/spell_check +49 -0
- data/lib/manager/test +404 -0
- data/lib/manager/test_helper +9 -0
- data/license +9 -0
- data/manager.gemspec +21 -0
- data/spec/alternatives_implemented.png +0 -0
- data/spec/alternatives_unimplemented.png +0 -0
- data/spec/annotations.png +0 -0
- data/spec/benchmark_test.png +0 -0
- data/spec/citation.png +0 -0
- data/spec/code_block.png +0 -0
- data/spec/context_module.png +0 -0
- data/spec/documentation +1289 -0
- data/spec/external_link.png +0 -0
- data/spec/image.png +0 -0
- data/spec/list.png +0 -0
- data/spec/long.png +0 -0
- data/spec/main_and_object.png +0 -0
- data/spec/markup.png +0 -0
- data/spec/module_diagram.png +0 -0
- data/spec/navigation.png +0 -0
- data/spec/nested_section_headers.png +0 -0
- data/spec/ruby.png +0 -0
- data/spec/setup_teardown.png +0 -0
- data/spec/short.png +0 -0
- data/spec/signature.png +0 -0
- data/spec/spec +76 -0
- data/spec/spec_example.png +0 -0
- data/spec/table.png +0 -0
- data/spec/test_header.png +0 -0
- data/spec/test_non_unit_spec +184 -0
- data/spec/test_program +71 -0
- data/spec/test_unit_spec +790 -0
- data/spec/tutorial_1.png +0 -0
- data/spec/tutorial_2.png +0 -0
- data/spec/tutorial_3.png +0 -0
- data/spec/tutorial_4.png +0 -0
- data/spec/tutorial_5.png +0 -0
- data/spec/tutorial_6.png +0 -0
- data/spec/tutorial_7.png +0 -0
- data/spec/tutorial_8.png +0 -0
- data/spec/unambiguous_links.png +0 -0
- data/spec/unit_test_failure.png +0 -0
- data/spec/unit_test_raise.png +0 -0
- data/spec/unit_test_receiver.png +0 -0
- data/spec/unit_test_succeed.png +0 -0
- data/spec/unit_test_success.png +0 -0
- data/spec/unit_test_throw.png +0 -0
- data/spec/valid_heading.png +0 -0
- data/spec/with_expr.png +0 -0
- data/spec/without_expr.png +0 -0
- data/theme/2016a.css +670 -0
- data/theme/coderay_github.css +132 -0
- metadata +140 -11
data/lib/manager.rb
CHANGED
@@ -0,0 +1,528 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2016 sawa
|
4
|
+
|
5
|
+
# stdlib
|
6
|
+
require "pathname"
|
7
|
+
require "stringio"
|
8
|
+
require "objspace"
|
9
|
+
require "coverage"
|
10
|
+
|
11
|
+
# gems
|
12
|
+
require "benchmark/ips"
|
13
|
+
require "dom"
|
14
|
+
require "coderay"
|
15
|
+
|
16
|
+
# project files
|
17
|
+
load("#{__dir__}/manager/refine_module")
|
18
|
+
load("#{__dir__}/manager/refine_test")
|
19
|
+
load("#{__dir__}/manager/input")
|
20
|
+
load("#{__dir__}/manager/test")
|
21
|
+
load("#{__dir__}/manager/test_helper")
|
22
|
+
load("#{__dir__}/manager/annotation")
|
23
|
+
load("#{__dir__}/manager/render")
|
24
|
+
load("#{__dir__}/manager/refine_object_mapping")
|
25
|
+
load("#{__dir__}/manager/spell_check")
|
26
|
+
|
27
|
+
class Manager
|
28
|
+
DebugDirs = [
|
29
|
+
$0,
|
30
|
+
File.expand_path("#{__dir__}/../bin"),
|
31
|
+
__dir__,
|
32
|
+
Gem.loaded_specs["benchmark-ips"].gem_dir,
|
33
|
+
]
|
34
|
+
Gem.loaded_specs.clear
|
35
|
+
Main = TOPLEVEL_BINDING.receiver
|
36
|
+
#! This format must be applicable to methods whose normal form ends with `?`, `!`, or `=`.
|
37
|
+
# Also, it must save the alternative part like `2` from being in the first part of the method, to
|
38
|
+
# avoid syntax error.
|
39
|
+
AlternativeMethod = /\A(?!.*__.+__)(.+)__[^?!=]+([?!=]?)\z/
|
40
|
+
InterruptionInactive = Proc.new{print "\b\b\b\b\b"}
|
41
|
+
|
42
|
+
using ModuleRefinement
|
43
|
+
using ObjectMappingRefinement
|
44
|
+
using TesterRefinement
|
45
|
+
|
46
|
+
class Spec
|
47
|
+
attr_reader :i, :items
|
48
|
+
attr_accessor :header, :documentation, :hidden, :type, :aliases, :alts
|
49
|
+
def initialize; @hidden, @alts, @items = false, {}, [] end
|
50
|
+
def undocumented_mark
|
51
|
+
@undocumented = true
|
52
|
+
self
|
53
|
+
end
|
54
|
+
def order_fix; @i = Manager.counts[:spec] += 1 end
|
55
|
+
def missing? type, module_documentation, module_hidden
|
56
|
+
#! Cannot replace `===` with `kind_of?` because `e` may be a `SanitizedObject`.
|
57
|
+
@type = Render::Tag.new(module_hidden || @hidden, nil, type.to_s)
|
58
|
+
if module_documentation.! and @undocumented
|
59
|
+
@documentation = Render::Tag.new(true, "Undocumented")
|
60
|
+
elsif module_hidden.! and @hidden.!
|
61
|
+
if @items.none?{|e| Manager::UnitTest === e}
|
62
|
+
@items.push(Render::BulletWrapper.new("Missing test", message: "Not unit tested."))
|
63
|
+
end
|
64
|
+
if @items.none?{|e| Render::UserParagraph === e}
|
65
|
+
@items.push(Render::BulletWrapper
|
66
|
+
.new("Missing Doc", message: "No paragraph given for user."))
|
67
|
+
elsif @items.none?{|e| Render::MethodSignature === e}
|
68
|
+
case type; when :singleton, :instance
|
69
|
+
@items.push(Render::BulletWrapper
|
70
|
+
.new("Missing Doc", message: "No method signature given."))
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
singleton_class.class_eval{attr_accessor :current}
|
78
|
+
def self.context; current.context end
|
79
|
+
def self.counts; current.counts end
|
80
|
+
@config = {}
|
81
|
+
def self.config k = nil, bdir: nil, bdir_expanded: nil, odir: nil, user: nil, dev: nil, theme: nil, highlight: nil, debug: nil, spell_check: nil, case_sensitive: nil, case_insensitive: nil, spell_checker: nil, spell_check_regex: nil, timeout: nil, title: nil, coverage: nil #! `coverage` is a temporal workaround
|
82
|
+
return @config[k] if k
|
83
|
+
@config[:bdir] = bdir if bdir
|
84
|
+
@config[:bdir_expanded] = Pathname.new(bdir_expanded) if bdir_expanded
|
85
|
+
@config[:odir] = odir if odir
|
86
|
+
@config[:user] = user if user
|
87
|
+
@config[:dev] = dev if dev
|
88
|
+
@config[:theme] = File.expand_path("#{__dir__}/../theme/#{theme}") if theme
|
89
|
+
@config[:highlight] = File.expand_path("#{__dir__}/../theme/#{highlight}") if highlight
|
90
|
+
@config[:debug] = debug if debug == true or debug == false
|
91
|
+
if spell_check
|
92
|
+
spell_check = spell_check.to_s
|
93
|
+
if Spellcheck.language?(spell_check)
|
94
|
+
@config[:spell_check] = spell_check
|
95
|
+
else
|
96
|
+
raise "The configured spell checking language `#{spell_check}` is not available. "\
|
97
|
+
"Either install the corresponding aspell language component, or change "\
|
98
|
+
"the `spell_check` option."
|
99
|
+
end
|
100
|
+
end
|
101
|
+
if case_sensitive
|
102
|
+
@config[:case_sensitive] =
|
103
|
+
case_sensitive.each_with_object({}){|w, h| h[w] = true}
|
104
|
+
end
|
105
|
+
if case_insensitive
|
106
|
+
@config[:case_insensitive] =
|
107
|
+
case_insensitive.each_with_object({}){|w, h| h[w.downcase] = true}
|
108
|
+
end
|
109
|
+
@config[:spell_checker] = spell_checker if spell_checker
|
110
|
+
@config[:spell_check_regex] = spell_check_regex if spell_check_regex
|
111
|
+
@config[:timeout] = timeout if timeout
|
112
|
+
@config[:title] = title if title
|
113
|
+
@config[:coverage] = coverage
|
114
|
+
nil
|
115
|
+
end
|
116
|
+
attr_accessor :files, :implementations, :annotations, :context, :counts, :annotation_extractor
|
117
|
+
attr_reader :slf, :sample, :modul, :type, :feature
|
118
|
+
def initialize spec, **command_options
|
119
|
+
Signal.trap("INT", &InterruptionInactive)
|
120
|
+
@files, @specs, @described_headers, @implementations, @annotations, @context, @counts =
|
121
|
+
[], {}, {}, {}, {}, Context.new, Hash.new(0)
|
122
|
+
self.class.current = self
|
123
|
+
abort "Spec file not found" unless spec.kind_of?(String)
|
124
|
+
abort "Spec file not found: #{spec}" unless File.exist?(spec)
|
125
|
+
spec_s, _, data = File.read(spec).partition(/^__END__[ \t]*\n/)
|
126
|
+
l = spec_s.count($/)
|
127
|
+
@files.push([spec, {mtime: File.new(spec).mtime, lines: l}])
|
128
|
+
manage(spec, data, l) unless data.empty?
|
129
|
+
Coverage.start
|
130
|
+
ObjectSpace.trace_object_allocations_start
|
131
|
+
begin
|
132
|
+
load(spec)
|
133
|
+
rescue Exception => e
|
134
|
+
Console.abort(e)
|
135
|
+
ensure
|
136
|
+
ObjectSpace.trace_object_allocations_stop
|
137
|
+
end
|
138
|
+
#! command line options override options given in spec files
|
139
|
+
Manager.config(command_options)
|
140
|
+
bdir = File.expand_path(Manager.config(:bdir), File.dirname(spec))
|
141
|
+
Console.abort("Configured \"#{bdir}\" is not a directory") unless File.directory?(bdir)
|
142
|
+
Manager.config(bdir_expanded: bdir)
|
143
|
+
odir = File.expand_path(Manager.config(:odir), File.dirname(spec))
|
144
|
+
Console.abort("Configured \"#{odir}\" is not a directory") unless File.directory?(odir)
|
145
|
+
title = Manager.config(:title) || (@files.dig(1, 0) && File.basename(@files[1][0], ".*"))
|
146
|
+
print "Combining spec with information gathered from source..."; $stdout.flush
|
147
|
+
@implementations.each do
|
148
|
+
|(modul, type, alt), location|
|
149
|
+
@specs[modul] ||= {[:module, nil] => Spec.new.undocumented_mark}
|
150
|
+
next if type == :module
|
151
|
+
feature = Manager.main_method(alt)
|
152
|
+
begin @specs[modul][[type, feature]] ||=
|
153
|
+
case type
|
154
|
+
when :module_as_constant then Spec.new
|
155
|
+
when :instance, :singleton, :constant then Spec.new.undocumented_mark
|
156
|
+
end end
|
157
|
+
.alts[alt] = nil
|
158
|
+
end
|
159
|
+
@annotations.each do
|
160
|
+
|(modul, type, feature), h|
|
161
|
+
global = modul == Main
|
162
|
+
if feature == nil
|
163
|
+
@specs[modul] ||= {[:module, nil] => Spec.new}
|
164
|
+
search_items = @specs[modul]
|
165
|
+
.each_with_object([]){|((type, _), spec), a| a.concat(spec.items) if type == :module}
|
166
|
+
#! Global annotations have the file name for `type`.
|
167
|
+
# Local `main` annotations have `nil` for `type`.
|
168
|
+
target_items = @specs[modul][[:module, feature]].items
|
169
|
+
else
|
170
|
+
search_items =
|
171
|
+
target_items = @specs[modul][[type, feature]].items
|
172
|
+
end
|
173
|
+
h.each do
|
174
|
+
|tag, (locations, text)|
|
175
|
+
search_items.any?{|e| Render::Annotation.concat?(e, global, tag, locations, text)} or
|
176
|
+
target_items.push(Render::Annotation.new(global, tag, locations, text))
|
177
|
+
end
|
178
|
+
end
|
179
|
+
print "done\r"; $stdout.flush
|
180
|
+
print "Organizing items..."; $stdout.flush
|
181
|
+
@specs[Object] &.reject! do
|
182
|
+
|(type, _), spec| type == :module_as_constant and spec.items.empty?
|
183
|
+
end
|
184
|
+
@specs.delete(Object) if begin
|
185
|
+
@specs[Object] &.length == 1 and @specs[Object][[:module, nil]].items.empty?
|
186
|
+
end
|
187
|
+
@specs.each do
|
188
|
+
|modul, h|
|
189
|
+
unless modul == Main
|
190
|
+
#! Main only has `:module` item. `aliases` is used only with
|
191
|
+
# `:instance` and `:singleton` items.
|
192
|
+
aliases = {instance: modul.aliases, singleton: modul.singleton_class.aliases}
|
193
|
+
#! Must refer from child to ancestors, not the other way around because it is not
|
194
|
+
# guaranteed that the child is ordered later than the ancestors within `@specs`.
|
195
|
+
namespaces = modul.namespace
|
196
|
+
module_documentation = namespaces
|
197
|
+
.find{|m| break e if e = @specs.dig(m, [:module, nil]) &.documentation}
|
198
|
+
module_hidden = namespaces
|
199
|
+
.find{|m| break e if e = @specs.dig(m, [:module, nil]) &.hidden}
|
200
|
+
h[[:module, nil]].hidden = true if module_hidden
|
201
|
+
end
|
202
|
+
h.each do
|
203
|
+
|(type, feature), spec|
|
204
|
+
if spec.hidden
|
205
|
+
spec.items.each do
|
206
|
+
|e| case e; when Render::UserItem
|
207
|
+
Console.abort wrong_item(
|
208
|
+
e.source_item || e, "A hidden feature has a user document item")
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
spec.order_fix
|
213
|
+
case type
|
214
|
+
when :module
|
215
|
+
case feature
|
216
|
+
when Render::DescribedHeader
|
217
|
+
spec.header = feature
|
218
|
+
when nil
|
219
|
+
if spec.hidden
|
220
|
+
h.each do |_, spec| spec.items.each do
|
221
|
+
|e| case e; when Render::UserItem
|
222
|
+
Console.abort wrong_item(
|
223
|
+
e.source_item || e, "A hidden feature has a user document item")
|
224
|
+
end
|
225
|
+
end end
|
226
|
+
end
|
227
|
+
case modul
|
228
|
+
when Main
|
229
|
+
spec.header = Render::NilHeader
|
230
|
+
else
|
231
|
+
visibility = modul.constant_visibility(nil)
|
232
|
+
spec.header =
|
233
|
+
(spec.hidden ?
|
234
|
+
Render::DevModuleHeader : Render::UserModuleHeader)
|
235
|
+
.new(modul, spec, visibility)
|
236
|
+
spec.items.unshift(Render::AncestorDiagrams.new(modul))
|
237
|
+
end
|
238
|
+
end
|
239
|
+
when :module_as_constant
|
240
|
+
spec.type = nil
|
241
|
+
spec.alts[feature] = nil
|
242
|
+
spec.alts.each_key{|alt| spec.alts[alt] = modul.const_get(alt.to_s)}
|
243
|
+
spec.header =
|
244
|
+
(module_hidden || spec.hidden ?
|
245
|
+
Render::DevFeatureHeader : Render::UserFeatureHeader)
|
246
|
+
.new(modul, type, feature, spec)
|
247
|
+
when :constant
|
248
|
+
spec.missing?(type, module_documentation, module_hidden)
|
249
|
+
spec.alts[feature] = nil
|
250
|
+
spec.alts.each_key{|alt| spec.alts[alt] =
|
251
|
+
modul.constant_value(alt, module_hidden || spec.hidden)}
|
252
|
+
visibility = spec.alts[feature] &.visibility
|
253
|
+
location = spec.alts[feature] &.location
|
254
|
+
inherited = visibility && modul.inherited?(type, feature)
|
255
|
+
#! Unlike methods, unknown (i.e. `location == nil`) does not count as `different_file`.
|
256
|
+
# It may or may not belong to an unlisted source location, but as of now,
|
257
|
+
# there is no way to tell.
|
258
|
+
different_file = visibility && location && (@files.assoc(location &.[](0)).! || nil)
|
259
|
+
spec.documentation ||= (inherited || different_file) && module_documentation.! &&
|
260
|
+
Render::Tag.new(true, "Misplaced", message: [
|
261
|
+
(inherited && "Defined on `#{inherited.inspect}`."),
|
262
|
+
(different_file && "Not defined in a listed file."),
|
263
|
+
].join(" "))
|
264
|
+
spec.header =
|
265
|
+
(module_hidden || spec.hidden ?
|
266
|
+
Render::DevFeatureHeader : Render::UserFeatureHeader)
|
267
|
+
.new(modul, type, feature, spec)
|
268
|
+
spec.items.unshift(
|
269
|
+
spec.alts.values.first,
|
270
|
+
Render::Assignment.new(visibility, location)
|
271
|
+
)
|
272
|
+
when :instance, :singleton
|
273
|
+
spec.missing?(type, module_documentation, module_hidden)
|
274
|
+
spec.aliases = aliases.dig(type, feature)
|
275
|
+
if spec.alts.key?(feature).!
|
276
|
+
spec.alts[feature] = nil
|
277
|
+
spec.alts = spec.alts.sort.to_h
|
278
|
+
end
|
279
|
+
spec.alts.each_key{|alt| spec.alts[alt] = modul.implementation(type, alt)}
|
280
|
+
visibility = spec.alts[feature] &.visibility
|
281
|
+
location = spec.alts[feature] &.location
|
282
|
+
inherited = visibility && modul.inherited?(type, feature)
|
283
|
+
different_file = visibility && (@files.assoc(location &.[](0)).! || nil)
|
284
|
+
original_name = visibility && modul.original_name(type, feature)
|
285
|
+
aliasing = (original_name unless original_name == feature) || nil
|
286
|
+
spec.documentation ||= (inherited || different_file || aliasing) &&
|
287
|
+
module_documentation.! &&
|
288
|
+
Render::Tag.new(true, "Misplaced", message: [
|
289
|
+
(inherited && "Defined on `#{inherited.inspect}`."),
|
290
|
+
(different_file && "Not defined in a listed file."),
|
291
|
+
(aliasing && "Defined as alias of `#{aliasing}`.")
|
292
|
+
].join(" "))
|
293
|
+
spec.header =
|
294
|
+
(module_hidden || spec.hidden ?
|
295
|
+
Render::DevFeatureHeader : Render::UserFeatureHeader)
|
296
|
+
.new(modul, type, feature, spec)
|
297
|
+
spec.items.unshift(Render::Implementations.new(spec.alts.values))
|
298
|
+
end
|
299
|
+
#! The first key (the main key) can potentially have a `nil` value.
|
300
|
+
test_alts = spec.alts.any?{|_, v| v} ? spec.alts.select{|_, v| v} : spec.alts
|
301
|
+
@context.new_feature(modul, type, test_alts.keys)
|
302
|
+
#! Symbol to proc cannot be used here because `evaluate` is refinement
|
303
|
+
spec.items.map!{|e| e.evaluate}
|
304
|
+
end
|
305
|
+
end
|
306
|
+
@top = Render::Top.new
|
307
|
+
ObjectSpace.trace_object_allocations_clear
|
308
|
+
print "Processing coverage..."; $stdout.flush
|
309
|
+
@coverage = Coverage.result
|
310
|
+
if Manager.config(:coverage)
|
311
|
+
#! Drop the spec file
|
312
|
+
@files.drop(1).each do
|
313
|
+
|f, _|
|
314
|
+
puts f
|
315
|
+
l_i = @coverage[f].length.to_s.length
|
316
|
+
l_s = @coverage[f].compact.max.to_s.length
|
317
|
+
puts(@coverage[f].zip(File.readlines(f)).map.with_index(1) do
|
318
|
+
|(n, l), i|
|
319
|
+
s = (n || "-").to_s.rjust(l_s)
|
320
|
+
s =
|
321
|
+
case n
|
322
|
+
when 0 then "\e[31m#{s}\e[m"
|
323
|
+
else "\e[32m#{s}\e[m"
|
324
|
+
end
|
325
|
+
"#{i.to_s.rjust(l_i)} #{s} #{l}"
|
326
|
+
end)
|
327
|
+
end
|
328
|
+
end
|
329
|
+
print "done\r"; $stdout.flush
|
330
|
+
print "Rendering user's manual...\r"; $stdout.flush
|
331
|
+
size = File.write(user = "#{odir}/#{Manager.config(:user)}", render(mode: :user, title: title))
|
332
|
+
puts "Wrote #{user} (#{size.to_s.gsub(/(?<=\d)(?=(?:\d{3})+\z)/, ",")} bytes)."
|
333
|
+
print "Rendering developer's chart...\r"; $stdout.flush
|
334
|
+
size = File.write(dev = "#{odir}/#{Manager.config(:dev)}", render(mode: :dev, title: title))
|
335
|
+
puts "Wrote #{dev} (#{size.to_s.gsub(/(?<=\d)(?=(?:\d{3})+\z)/, ",")} bytes)."
|
336
|
+
end
|
337
|
+
def gemspec f; @gemspec = Gem::Specification.load(f) end
|
338
|
+
def manage f, data = nil, l = nil
|
339
|
+
#! The ordering between the following two lines is important. If done otherwise,
|
340
|
+
# the same file called through different paths would be loaded multiple times.
|
341
|
+
return Dir.entries(f).each{|f| manage(f)} if File.directory?(f) unless f.nil?
|
342
|
+
return if data.nil? and @files.any?{|k, _| k == f}
|
343
|
+
begin
|
344
|
+
l ||= File.read(f) &.count($/) unless f.nil?
|
345
|
+
rescue Errno::ENOENT => e
|
346
|
+
raise "#{e.message}\n"\
|
347
|
+
"Called from #{caller_locations[2].absolute_path}:#{caller_locations[2].lineno}"
|
348
|
+
end
|
349
|
+
@files.push([f, {mtime: f && File.new(f) &.mtime, lines: (data ? data.count($/) + 1 : l)}])
|
350
|
+
#!`nil` denotes that "source unknown" methods will be immune from "misplaced" error.
|
351
|
+
return if f.nil?
|
352
|
+
@annotation_extractor = AnnotationExtractor.new(f)
|
353
|
+
tracing_classes = {}
|
354
|
+
constants_stack = []
|
355
|
+
Object.module_start(constants_stack, tracing_classes)
|
356
|
+
tp_module_start = TracePoint.new(:class) do
|
357
|
+
|tp|
|
358
|
+
modul, _f, l = tp.binding.receiver, tp.path, tp.lineno
|
359
|
+
next unless _f == f
|
360
|
+
@annotation_extractor.read_upto(modul, :module, nil, [_f, l])
|
361
|
+
modul.module_start(constants_stack, tracing_classes)
|
362
|
+
end
|
363
|
+
tp_module_end = TracePoint.new(:end) do
|
364
|
+
|tp|
|
365
|
+
modul, _f, l = tp.binding.receiver, tp.path, tp.lineno
|
366
|
+
next unless _f == f
|
367
|
+
@annotation_extractor.read_upto(modul, :module_end, nil, [_f, l])
|
368
|
+
modul.module_end(constants_stack)
|
369
|
+
end
|
370
|
+
tp_module_start.enable
|
371
|
+
tp_module_end.enable
|
372
|
+
begin
|
373
|
+
#! `l` for the spec code lines, `1` for the `__END__` line, and `1` for starting at 1.
|
374
|
+
data ? TOPLEVEL_BINDING.eval(data, f, l + 1 + 1) : load(f)
|
375
|
+
rescue Exception => e
|
376
|
+
Console.abort(e)
|
377
|
+
end
|
378
|
+
tp_module_start.disable
|
379
|
+
tp_module_end.disable
|
380
|
+
Object.module_end(constants_stack)
|
381
|
+
tracing_classes.each_key do
|
382
|
+
|modul|
|
383
|
+
modul.singleton_class.__send__(:remove_method, :singleton_method_added)
|
384
|
+
modul.singleton_class.__send__(:remove_method, :method_added)
|
385
|
+
end
|
386
|
+
@annotation_extractor.close
|
387
|
+
nil
|
388
|
+
end
|
389
|
+
def hide spec
|
390
|
+
bad_spec " `hide` must be followed by `spec`." unless spec.instance_of?(Spec)
|
391
|
+
case @type
|
392
|
+
when :module
|
393
|
+
bad_spec " `hide` can only apply to a method or a constant (including module)."
|
394
|
+
when :module_as_constant
|
395
|
+
modul = @specs[@modul.const_get(@feature.to_s)][[:module, nil]]
|
396
|
+
bad_spec " Conflicting `hide` and `move`." if modul.documentation and modul.hidden.!
|
397
|
+
modul.documentation = Render::Tag.new(true, "Hidden")
|
398
|
+
modul.hidden = true
|
399
|
+
spec.hidden = true
|
400
|
+
when :instance, :singleton, :constant
|
401
|
+
bad_spec " Conflicting `hide` and `move`." if spec.documentation and spec.hidden.!
|
402
|
+
spec.documentation = Render::Tag.new(true, "Hidden")
|
403
|
+
spec.hidden = true
|
404
|
+
end
|
405
|
+
nil
|
406
|
+
end
|
407
|
+
def move spec
|
408
|
+
bad_spec " `move` must be followed by `spec`." unless spec.instance_of?(Spec)
|
409
|
+
case @type
|
410
|
+
when :module
|
411
|
+
bad_spec " `move` can only apply to a method or a constant (including module)."
|
412
|
+
when :module_as_constant
|
413
|
+
modul = @specs[@modul.const_get(@feature.to_s)][[:module, nil]]
|
414
|
+
bad_spec " Conflicting `hide` and `move`." if modul.documentation and modul.hidden
|
415
|
+
modul.documentation = Render::Tag.new(true, "Moved")
|
416
|
+
when :instance, :singleton, :constant
|
417
|
+
bad_spec " Conflicting `hide` and `move`." if spec.documentation and spec.hidden
|
418
|
+
spec.documentation = Render::Tag.new(true, "Moved")
|
419
|
+
end
|
420
|
+
nil
|
421
|
+
end
|
422
|
+
def _spec slf, type, feature, items
|
423
|
+
modul = slf == Main ? Object : slf
|
424
|
+
case type
|
425
|
+
when :module
|
426
|
+
@modul = slf
|
427
|
+
when :instance, :singleton
|
428
|
+
@modul = modul
|
429
|
+
@sample = slf
|
430
|
+
when :constant
|
431
|
+
@modul = modul
|
432
|
+
@sample = modul
|
433
|
+
if Module === (m = (modul.const_get(feature.to_s) rescue nil))
|
434
|
+
@specs[m] ||= {[:module, nil] => Spec.new}
|
435
|
+
type = :module_as_constant
|
436
|
+
end
|
437
|
+
end
|
438
|
+
h = @specs[@modul] ||= {[:module, nil] => Spec.new}
|
439
|
+
@type, @feature = type, feature
|
440
|
+
spec = h[[@type, @feature]] ||= Spec.new
|
441
|
+
#! Cannot use symbol to proc because `to_manager_object` is refined.
|
442
|
+
spec.items.concat(items.map{|item| item.to_manager_object})
|
443
|
+
#! Return value to pass to `hide` or `move`.
|
444
|
+
spec
|
445
|
+
end
|
446
|
+
module Coda; end
|
447
|
+
def self.validate_feature_call modul, feature, coda
|
448
|
+
#! When `coda` is an instance of `Manager::UnitTest`, which is a child of `BasicObject`,
|
449
|
+
# `coda ==` would not be defined. Hence this order. The `==` is not symmetric.
|
450
|
+
current.bad_spec "Missing `coda` as the last argument" unless Coda == coda
|
451
|
+
case feature
|
452
|
+
when AlternativeMethod
|
453
|
+
raise "The feature name crashes with alternative"\
|
454
|
+
" method name format reserved by manager gem, and cannot be used: #{feature}"
|
455
|
+
when /\A::(.+)/
|
456
|
+
[:constant, $1.to_sym]
|
457
|
+
when /\A\.(.+)/
|
458
|
+
[:singleton, $1.to_sym]
|
459
|
+
when /\A#(.+)/
|
460
|
+
[:instance, $1.to_sym]
|
461
|
+
when /\A=/
|
462
|
+
feature.gsub!(/\A(=+) ?/, "")
|
463
|
+
[:module, current.add_described_header(modul, $1.length, feature)]
|
464
|
+
when nil
|
465
|
+
[:module, nil]
|
466
|
+
else
|
467
|
+
raise current.wrong_item(feature, "Invalid feature name")
|
468
|
+
end
|
469
|
+
end
|
470
|
+
def self.main_method sym
|
471
|
+
AlternativeMethod.match(sym) &.captures &.join &.to_sym || sym
|
472
|
+
end
|
473
|
+
def i modul, type, feature
|
474
|
+
@specs.dig(modul, [type, feature]) &.i
|
475
|
+
end
|
476
|
+
def add_described_header modul, depth, feature
|
477
|
+
h = @described_headers[modul] ||= {}
|
478
|
+
k = depth == 1 ? [] : h.reverse_each.find do
|
479
|
+
|k, _|
|
480
|
+
if k.length == depth and k.last == feature
|
481
|
+
raise wrong_item(feature, "Described feature name crash")
|
482
|
+
end
|
483
|
+
k.length == depth - 1
|
484
|
+
end &.first
|
485
|
+
raise wrong_item(feature, "Invalid depth for described feature") unless k
|
486
|
+
h[[*k, feature]] = Render::DescribedHeader.new(modul, depth, feature)
|
487
|
+
end
|
488
|
+
def described_headers modul, names_path
|
489
|
+
@described_headers[modul]
|
490
|
+
&.select{|k, _| k.last(names_path.length) == names_path}
|
491
|
+
&.group_by{|k, _| k.length} &.min_by(&:first) &.last &.map(&:last)
|
492
|
+
end
|
493
|
+
def bad_spec message
|
494
|
+
raise +"In" <<
|
495
|
+
[
|
496
|
+
@files.first.first,
|
497
|
+
"#{caller.find{|l| l.start_with?(@files.first.first)} &.split(":") &.[](1)}",
|
498
|
+
message,
|
499
|
+
].join(":")
|
500
|
+
end
|
501
|
+
def wrong_item item, message
|
502
|
+
RuntimeError.new((+"In ") <<
|
503
|
+
[
|
504
|
+
*if Manager.config(:debug) or Manager::DebugDirs
|
505
|
+
.none?{|dir| ObjectSpace.allocation_sourcefile(item) &.start_with?(dir)}
|
506
|
+
[ObjectSpace.allocation_sourcefile(item), ObjectSpace.allocation_sourceline(item)]
|
507
|
+
else "?"
|
508
|
+
end,
|
509
|
+
message,
|
510
|
+
item.inspect,
|
511
|
+
].join(":"))
|
512
|
+
end
|
513
|
+
end
|
514
|
+
|
515
|
+
Manager.config(
|
516
|
+
bdir: "./",
|
517
|
+
bdir_expanded: __dir__,
|
518
|
+
odir: "./",
|
519
|
+
user: "MANUAL.html",
|
520
|
+
dev: "CHART.html",
|
521
|
+
theme: "2016a.css",
|
522
|
+
highlight: "coderay_github.css",
|
523
|
+
debug: false,
|
524
|
+
spell_check: nil,
|
525
|
+
case_sensitive: [],
|
526
|
+
case_insensitive: [],
|
527
|
+
)
|
528
|
+
Manager::CodeRayOption = {tab_width: 2, css: :class}
|