manager 0.0.0 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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}
|