manager 0.0.0 → 0.1.0

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