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.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHART.html +1270 -0
  3. data/MANUAL.html +1252 -0
  4. data/bin/manager +43 -0
  5. data/examples/array/CHART.html +1376 -0
  6. data/examples/array/MANUAL.html +1126 -0
  7. data/examples/array/spec +3438 -0
  8. data/lib/manager.rb +528 -0
  9. data/lib/manager/annotation +96 -0
  10. data/lib/manager/input +189 -0
  11. data/lib/manager/js +257 -0
  12. data/lib/manager/refine_module +142 -0
  13. data/lib/manager/refine_object_mapping +143 -0
  14. data/lib/manager/refine_test +97 -0
  15. data/lib/manager/render +1228 -0
  16. data/lib/manager/spell_check +49 -0
  17. data/lib/manager/test +404 -0
  18. data/lib/manager/test_helper +9 -0
  19. data/license +9 -0
  20. data/manager.gemspec +21 -0
  21. data/spec/alternatives_implemented.png +0 -0
  22. data/spec/alternatives_unimplemented.png +0 -0
  23. data/spec/annotations.png +0 -0
  24. data/spec/benchmark_test.png +0 -0
  25. data/spec/citation.png +0 -0
  26. data/spec/code_block.png +0 -0
  27. data/spec/context_module.png +0 -0
  28. data/spec/documentation +1289 -0
  29. data/spec/external_link.png +0 -0
  30. data/spec/image.png +0 -0
  31. data/spec/list.png +0 -0
  32. data/spec/long.png +0 -0
  33. data/spec/main_and_object.png +0 -0
  34. data/spec/markup.png +0 -0
  35. data/spec/module_diagram.png +0 -0
  36. data/spec/navigation.png +0 -0
  37. data/spec/nested_section_headers.png +0 -0
  38. data/spec/ruby.png +0 -0
  39. data/spec/setup_teardown.png +0 -0
  40. data/spec/short.png +0 -0
  41. data/spec/signature.png +0 -0
  42. data/spec/spec +76 -0
  43. data/spec/spec_example.png +0 -0
  44. data/spec/table.png +0 -0
  45. data/spec/test_header.png +0 -0
  46. data/spec/test_non_unit_spec +184 -0
  47. data/spec/test_program +71 -0
  48. data/spec/test_unit_spec +790 -0
  49. data/spec/tutorial_1.png +0 -0
  50. data/spec/tutorial_2.png +0 -0
  51. data/spec/tutorial_3.png +0 -0
  52. data/spec/tutorial_4.png +0 -0
  53. data/spec/tutorial_5.png +0 -0
  54. data/spec/tutorial_6.png +0 -0
  55. data/spec/tutorial_7.png +0 -0
  56. data/spec/tutorial_8.png +0 -0
  57. data/spec/unambiguous_links.png +0 -0
  58. data/spec/unit_test_failure.png +0 -0
  59. data/spec/unit_test_raise.png +0 -0
  60. data/spec/unit_test_receiver.png +0 -0
  61. data/spec/unit_test_succeed.png +0 -0
  62. data/spec/unit_test_success.png +0 -0
  63. data/spec/unit_test_throw.png +0 -0
  64. data/spec/valid_heading.png +0 -0
  65. data/spec/with_expr.png +0 -0
  66. data/spec/without_expr.png +0 -0
  67. data/theme/2016a.css +670 -0
  68. data/theme/coderay_github.css +132 -0
  69. metadata +140 -11
@@ -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}