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,142 @@
1
+ #!ruby
2
+ #frozen-string-literal: true
3
+
4
+ # Copyright (c) 2016 sawa
5
+
6
+ class Manager
7
+ module ModuleRefinement
8
+ refine Module do
9
+ def aliases
10
+ instance_methods(false).concat(
11
+ private_instance_methods(false)).concat(
12
+ protected_instance_methods(false))
13
+ .group_by{|alt| instance_method(alt)}
14
+ .each_with_object({}) do
15
+ |(k, a), h|
16
+ a.delete(k.original_name)
17
+ h[k.original_name] = a unless a.empty?
18
+ end
19
+ end
20
+ def module_start constants_stack, tracing_classes
21
+ Manager.current.implementations[[self, :module, nil]] = true
22
+ tracing_classes[self] = true
23
+ constants_stack.push(constants(false))
24
+ __send__(:define_singleton_method, :singleton_method_added) do
25
+ |alt|
26
+ case alt; when :singleton_method_added, :method_added; else
27
+ alt = method(alt).original_name
28
+ loc = method(alt).real_location
29
+ # Manager.current.implementations[[self, :singleton, alt]] = loc
30
+ Manager.current.implementations[[self, :singleton, alt]] = true
31
+ Manager.current.annotation_extractor.read_upto(self, :singleton, alt, loc)
32
+ end
33
+ end
34
+ __send__(:define_singleton_method, :method_added) do
35
+ |alt|
36
+ alt = instance_method(alt).original_name
37
+ loc = instance_method(alt).real_location
38
+ # Manager.current.implementations[[self, :instance, alt]] = loc
39
+ Manager.current.implementations[[self, :instance, alt]] = true
40
+ Manager.current.annotation_extractor.read_upto(self, :instance, alt, loc)
41
+ end
42
+ end
43
+ def module_end constants_stack
44
+ (constants(false) - constants_stack.pop).each do
45
+ |c|
46
+ e = const_get(c.to_s)
47
+ if Module === e
48
+ Manager.current.implementations[[e, :module, nil]] = true
49
+ Manager.current.implementations[[self, :module_as_constant, c]] = true
50
+ else
51
+ Manager.current.implementations[[self, :constant, c]] = true
52
+ end
53
+ end
54
+ end
55
+ def constant_value feature, hide
56
+ visibility = constant_visibility(feature)
57
+ value = visibility && const_get(feature.to_s)
58
+ location = visibility && begin
59
+ f = ObjectSpace.allocation_sourcefile(value)
60
+ l = ObjectSpace.allocation_sourceline(value)
61
+ f && [File.realpath(f), l]
62
+ end
63
+ (hide ? Render::DevConstantValue : Render::UserConstantValue)
64
+ .new(visibility, location, value)
65
+ end
66
+ def implementation type, feature
67
+ case type
68
+ when :singleton
69
+ visibility = singleton_class.method_visibility(feature)
70
+ location = visibility && method(feature).real_location
71
+ when :instance
72
+ visibility = method_visibility(feature)
73
+ location = visibility && instance_method(feature).real_location
74
+ end
75
+ visibility && Render::Implementation.new(feature, visibility, location)
76
+ end
77
+ def original_name type, feature
78
+ case type
79
+ when :singleton
80
+ method(feature).original_name
81
+ when :instance
82
+ instance_method(feature).original_name
83
+ end
84
+ end
85
+ def inherited? type, feature
86
+ case type
87
+ when :constant
88
+ owner = ancestors.find{|klass| klass.const_defined?(feature, false)}
89
+ owner == self ? nil : owner
90
+ when :singleton
91
+ owner = method(feature).owner
92
+ owner == singleton_class ? nil : owner
93
+ when :instance
94
+ owner = instance_method(feature).owner
95
+ owner == self ? nil : owner
96
+ end
97
+ end
98
+ def constant_visibility feature
99
+
100
+ #! This is necessary because the `eval ... :: ...` below is ambiguous with method call.
101
+ return unless feature == nil or (const_get(feature.to_s) rescue nil)
102
+ name =
103
+ #!note1: `inspect` is not the same as `name` in case of the `main` object.
104
+ if singleton_class? then inspect.gsub("#<Class:", "").gsub(">", ".singleton_class")
105
+ else inspect
106
+ end
107
+ begin
108
+ eval(feature == nil ? "#{Object}::#{name}" : "#{name}::#{feature}")
109
+ return :public
110
+ rescue NameError => e
111
+ #! `private constant` to make sure it is not `private method`. Redundantly fail safe.
112
+ return :private if e.message.start_with?("private constant")
113
+ rescue SyntaxError => e
114
+ return :public #! Actually unknown
115
+ end
116
+ end
117
+ def method_visibility feature
118
+ return :private if private_method_defined?(feature)
119
+ return :protected if protected_method_defined?(feature)
120
+ return :public if public_method_defined?(feature)
121
+ end
122
+ def namespace
123
+ return [self] if singleton_class? #! This is the current limitation.
124
+ to_s.split("::").each_with_object([]){|s, a| a.unshift((a.first || Object).const_get(s))}
125
+ end
126
+ end
127
+
128
+ refine Method do
129
+ def real_location
130
+ f, l = source_location
131
+ f && [File.realpath(f), l]
132
+ end
133
+ end
134
+
135
+ refine UnboundMethod do
136
+ def real_location
137
+ f, l = source_location
138
+ f && [File.realpath(f), l]
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,143 @@
1
+ #!ruby
2
+ #frozen_string_literal: true
3
+
4
+ # Copyright (c) 2016 sawa
5
+
6
+ class Manager
7
+ module ObjectMappingRefinement
8
+ refine Render::UserCode do
9
+ def to_manager_object; self end
10
+ end
11
+
12
+ refine Render::DevCode do
13
+ def to_manager_object; self end
14
+ end
15
+
16
+ refine Render::UserImage do
17
+ def to_manager_object; self end
18
+ end
19
+
20
+ refine Render::DevImage do
21
+ def to_manager_object; self end
22
+ end
23
+
24
+ refine Render::TeardownClass do
25
+ def to_manager_object; self end
26
+ end
27
+
28
+ refine Setup do
29
+ def to_manager_object
30
+ @exp = ::Manager::Render::Expression.new(@exp)
31
+ self
32
+ end
33
+ end
34
+
35
+ refine Expr do
36
+ def to_manager_object; self end
37
+ end
38
+
39
+ refine BasicObject do
40
+ def UT.to_manager_object
41
+ ::Manager::UnitTest.new(nil, nil, nil).to_manager_object
42
+ end
43
+ def RETURN.to_manager_object
44
+ ::Manager::UnitTest.new(:return, nil, nil).to_manager_object
45
+ end
46
+ def RECEIVER.to_manager_object
47
+ ::Manager::UnitTest.new(:receiver, nil, nil).to_manager_object
48
+ end
49
+ def OUTPUT.to_manager_object
50
+ ::Manager::UnitTest.new(:output, nil, nil).to_manager_object
51
+ end
52
+ def BM.to_manager_object
53
+ ::Manager::Benchmark.new(nil, nil).to_manager_object
54
+ end
55
+ end
56
+
57
+ refine UnitTest do
58
+ def to_manager_object
59
+ @sample = ::Manager.current.sample if @feature_args.nil? and @referer.nil?
60
+ @exp =
61
+ if @referer
62
+ ::Manager::Render::Expression.new(@referer.upcase.to_s)
63
+ else
64
+ ::Manager::Render::Expression.new(@sample.inspect,
65
+ ::Manager.current.type, ::Manager.current.feature, @feature_args || [[], {}, nil])
66
+ end
67
+ @exp.push(@verifiers, @verifiers_args)
68
+ self
69
+ end
70
+ end
71
+
72
+ refine Benchmark do
73
+ def to_manager_object
74
+ @sample = ::Manager.current.sample if @feature_args.nil?
75
+ @exp = ::Manager::Render::Expression.new(@sample.inspect,
76
+ ::Manager.current.type, ::Manager.current.feature, @feature_args || [[], {}, nil])
77
+ self
78
+ end
79
+ end
80
+
81
+ refine String do
82
+ def to_manager_object
83
+ @modul = Manager.current.modul
84
+ gsub!(/[\r\n]\s*/, " ")
85
+ case self
86
+ when /\A(!{1,2}) ?([^\s:]*):/
87
+ global = $1.length == 2
88
+ if global and @modul != Manager::Main
89
+ raise Manager.current.wrong_item(self,
90
+ "Global annotation can only be written in the `main` context "\
91
+ "with an implicit (`nil`) or explicit (`\"=Title ...\"`) section header")
92
+ end
93
+ sub!(/\A(!{1,2}) ?([^\s:]*):/, "")
94
+ Render::Annotation.new(global, $2, [], self)
95
+ when /\A(\?+) ?/
96
+ sub!(/\A(\?+) ?/, "")
97
+ Render::TestDescription.new($1.length, self)
98
+ when /\A-+!\z/
99
+ Render::DevLine
100
+ when /\A-+\z/
101
+ Render::UserLine
102
+ when /\A([*#]+)!/
103
+ sub!(/\A([*#]+)! ?/, "")
104
+ Render::DevList.new($1, self)
105
+ when /\A([*#]+)/
106
+ sub!(/\A([*#]+) ?/, "")
107
+ Render::UserList.new($1, self)
108
+ when /\A>!/
109
+ sub!(/\A>! ?/, "")
110
+ Render::DevCite.new(self)
111
+ when /\A>/
112
+ sub!(/\A> ?/, "")
113
+ Render::UserCite.new(self)
114
+ when /\A!/
115
+ sub!(/\A! ?/, "")
116
+ Render::DevParagraph.new(self)
117
+ else
118
+ Render::UserParagraph.new(self)
119
+ end
120
+ end
121
+ end
122
+
123
+ refine Hash do
124
+ def to_manager_object; Render::MethodSignature.new(self) end
125
+ end
126
+
127
+ refine Array do
128
+ def to_manager_object
129
+ each.with_index do |row, j| row.each.with_index do |cell, k|
130
+ next if String === cell #! `kind_of?`,`instance_of?`:
131
+ raise Manager.current.wrong_item(cell, "Table cell (#{j}, #{k}) must be a string")
132
+ end end
133
+ Render::Table.new(self)
134
+ end
135
+ end
136
+
137
+ refine Object do
138
+ def to_manager_object
139
+ raise ::Manager.current.wrong_item(self, "Cannot handle item of this class")
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,97 @@
1
+ #!ruby
2
+ #frozen_string_literal: true
3
+
4
+ # Copyright (c) 2016 sawa
5
+
6
+ class Manager
7
+ class SanitizedObject < BasicObject
8
+ #! Instance methods (of any visibility) defined on `SanitizedObject` are:
9
+ # __send__,
10
+ # :singleton_method_added, :singleton_method_removed, :singleton_method_undefined,
11
+ # :method_missing, :initialize
12
+ original_verbose, $VERBOSE = $VERBOSE, nil
13
+ undef_method :==, :equal?, :!, :!=, :instance_eval, :instance_exec, :__id__
14
+ $VERBOSE = original_verbose
15
+ end
16
+
17
+ class Setup
18
+ end
19
+
20
+ class Expr < SanitizedObject
21
+ end
22
+
23
+ class UnitTest < SanitizedObject
24
+ end
25
+
26
+ class Benchmark < SanitizedObject
27
+ end
28
+
29
+ module TesterRefinement
30
+ refine Manager::Setup do
31
+ def evaluate
32
+ result, error, output = Manager.context.setup(@exp.to_s, @f, @l)
33
+ case result
34
+ when :bad_test
35
+ Manager::Render::Result.new(@exp, result, error, output, note: "test")
36
+ when :success
37
+ Manager::Render::Setup.new(@exp, output)
38
+ end
39
+ end
40
+ end
41
+
42
+ refine Manager::Expr do
43
+ def tainted?
44
+ ::RubyVM::InstructionSequence.compile(@s)
45
+ rescue ::SyntaxError
46
+ ::Kernel.throw Context::All, [:bad_test, ::SyntaxError.new($stderr.string), nil]
47
+ end
48
+ def itself
49
+ ::Manager.context.expr(@s, @location, @modifiers, @modifiers_args)
50
+ end
51
+ end
52
+
53
+ #! Allowing `BasicObject` instances to be the target of test
54
+ refine BasicObject do
55
+ def tainted?; end
56
+ def itself; self end
57
+ end
58
+
59
+ refine Manager::Benchmark do
60
+ def evaluate
61
+ if @feature_args.nil? and ::Manager.context.type == :constant
62
+ return ::Manager::Render::Result
63
+ .new(@exp, :bad_test, "Cannot benchmark a constant.", nil, note: "benchmark")
64
+ end
65
+ ::Kernel.print "(Ctrl+C to skip) Benchmarking `#@exp'...\r"
66
+ $stdout.flush
67
+ result, *a = ::Manager.context.benchmark(@sample, @feature_args)
68
+ case result
69
+ when :bad_test, :untestable, :bug
70
+ ::Manager::Render::Result.new(@exp, result, *a, note: "benchmark")
71
+ when :success
72
+ h = a.first
73
+ ::Manager::Render::Benchmark.new(@exp, h) unless h.empty?
74
+ end
75
+ end
76
+ end
77
+
78
+ refine Manager::UnitTest do
79
+ def evaluate
80
+ if a = ::Manager.context.syntax_check_all(@sample, @feature_args, *@verifiers_args)
81
+ ::Manager::Render::Result.new(@exp, *a)
82
+ elsif @verifiers.empty?
83
+ ::Manager::Render::Result.new(@exp, :bad_test, "Missing a verification.", nil)
84
+ else
85
+ case result =
86
+ ::Manager.context.unit_test(@sample, @feature_args, @referer, @verifiers, @verifiers_args)
87
+ when ::Hash
88
+ value = result.values.map(&:shift).first
89
+ ::Manager::Render::Result.new(@exp, value, nil, nil, alts: result)
90
+ else
91
+ ::Manager::Render::Result.new(@exp, *result)
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,1228 @@
1
+ #!ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Copyright (c) 2016 sawa
5
+
6
+ using (Module.new do
7
+ refine Module.singleton_class do
8
+ def join cref, modul
9
+ return Manager::Main if modul == "::"
10
+ begin
11
+ (cref == Manager::Main ? Object : cref).const_get(modul.to_s)
12
+ rescue NameError
13
+ end
14
+ end
15
+ end
16
+ refine Module do
17
+ #!note1: `inspect` is not the same as `name` in case of the `main` object.
18
+ #!note2: Singleton class of `self` might further be a singleton class, and so on.
19
+ def clean_name
20
+ s = inspect
21
+ nil while s.gsub!(/#<Class:(.+)>/, '<< \1')
22
+ s.sub!(/#<[A-Z][a-zA-Z]*:(0x[0-9a-f]+)>/) do
23
+ ObjectSpace._id2ref(Integer($1) >> 1).inspect rescue $&
24
+ end
25
+ s
26
+ end
27
+ end
28
+
29
+ refine String do
30
+ def flush_right
31
+ [self.dom(:span, class: "right"), "".dom(:div, class: "clear")].dom
32
+ end
33
+ def exc type
34
+ type ? dom(:span, class: "bold") : dom(:span, class: "verification")
35
+ end
36
+ def vrf type
37
+ type ? self : dom(:span, class: "verification")
38
+ end
39
+ def markup spell_check = true
40
+ s = (+"").dom_escaped
41
+ i = 0
42
+ scan(/
43
+ (?<!`)(?<backticks>`+)(?<inline>.+?)\k<backticks>(?!`) | #`
44
+ \*\*(?<bold>[^*]+)\*\* |
45
+ \*(?<italic>[^*]+)\* |
46
+ \{(?<link>[^{}]+)\}|
47
+ \z # This is for iterating over the last non-matching part
48
+ /x) do
49
+ inbetween = self[i...$~.begin(0)].squeeze(" ")
50
+ s.concat(inbetween.spell_check(spell_check))
51
+ i = $~.end(0)
52
+ s.concat(
53
+ $~[:inline] &.strip &.dom(:code, class: "inline") ||
54
+ $~[:bold] &.spell_check(spell_check) &.dom(:span, class: "bold") ||
55
+ $~[:italic] &.spell_check(spell_check) &.dom(:span, class: "italic") ||
56
+ #! String with `@modul` assigned come directly from spec file, and are the ones that
57
+ # may include a link.
58
+ $~[:link] && (@modul ? $~[:link].link(@modul) : $&.dom) ||
59
+ ""
60
+ )
61
+ end
62
+ s
63
+ end
64
+ def spell_check bool
65
+ return self.dom unless bool and Manager.config(:spell_checker)
66
+ s = (+"").dom_escaped
67
+ i = 0
68
+ scan(Regexp.union(Manager.config(:spell_check_regex), /\z/)) do
69
+ inbetween = self[i...$~.begin(0)]
70
+ s.concat(inbetween.dom)
71
+ i = $~.end(0)
72
+ s.concat(
73
+ if Manager.config(:spell_checker).correct?($&) or
74
+ Manager.config(:case_sensitive)[$&] or
75
+ Manager.config(:case_insensitive)[$&.downcase]
76
+ $&.dom
77
+ else
78
+ Manager::Render.bad_doc($&)
79
+ end
80
+ )
81
+ end
82
+ s
83
+ end
84
+ def link orig_modul
85
+ case self
86
+ #! external link
87
+ when %r[\A(?:(?<text>[^=]+),\s*)?(?<href>[a-z]+://.+)]
88
+ ($~[:text] || $~[:href]).dom(:a, href: $~[:href])
89
+ #! internal link
90
+ when /\A(?:(?:(?<singleton><<)?(?<slf>(?:\w|::)+))?(?<type>#|\.|::|=))?(?<feature>.+)/
91
+ #! This part of code corresponds to `Manager#_spec`.
92
+ singleton, slf, type, feature = $~[:singleton], $~[:slf], $~[:type], $~[:feature]
93
+ slf = slf ? Module.join(orig_modul, slf) : orig_modul
94
+ modul =
95
+ if singleton then slf.singleton_class
96
+ elsif slf == Manager::Main then Object
97
+ else slf
98
+ end
99
+ i =
100
+ case type
101
+ when "="
102
+ headers = Manager.current.described_headers(slf, feature.split("="))
103
+ if headers.nil? then nil
104
+ elsif headers[1] then true
105
+ else
106
+ s = "#{"(#{slf}) " unless slf == orig_modul}"\
107
+ "#{headers.first.i}. #{headers.first.source_item}"
108
+ Manager.current.i(slf, :module, headers.first)
109
+ end
110
+ when "#"
111
+ Manager.current.i(modul, :instance, feature.to_sym)
112
+ when "."
113
+ Manager.current.i(modul, :singleton, feature.to_sym)
114
+ when "::", nil
115
+ Manager.current.i(modul, :constant, feature.to_sym) ||
116
+ Manager.current.i(Module.join(modul, feature), :module, nil)
117
+ end
118
+ s ||= self.dom(:code, class: "inline")
119
+ case i
120
+ when Integer then s.dom(:a, href: "#feature#{i}")
121
+ when nil then Manager::Render.bad_doc("[Missing link target: #{self}]")
122
+ when true then Manager::Render.bad_doc("[Ambiguous link target: #{self}]")
123
+ end
124
+ else
125
+ Manager::Render.bad_doc("[Invalid link format: #{self}]")
126
+ end
127
+ end
128
+ def block_code language
129
+ CodeRay.scan(self, language).div(Manager::CodeRayOption).dom_escaped
130
+ end
131
+ def minify_css
132
+ #! Just a simple oneliner. Not seriously optimizing to the last bit.
133
+ gsub(/(?<=\W)\s+/, "").gsub(/\s+/, " ")
134
+ end
135
+ def message_format; self end
136
+ def bt; end
137
+ end
138
+
139
+ refine Exception do
140
+ def message_format
141
+ "#{self.class}. #{message.sub(/./, &:upcase).sub(/(?<=[^.])\z/, ".").tr("'", "`")}" #'
142
+ end
143
+ def bt
144
+ #! Ruby bug. `backtrace_locations` is sometimes `nil`.
145
+ if a = backtrace_locations
146
+ fs, ls, ms = a.map(&:absolute_path), a.map(&:lineno), a.map(&:base_label)
147
+ elsif a = backtrace
148
+ fs, ls, ms =
149
+ a.map{|s| s.match(/\A([^:]+):(\d+):in `([^']+)'\z/).captures}.transpose
150
+ else
151
+ return
152
+ end
153
+ Manager::Render::Backtrace.new([fs, ls, ms.unshift("")[0...-1]].transpose)
154
+ end
155
+ end
156
+
157
+ refine NameError do
158
+ def bt
159
+ #! Ruby bug. `backtrace_locations` is sometimes `nil`.
160
+ if a = backtrace_locations
161
+ fs, ls, ms = a.map(&:absolute_path), a.map(&:lineno), a.map(&:base_label)
162
+ elsif a = backtrace
163
+ fs, ls, ms =
164
+ a.map{|s| s.match(/\A([^:]+):(\d+):in `([^']+)'\z/).captures}.transpose
165
+ else
166
+ return
167
+ end
168
+ Manager::Render::Backtrace.new([fs, ls, ms.unshift(name.to_s)[0...-1]].transpose)
169
+ end
170
+ end
171
+
172
+ refine SyntaxError do
173
+ def message_format
174
+ message, code, i = message().chomp.split($/)
175
+ [
176
+ message.split(":", 3).last.strip.sub(/./, &:upcase).tr("'", "`"),
177
+ ": ",
178
+ if code and i
179
+ i = i.length - 2
180
+ [code[0..i].dom(:span, class: "underline"), code[(i + 1)..-1]]
181
+ .dom(:code, class: "inline")
182
+ end,
183
+ ".",
184
+ ].dom
185
+ end
186
+ end
187
+
188
+ refine Array do
189
+ def select_items mode; mode == :user ? reject(&:dev?) : self end
190
+ def list depth, mode = nil
191
+ chunk{|e| e.bullet(depth)}.flat_map do
192
+ |bullet, items|
193
+ case bullet
194
+ when "*" then items.items(depth).dom(:ul)
195
+ when "#" then items.items(depth).dom(:ol)
196
+ #! Cannot use `nil` here due to a peculiarity of `chunk`.
197
+ when true then items.map{|e| e.render(mode)}
198
+ end
199
+ end
200
+ end
201
+ def items depth
202
+ slice_before{|e| e.bullet(depth + 1) == true}
203
+ .map{|items| items.list(depth + 1).dom(:li)}
204
+ end
205
+ end
206
+
207
+ refine Proc do
208
+ def inspect
209
+ f, l = source_location
210
+ "{(#{Manager::Render.relative_path(f)}:#{l})}"
211
+ end
212
+ end
213
+ end)
214
+
215
+ class Manager
216
+ MouseFocus = {onmouseover: "this.focus()", onmouseout: "this.blur()", tabindex: "0"}
217
+ def render mode:, title:
218
+ title_mode =
219
+ case mode
220
+ when :dev then "Developer's Chart"
221
+ when :user then "User's Manual"
222
+ end
223
+ #! "<!DOCTYPE html>" +
224
+ [
225
+ [
226
+ dom(:meta, charset: "UTF-8"),
227
+ File.read(Manager.config(:theme)).minify_css.dom(:style),
228
+ File.read(Manager.config(:highlight)).minify_css.dom(:style),
229
+ [*title, title_mode].join(" ").dom(:title),
230
+ ].dom(:head),
231
+ [
232
+ (left(title) if mode == :dev),
233
+ #! Render `main` before `@top` to reflect error counts (bad links and spelling)
234
+ # added during rendering of `main` .
235
+ [main(mode), @top.render(mode, title)].reverse
236
+ .dom(:div, id: "right"),
237
+ File.read(File.expand_path("#{__dir__}/js")).dom(:script),
238
+ ].dom(:body, id: mode)
239
+ ].dom(:html)
240
+ end
241
+ protected def left title
242
+ latest = @files.map{|_, h| h[:mtime]}.compact.max
243
+ [
244
+ [title && "“#{title}” ", "Developer's Chart"].dom(:span, id: "top-title"),
245
+ if @gemspec; [
246
+ "Gem Spec".dom(:span, class: "head").dom(:div, class: "p"),
247
+ [
248
+ Array.new(2).dom(:col, :colgroup),
249
+ @gemspec.instance_variables.map do
250
+ |k|
251
+ v = @gemspec.instance_variable_get(k)
252
+ v = case v; when Array; v.join(", "); else v.to_s end
253
+ [k.to_s.delete("@").gsub("_", " "), v]
254
+ end.dom(:td, :tr, :tbody),
255
+ ].dom(:table, id: "gemspec", **MouseFocus).dom(:div, class: "tablewrapper"),
256
+ ].dom end,
257
+ "Base Directory".dom(:span, class: "head").dom(:div, class: "p"),
258
+ Render.relative_path.dom(:code),
259
+ "Files".dom(:span, class: "head").dom(:div, class: "p"),
260
+ [
261
+ Array.new(5).dom(:col, :colgroup),
262
+ (
263
+ # [%w[Listed Category Name Version Lines]] +
264
+ [%w[Category Name Version Lines]] +
265
+ [
266
+ [
267
+ # "",
268
+ "Depended",
269
+ "#{RUBY_ENGINE}".dom(:code),
270
+ "#{RUBY_VERSION}p#{RUBY_PATCHLEVEL}",
271
+ "",
272
+ ],
273
+ *Gem.loaded_specs
274
+ .map{|_, g| ["Depended", g.name.dom(:code), g.version.to_s, ""]},
275
+ # .map{|_, g| ["", "Depended", g.name.dom(:code), g.version.to_s, ""]},
276
+ *@files.map.with_index do
277
+ |(f, h), i|
278
+ [
279
+ # i.zero? ? "" : checkbox("segment", "file_#{i}"),
280
+ i.zero? ? "Specification" : (f ? coverage(f) : "Unknown source"),
281
+ f && Render.relative_path(f).dom(:code, style: "word-break:break-all;"),
282
+ h[:mtime]&.strftime("%Y-%m-%d %H:%M")
283
+ &.dom(:div, class: ("bold" if h[:mtime] == latest)),
284
+ "#{h[:lines]}",
285
+ ]
286
+ end
287
+ ]
288
+ ).dom(:td, :tr, :tbody),
289
+ ].dom(:table, id: "files").dom(:div, class: "tablewrapper"),
290
+ ].dom(:div, id: "left")
291
+ end
292
+ protected def coverage f
293
+ a = @coverage[f]
294
+ total, missed = a.compact.length, a.count(0)
295
+ [
296
+ "%0.2f%% Tested".%(100 * (total - missed).to_f/total)
297
+ .dom(:span, "data-file": f, "data-coverage": a.join(","), onclick: "coverage(this)"),
298
+ #! Cannot use `display:none` for style as that would avoid the file from being loaded.
299
+ # dom(:object, data: f, type: "text/plain", style: "height:0;width:0;"),
300
+ ].dom
301
+ end
302
+ protected def main mode
303
+ spell_check = Manager.config(:spell_check)
304
+ if mode == :dev and spell_check
305
+ Manager.config(spell_checker: Manager::Spellcheck.new(spell_check))
306
+ Manager.config(spell_check_regex: Manager::Spellcheck.regex(spell_check))
307
+ end
308
+ Manager.counts[:bad_doc] = 0
309
+ @specs.map do
310
+ |modul, h|
311
+ next if mode == :user and h[[:module, nil]].hidden
312
+ h.map do
313
+ |(type, feature), spec|
314
+ next if mode == :user and spec.header.dev?
315
+ [
316
+ spec.header.render(mode, spec.i),
317
+ (items = spec.items.compact.chunk(&:dev?).map do
318
+ |dev, items|
319
+ (mode == :dev || dev.!) &&
320
+ items.list(0, mode).dom(:div, class: (dev ? "dev-item" : "user-item")) || nil
321
+ end).dom(:div, class: "feature-contents")
322
+ ].dom(:div, class: "feature",
323
+ onclick: (spec.header.onclick if items.any? or spec.header.is_a?(Render::ModuleHeader)))
324
+ end.dom(:div, class: "module")
325
+ end.dom(:div, id: "main", **MouseFocus)
326
+ ensure
327
+ Manager.config(:spell_checker) &.close
328
+ Manager.config(spell_checker: nil)
329
+ end
330
+ protected def checkbox name, value
331
+ dom(:input, type: "checkbox", name: name, value: value, checked: "")
332
+ end
333
+ end
334
+
335
+ class Manager::Console
336
+ #! Since this message may be displayed after the last `Reading <file>:<line>...` message,
337
+ # the order is backtrace from first called to last called, then message.
338
+ def self.abort(e)
339
+ e = RunTimeError.new(e) unless e.kind_of?(Exception)
340
+ Kernel.abort "#$/#{e.bt}#$/#{e.message}"
341
+ end
342
+ end
343
+
344
+ class Manager::Render
345
+ def self.relative_path path = nil
346
+ return Manager.config(:bdir_expanded).to_s unless path
347
+ [
348
+ Pathname.new(path).relative_path_from(Manager.config(:bdir_expanded)).to_s ,
349
+ path,
350
+ ].min_by(&:length)
351
+ end
352
+ def self.bad_doc s
353
+ i_dev = Manager.counts[:bad_doc] += 1
354
+ s.dom(:span, class: "tag bad_doc")
355
+ .dom(:span, class: "anchor", id: "bad_doc#{i_dev}")
356
+ end
357
+
358
+ class UserItem
359
+ def bullet depth; true end
360
+ def header?; false end
361
+ def dev?; false end
362
+ def evaluate; self end
363
+ attr_reader :source_item
364
+ end
365
+
366
+ class DevItem
367
+ def bullet depth; true end
368
+ def header?; false end
369
+ def dev?; true end
370
+ def evaluate; self end
371
+ end
372
+
373
+ class UserHeaderDummy
374
+ def header?; true end
375
+ def dev?; false end
376
+ def evaluate; self end
377
+ end
378
+
379
+ class DevHeaderDummy
380
+ def header?; true end
381
+ def dev?; true end
382
+ def evaluate; self end
383
+ end
384
+
385
+ module ModuleHeader
386
+ def initialize modul, spec, visibility
387
+ @modul, @spec = modul, spec
388
+ @visibility = Tag.new(dev?, visibility.to_s)
389
+ end
390
+ def render mode, i
391
+ [
392
+ @visibility.render(mode), "\u2002",
393
+ @modul.class.name.downcase,
394
+ " ",
395
+ @modul.clean_name.dom(:span, class: "feature-name"),
396
+ ((mode != :user) || nil) && (@spec.documentation || nil) &&
397
+ ["\u2002", @spec.documentation.render(mode)],
398
+ ].dom(:h1, class: "module-header", id: (i && "feature#{i}"))
399
+ end
400
+ def onclick; "toggleModuleContents(this)" end
401
+ end
402
+
403
+ class UserModuleHeader < UserHeaderDummy
404
+ include ModuleHeader
405
+ end
406
+
407
+ class DevModuleHeader < DevHeaderDummy
408
+ include ModuleHeader
409
+ end
410
+
411
+ module FeatureHeader
412
+ def initialize modul, type, feature, spec
413
+ @modul, @type, @feature, @spec = modul, type, feature, spec
414
+ @visibility =
415
+ case @type
416
+ when :module_as_constant
417
+ ModuleAsConstantTag.new(spec.alts[feature].class.name.downcase)
418
+ else
419
+ Tag.new(dev?, spec.alts[feature] &.visibility &.to_s || "Unimplemented")
420
+ end
421
+ end
422
+ def render mode, i
423
+ [
424
+ @visibility && [@visibility.render(mode), "\u2002"],
425
+ @spec.type && [@spec.type.render(mode), "\u2002"],
426
+ @modul.clean_name,
427
+ case @type
428
+ when :constant, :module_as_constant then "::"
429
+ when :singleton then "."
430
+ when :instance then "#"
431
+ end,
432
+ case @type
433
+ when :module_as_constant
434
+ @feature.to_s.dom(:span, class: "feature-name")
435
+ .dom(:a, href: "#feature#{Manager.current.i(@spec.alts[@feature], :module, nil)}")
436
+ else
437
+ @feature.to_s.dom(:span, class: "feature-name")
438
+ end,
439
+ ((mode != :user) || nil) && (@spec.documentation || nil) &&
440
+ ["\u2002", @spec.documentation.render(mode)],
441
+ @spec.aliases && [
442
+ "\u2002(alias: ",
443
+ "#{@spec.aliases.map{|sy| sy.to_s.dom(:span, class: "feature-name")}.join(", ")})"
444
+ .dom_escaped,
445
+ ],
446
+ ].dom(:h2, id: (i && "feature#{i}"))
447
+ end
448
+ def onclick
449
+ case @type
450
+ when :module_as_constant then nil
451
+ else "toggleFeatureContents(this)"
452
+ end
453
+ end
454
+ end
455
+
456
+ class UserFeatureHeader < UserHeaderDummy
457
+ include FeatureHeader
458
+ end
459
+
460
+ class DevFeatureHeader < DevHeaderDummy
461
+ include FeatureHeader
462
+ end
463
+
464
+ class AncestorDiagrams < DevItem
465
+ def initialize modul
466
+ @diagrams =
467
+ [
468
+ AncestorDiagram.new(modul),
469
+ AncestorDiagram.new(modul.singleton_class),
470
+ ]
471
+ end
472
+ def render mode; @diagrams.map(&:render).dom end
473
+ end
474
+
475
+ class AncestorDiagram
476
+ @@mixins = {}
477
+ def initialize modul; @modul = modul end
478
+ def render
479
+ @modul.ancestors.grep(Class).tap{|a| a.unshift(@modul) if a.empty?}
480
+ .map do
481
+ |modul|
482
+ [
483
+ mixin(modul)[:included].reverse
484
+ .flat_map{|e| [link(e).dom(:div), "\u250a".dom(:div)]},
485
+ link(modul).dom(:div, class: "baseline"),
486
+ mixin(modul)[:prepended].reverse
487
+ .flat_map{|e| ["\u250a".dom(:div), link(e).dom(:div)]},
488
+ ].dom(:div, class: "mixins")
489
+ end
490
+ .join(" &lt;\u00a0").dom_escaped.dom(:div, class: "diagram")
491
+ end
492
+ protected def mixin modul
493
+ @@mixins[modul] ||=
494
+ begin
495
+ mixin = modul.ancestors
496
+ if modul.is_a?(Class) and modul.superclass
497
+ mixin = mixin[0...-modul.superclass.ancestors.length]
498
+ end
499
+ i = mixin.index(modul)
500
+ {prepended: mixin[0...i], included: sanitize(mixin)[i + 1..-1]}
501
+ end
502
+ end
503
+ protected def sanitize a
504
+ a - [Manager::MethodSignatureItem]
505
+ end
506
+ protected def link modul
507
+ s = modul.clean_name
508
+ if modul != @modul and modul.singleton_class != @modul and
509
+ i = Manager.current.i(modul, :module, nil)
510
+ s.dom(:a, href: "#feature#{i}")
511
+ else
512
+ s
513
+ end
514
+ end
515
+ end
516
+
517
+ class Implementations < DevItem
518
+ def initialize implementations
519
+ @implementations = implementations.compact
520
+ @main_visibility = implementations.first &.visibility
521
+ end
522
+ def render mode
523
+ if mode == :dev and @implementations.empty?.!
524
+ [
525
+ "Implementation candidates".dom(:span, class: "head").dom(:div, class: "p"),
526
+ @implementations.map{|e| e.render(mode, @main_visibility)}.dom(:ul),
527
+ ].dom(:div, class: "p")
528
+ end
529
+ end
530
+ end
531
+
532
+ module ConstantValue
533
+ attr_reader :visibility, :location
534
+ def initialize visibility, location, value
535
+ @visibility, @location, @value = visibility, location, value
536
+ end
537
+ def render mode
538
+ if @visibility
539
+ [
540
+ "Value".dom(:span, class: "head"),
541
+ "\u2002",
542
+ @value.inspect.dom(:code, class: "inline"),
543
+ ].dom(:div, class: "p")
544
+ end
545
+ end
546
+ end
547
+
548
+ class UserConstantValue < UserItem
549
+ include ConstantValue
550
+ end
551
+
552
+ class DevConstantValue < DevItem
553
+ include ConstantValue
554
+ end
555
+
556
+ class Assignment < DevItem
557
+ def initialize visibility, location
558
+ @visibility, @location = visibility, location
559
+ end
560
+ def render mode
561
+ if mode == :dev and @visibility
562
+ [
563
+ "Implementation".dom(:span, class: "head").dom(:div, class: "p"),
564
+ @location &&
565
+ ["\u2002(", Manager::Render.relative_path(@location.join(":")), ")\u2002"]
566
+ .dom(:code, :span, class: "dev-text").flush_right
567
+ ].dom(:div, class: "p indent")
568
+ end
569
+ end
570
+ end
571
+
572
+ class Implementation
573
+ attr_reader :visibility, :location
574
+ def initialize alt, visibility, location
575
+ @alt = alt.to_s
576
+ @visibility = visibility
577
+ @location = location
578
+ end
579
+ def render mode, main_visibility
580
+ [
581
+ @alt.dom(:code, class: "inline"),
582
+ (@visibility != main_visibility || nil) && ["\u2002", "(#@visibility)"],
583
+ @location &&
584
+ ["\u2002(", Manager::Render.relative_path(@location.join(":")), ")\u2002"]
585
+ .dom(:code, :span, class: "dev-text").flush_right,
586
+ ].dom(:li, class: "implementation")
587
+ end
588
+ end
589
+
590
+ class NilHeaderClass < UserHeaderDummy
591
+ def render mode, i; "".dom(:div) end
592
+ def onclick; end
593
+ end
594
+ NilHeader = NilHeaderClass.new
595
+
596
+ class DescribedHeader < UserHeaderDummy
597
+ attr_reader :i, :source_item
598
+ def initialize modul, depth, source_item
599
+ @depth, @source_item = [depth, 5].min, source_item
600
+ Manager.counts[[:header, modul, depth]] += 1
601
+ (depth + 1..Manager.counts[[:header_depth, modul]])
602
+ .each{|n| Manager.counts.delete([:header, modul, n])}
603
+ Manager.counts[[:header_depth, modul]] = depth
604
+ @i = (1..depth).map{|n| Manager.counts[[:header, modul, n]]}.join(".")
605
+ end
606
+ def render mode, i
607
+ "#@i. #@source_item".markup.dom("h#{@depth + 1}", id: (i && "feature#{i}"))
608
+ end
609
+ def onclick; "toggleFeatureContents(this)" end
610
+ end
611
+
612
+ class TestDescription < DevItem
613
+ def initialize depth, source_item
614
+ @source_item = source_item
615
+ Manager.counts[[:test, depth]] += 1
616
+ (depth + 1..Manager.counts[:test_depth]).each{|n| Manager.counts.delete([:test, n])}
617
+ Manager.counts[:test_depth] = depth
618
+ @i = (1..depth).map{|n| Manager.counts[[:test, n]]}.join(".")
619
+ end
620
+ def render mode
621
+ ["Test #@i.".dom(:span, class: "head"), "\u2002", @source_item.markup(false)]
622
+ .dom(:div, class: "p")
623
+ end
624
+ end
625
+
626
+ module Line
627
+ def render mode; dom(:hr) end
628
+ def source_item; "-" end
629
+ end
630
+
631
+ class UserLineClass < UserItem
632
+ include Line
633
+ end
634
+ UserLine = UserLineClass.new
635
+
636
+ class DevLineClass < DevItem
637
+ include Line
638
+ end
639
+ DevLine = DevLineClass.new
640
+
641
+ class MethodSignature < UserItem
642
+ def initialize h; @h = h end
643
+ def render mode
644
+ @h.map.with_index do
645
+ #! "Space before `#{k}` is to avoid illicit parsing in case `k` is empty.
646
+ |(k, v), i|
647
+ ["Usage".dom(:span, class: "head", style: ("visibility:hidden;" unless i.zero?)),
648
+ "\u2002` #{k}` → #{v}".markup(false)]
649
+ end.dom
650
+ end
651
+ end
652
+
653
+ module Paragraph
654
+ def initialize source_item; @source_item = source_item end
655
+ def render mode; @source_item.markup.dom(:div, class: "p") end
656
+ end
657
+
658
+ class UserParagraph < UserItem
659
+ include Paragraph
660
+ end
661
+
662
+ class DevParagraph < DevItem
663
+ include Paragraph
664
+ end
665
+
666
+ class Table < UserItem
667
+ def initialize a; @table = a end
668
+ def render mode
669
+ @table.map{|row| row.map{
670
+ |cell|
671
+ text_align =
672
+ case cell
673
+ when /\A\s.*\s\z/ then :center
674
+ when /\A\s/ then :right
675
+ when /\s\z/ then :left
676
+ end
677
+ if text_align #! Ruby bug
678
+ cell.strip.squeeze(" \t\r\n").dom(:td, style: "text-align:%s;" % text_align)
679
+ else
680
+ cell.strip.squeeze(" \t\r\n").dom(:td)
681
+ end
682
+ }}.dom(:tr, :table)
683
+ end
684
+ end
685
+
686
+ module List
687
+ def initialize depth, source_item; @depth, @source_item = depth, source_item end
688
+ def bullet depth; @depth[depth] || true end
689
+ def render mode; @source_item.markup end
690
+ end
691
+
692
+ class UserList < UserItem
693
+ include List
694
+ end
695
+
696
+ class DevList < DevItem
697
+ include List
698
+ end
699
+
700
+ module Cite
701
+ def initialize source_item; @source_item = source_item end
702
+ def render mode; @source_item.markup.dom(:blockquote) end
703
+ end
704
+
705
+ class UserCite < UserItem
706
+ include Cite
707
+ end
708
+
709
+ class DevCite < DevItem
710
+ include Cite
711
+ end
712
+
713
+ module Code
714
+ def initialize source_item, lang; @source_item, @lang = source_item, lang end
715
+ def render mode; @source_item.block_code(@lang) end
716
+ end
717
+
718
+ class UserCode < UserItem
719
+ include Code
720
+ end
721
+
722
+ class DevCode < DevItem
723
+ include Code
724
+ end
725
+
726
+ module Image
727
+ def initialize source_item, path, style = nil
728
+ @source_item, @path, @style = source_item, path, style
729
+ @i_dev = Manager.counts[[:dev, :image]] += 1
730
+ @i_user = Manager.counts[[:user, :image]] += 1 unless dev?
731
+ end
732
+ def render mode
733
+ style = @style &.map{|a| a.join(":") << ";"}&.join
734
+ i = case mode
735
+ when :dev then @i_dev
736
+ when :user then @i_user
737
+ end
738
+ [
739
+ dom(:img, alt: @source_item, src: @path, style: style),
740
+ "Figure #{i}. #@source_item".markup.dom(:figcaption),
741
+ ].dom(:figure)
742
+ end
743
+ end
744
+
745
+ class UserImage < UserItem
746
+ include Image
747
+ end
748
+
749
+ class DevImage < DevItem
750
+ include Image
751
+ end
752
+
753
+ class Setup < DevItem
754
+ def initialize exp, output
755
+ @exp = exp
756
+ @output = output && StandardOutput.new(output)
757
+ end
758
+ def render mode
759
+ [
760
+ "Setup code".dom(:span, class: "head").dom(:div, class: "p"),
761
+ @exp.render.block_code(:ruby),
762
+ *@output &.render
763
+ ].dom
764
+ end
765
+ end
766
+
767
+ class TeardownClass < DevItem
768
+ def evaluate
769
+ Manager.context.teardown
770
+ self
771
+ end
772
+ def render mode
773
+ "Teardown".dom(:span, class: "head").dom(:div, class: "p")
774
+ end
775
+ end
776
+ Teardown = TeardownClass.new
777
+
778
+ class Annotation < DevItem
779
+ Joiner = " "
780
+ def initialize global, tag, locations, source_item
781
+ @global, @tag, @locations = global, tag, locations
782
+ @source_item =
783
+ case @tag
784
+ when String, Integer then source_item
785
+ when :debug then "Debugging command"
786
+ when :comment then "Comment line"
787
+ end
788
+ end
789
+ def concat? global, tag, locations, source_item
790
+ return false unless global == @global and tag == @tag
791
+ @locations.concat(locations)
792
+ @source_item.concat(Joiner)
793
+ @source_item.concat(source_item)
794
+ true
795
+ end
796
+ def self.concat? e, global, tag, locations, source_item
797
+ self === e and e.concat?(global, tag, locations, source_item)
798
+ end
799
+ def evaluate
800
+ @annotation =
801
+ (@tag.is_a?(Symbol) || @locations.any?{|_, text, _| text}) ? "agenda" : "log"
802
+ @bullet = Bullet.new(nil, @annotation, message: @source_item)
803
+ self
804
+ end
805
+ def render mode
806
+ [
807
+ @bullet.render.dom(:span, class: "#@annotation text"),
808
+ [
809
+ "\u2002(",
810
+ [*format_locations, *(@tag.markup(false) if @tag.is_a?(String))]
811
+ .join("; ").dom_escaped,
812
+ ")\u2002",
813
+ ].dom(:code).flush_right,
814
+ ].dom(:div, class: "p indent anchor", id: @bullet.id)
815
+ end
816
+ protected def format_locations
817
+ @locations.chunk{|f, _, _| f}.map{|f, a|
818
+ "#{Manager::Render.relative_path(f)}:#{
819
+ a.chunk_while{|(_, text1, l1), (_, text2, l2)| text1 == text2 and l1.succ == l2}
820
+ .map{|a|
821
+ if a.length >= 3 then
822
+ s = a.values_at(0, -1).map{|_, _, l| l}.join("\u2013").dom_escaped
823
+ a.first[1] ? s.dom(:span, class: "bold") : s
824
+ else
825
+ a.first[1] ? a.map{|_, _, l| l.to_s.dom(:span, class: "bold")} : a.map{|_, _, l| l}
826
+ .join(", ").dom_escaped
827
+ end
828
+ }.join(", ").dom_escaped
829
+ }"
830
+ }.join(", ").dom_escaped unless @locations.empty?
831
+ end
832
+ end
833
+
834
+ class Result < DevItem
835
+ def initialize exp, value, error, output, note: nil, alts: nil
836
+ @exp, @value, @bullet = exp, value, Bullet.new(nil, value.to_s)
837
+ @error, @output, @note, @alts = error, output, note, alts
838
+ end
839
+ def render mode
840
+ [
841
+ [
842
+ @bullet.render,
843
+ @exp && ["\u2002", @exp.render.dom(:code, class: "inline")],
844
+ *("\u2003(#@note)" if @note),
845
+ ].dom(:div, class: "indent dev-text #@value p"),
846
+ *(@output || @error) && [
847
+ *@output && StandardOutput.new(@output).render,
848
+ *@error &.message_format &.markup(false) &.dom(:span, class: "dev-text #@value"),
849
+ *@error &.bt &.render,
850
+ ].dom(:div, class: "p indent"),
851
+ *@alts&.map do
852
+ |alt, (error, output)|
853
+ [
854
+ alt.to_s.dom(:code, class: "inline"),
855
+ ":\u2002",
856
+ *output && StandardOutput.new(output) &.render,
857
+ *error &.message_format &.markup(false) &.dom(:span, class: "dev-text #@value"),
858
+ *error &.bt &.render,
859
+ ]
860
+ end &.dom(:li, :ul),
861
+ ].dom(:div, class: "anchor", id: @bullet.id)
862
+ end
863
+ end
864
+
865
+ class Benchmark < DevItem
866
+ def initialize exp, results
867
+ @exp, @results = exp, results
868
+ @norm = results[results.keys.first][:ips]
869
+ @bullet = Bullet.new(nil, "benchmark")
870
+ end
871
+ def render mode
872
+ [
873
+ [
874
+ @bullet.render,
875
+ "\u2002", @exp.render.dom(:code, class: "inline"),
876
+ "\u2003(benchmark)",
877
+ ].dom(:div, class: "p indent benchmark"),
878
+ [
879
+ "Iterations per second (parabolic scale)".dom(:caption, class: "benchmark"),
880
+ Array.new(2).dom(:col, :colgroup),
881
+ @results.map do
882
+ |method, h|
883
+ ave = h[:ips]
884
+ err = h[:ips] * 0.01 * h[:sd]
885
+ [
886
+ method.to_s.dom(:code, class: "inline"),
887
+ [
888
+ "%<ips>.2e ± %<sd>.1f%%".%(h)
889
+ .dom(:div, class: "fill", **style(scale(ave - err))),
890
+ "".dom(:div, class: "minedge"),
891
+ "".dom(:div, class: "min", **style(scale(ave) - scale(ave - err))),
892
+ "".dom(:div, class: "max", **style(scale(ave + err) - scale(ave))),
893
+ "".dom(:div, class: "maxedge"),
894
+ ].dom(:div, class: "barchart"),
895
+ ]
896
+ end.dom(:td, :tr, :tbody)
897
+ ].dom(:table, class: "barcharts"),
898
+ ].dom(:div, class: "anchor", id: @bullet.id)
899
+ end
900
+ protected def scale v
901
+ 1 - 1 / ((v / @norm) + 1)
902
+ end
903
+ protected def style v
904
+ {style: "width:%s%%;" % (v * 100.0).round(2)}
905
+ end
906
+ end
907
+
908
+ class Top
909
+ def initialize
910
+ @a = [
911
+ UserChartTag.new("displayMode(true, true)", "Modules", klass: "mode"),
912
+ UserChartTag.new("displayMode(false, true)", "Features", klass: "mode"),
913
+ UserChartTag.new("displayMode(false, false)", "Full", klass: "mode"),
914
+ DevChartTag.new("toggleUserItems(this)", "User Items", klass: "mode"),
915
+ UserNavigation.new(
916
+ "user-feature-navigation", "toggleFeatures(this, '%s', '%s')",
917
+ ["Unimplemented", "protected", "private", "public"],
918
+ modal: true,
919
+ ),
920
+ UserNavigation.new(
921
+ "user-feature-navigation", "toggleFeatures(this, '%s', '%s')",
922
+ ["Constant", "Singleton method", "Instance method"],
923
+ ["constant", "singleton", "instance"],
924
+ modal: true,
925
+ ),
926
+ DevNavigation.new(
927
+ "dev-feature-navigation", "toggleFeatures(this, '%s', '%s')",
928
+ ["Undocumented", "Hidden", "Misplaced", "Moved"],
929
+ modal: true,
930
+ ),
931
+ DevNavigation.new(
932
+ "anchor-navigation", "toggleAnchors(this, '%s', '%s')",
933
+ ["Missing doc", "Bad doc", "Agenda", "Log"],
934
+ ),
935
+ DevNavigation.new(
936
+ "anchor-navigation", "toggleAnchors(this, '%s', '%s')",
937
+ ["Missing test", "Bad test", "Untestable", "Bug", "Success", "Benchmark"]
938
+ ),
939
+ ]
940
+ end
941
+ def render mode, title
942
+ [
943
+ [
944
+ @a[0..2].map{|e| e.render(mode)}.dom(:span, class: "navigation"),
945
+ *@a[3..3].select_items(mode).map{|e| e.render(mode)}.tap{|a| break if a.empty?},
946
+ @a[4..10].select_items(mode).map{|e| e.render(mode)}.join("\u2002").dom_escaped,
947
+ ].join("\u2502".dom(:span, class: "separator")).dom_escaped.dom(:div, id: "top"),
948
+ ([title && "“#{title}” ", "User's Manual"].dom(:div, id: "top-title") if mode == :user),
949
+ ].dom
950
+ end
951
+ end
952
+
953
+ class Expression
954
+ def initialize sample, type = nil, feature = nil, feature_args = nil
955
+ @sample, @type, @a = sample, type, []
956
+ if feature
957
+ args, kargs, pr = feature_args
958
+ @a.push(
959
+ [
960
+ feature,
961
+ args.map(&:inspect) + kargs.map{|k, e| "#{k}: #{e.inspect}"},
962
+ pr &.inspect,
963
+ ]
964
+ )
965
+ end
966
+ end
967
+ def push verifiers, verifiers_args
968
+ @a.concat(verifiers.zip(verifiers_args).map do
969
+ |verifier, (args, kargs, pr)|
970
+ [
971
+ verifier,
972
+ args.map(&:inspect) + kargs.map{|k, e| "#{k}: #{e.inspect}"},
973
+ pr &.inspect,
974
+ ]
975
+ end)
976
+ self
977
+ end
978
+ def render
979
+ s = @sample
980
+ s = _render(s, *@a.first, @a.length > 1, @type) unless @a.empty?
981
+ s = @a[1...-1].inject(s){|s, e| _render(s, *e, true)} unless @a.empty?
982
+ s = _render(s, *@a.last, false) if @a.length > 1
983
+ s
984
+ end
985
+ def to_s
986
+ s = @sample
987
+ s = _to_s(s, *@a.first, @a.length > 1, @type) unless @a.empty?
988
+ s = @a[1...-1].inject(s){|s, e| _to_s(s, *e, true)} unless @a.empty?
989
+ s = _to_s(s, *@a.last, false) if @a.length > 1
990
+ s
991
+ end
992
+ def _render sample, feature, suite, pr, emb, type = nil
993
+ if type == :constant
994
+ sample = sample == "Object" ? "" : "#{sample}::"
995
+ return [sample, feature.to_s.exc(type)].dom
996
+ end
997
+ case feature
998
+ when :==, :!=, :=~, :!~, :<, :>, :<=, :>=, :<=>, :===,
999
+ :**, :*, :/, :+, :-, :&, :|, :^, :<<, :>>
1000
+ infix = true
1001
+ a =
1002
+ [sample, " ", feature.to_s.exc(type), " #{suite.join(", ")}#{pr}".vrf(type)]
1003
+ when :[]
1004
+ infix = false
1005
+ a =
1006
+ [sample, "[".exc(type), suite.join(", ").to_s.vrf(type), "]".exc(type), "#{pr}".vrf(type)]
1007
+ when :[]=
1008
+ infix = true
1009
+ a =
1010
+ [sample, "[".exc(type), "#{suite[0..-2].join(", ")}".vrf(type), "] = ".exc(type),
1011
+ "#{suite[-1]}#{pr}".vrf(type)]
1012
+ when /(?<attribute>.+)=/
1013
+ infix = true
1014
+ a =
1015
+ [sample, ".".vrf(type), "#{$~[:attribute]} = ".exc(type),
1016
+ "#{suite.join(", ")}#{pr}".vrf(type)]
1017
+ when /(?<prefix>.+)@/
1018
+ infix = false
1019
+ a =
1020
+ ["#{$~[:prefix]}".exc(type), sample, "#{suite.join(", ")}#{pr}".vrf(type)]
1021
+ else
1022
+ infix = false
1023
+ suite = suite.join(", ")
1024
+ suite = "(#{suite})" unless suite.empty?
1025
+ receiver = [sample, ".".vrf(type)].dom unless sample == "main"
1026
+ a = [*receiver, feature.to_s.exc(type), "#{suite}#{pr}".vrf(type)]
1027
+ end
1028
+ (infix && emb ? ["(", *a, ")"] : a).dom
1029
+ end
1030
+ def _to_s sample, feature, suite, pr, emb, type = nil
1031
+ if type == :constant
1032
+ sample = sample == "Object" ? "" : "#{sample}::"
1033
+ return "#{sample}#{feature}"
1034
+ end
1035
+ case feature
1036
+ when :==, :!=, :=~, :!~, :<, :>, :<=, :>=, :<=>, :===,
1037
+ :**, :*, :/, :+, :-, :&, :|, :^, :<<, :>>
1038
+ infix = true
1039
+ s = "#{sample} #{feature} #{suite.join(", ")}#{pr}"
1040
+ when :[]
1041
+ infix = false
1042
+ s = "#{sample}[#{suite.join(", ")}]#{pr}"
1043
+ when :[]=
1044
+ infix = true
1045
+ s = "#{sample}[#{suite[0..-2].join(", ")}] = #{suite[-1]}#{pr}"
1046
+ when /(?<attribute>.+)=/
1047
+ infix = true
1048
+ s = "#{sample}.#{$~[:attribute]} = #{suite.join(", ")}#{pr}"
1049
+ when /(?<prefix>.+)@/
1050
+ infix = false
1051
+ s = "#{$~[:prefix]}#{sample}#{suite.join(", ")}#{pr}"
1052
+ else
1053
+ infix = false
1054
+ suite = suite.join(", ")
1055
+ suite = "(#{suite})" unless suite.empty?
1056
+ feature = "#{sample}.#{feature}" unless sample == "main"
1057
+ s = "#{feature}#{suite}#{pr}"
1058
+ end
1059
+ infix && emb ? "(#{s})" : s
1060
+ end
1061
+ end
1062
+
1063
+ class StandardOutput
1064
+ def initialize s; @s = s end
1065
+ def render
1066
+ [
1067
+ "Standard output".dom(:span, class: "head").dom(:div, class: "p"),
1068
+ @s.ansi2html.dom(:pre).dom(:div, class: "code").dom(:div, class: "CodeRay")
1069
+ ].dom
1070
+ end
1071
+ end
1072
+
1073
+ class Backtrace
1074
+ def initialize a
1075
+ @a = a
1076
+ unless Manager.config(:debug)
1077
+ @a = @a.reject{|f, *| Manager::DebugDirs.any?{|dir| f &.start_with?(dir)}}
1078
+ end
1079
+ end
1080
+ def render
1081
+ return if @a.empty?
1082
+ [
1083
+ Array.new(2).dom(:col, :colgroup),
1084
+ @a.map do
1085
+ |f, l, method|
1086
+ listed = f && Manager.current.files.assoc(f).!.!
1087
+ [
1088
+ [Manager::Render.relative_path(f), l].join(":")
1089
+ .dom(:code, class: listed ? nil : "gray"),
1090
+ method.dom(:code, class: listed ? "inline" : "inline gray"),
1091
+ ]
1092
+ end.dom(:td, :tr, :tbody)
1093
+ ].dom(:table, class: "backtrace")
1094
+ end
1095
+ def to_s
1096
+ return "" if @a.empty?
1097
+ @a.map do
1098
+ |f, l, method| [Manager::Render.relative_path(f), l, "`#{method}`"].join(":")
1099
+ end.reverse.join($/)
1100
+ end
1101
+ end
1102
+
1103
+ class Bullet
1104
+ def initialize label, value = label.tr(" ", "_").downcase, message: nil
1105
+ @label, @value = label, value
1106
+ @i_dev = Manager.counts[@value.to_sym] += 1
1107
+ @message = message &.markup(false)
1108
+ end
1109
+ def render
1110
+ [
1111
+ "\u200b".dom(:span, class: "hang-indent"),
1112
+ (@label || @i_dev.to_s).dom(:span, class: "tag #@value"),
1113
+ *@message && ["\u2002", @message].dom(:span, class: "dev-text #@value"),
1114
+ ].dom
1115
+ end
1116
+ def id; "#@value#@i_dev" end
1117
+ end
1118
+
1119
+ class Tag < DevItem
1120
+ def initialize dev, label, value = label.tr(" ", "_").downcase, message: nil
1121
+ @label , @value = label, value
1122
+ @i_dev = Manager.counts[[:dev, @value.to_sym]] += 1
1123
+ @i_user = Manager.counts[[:user, @value.to_sym]] += 1 unless dev
1124
+ @message = message &.markup(false)
1125
+ end
1126
+ def render mode
1127
+ i = case mode
1128
+ when :dev then @i_dev
1129
+ when :user then @i_user
1130
+ end
1131
+ [
1132
+ (@label || i.to_s).dom(:span, class: "tag #@value"),
1133
+ *@message && ["\u2002", @message].dom(:span, class: "dev-text #@value"),
1134
+ ].dom(:span, class: "anchor", id: "#@value#{i}")
1135
+ end
1136
+ end
1137
+
1138
+ class ModuleAsConstantTag < DevItem
1139
+ def initialize type; @type = type end
1140
+ def render mode; "(#@type)" end
1141
+ end
1142
+
1143
+ class BulletWrapper < DevItem
1144
+ def initialize label, value = label.tr(" ", "_").downcase, message: nil
1145
+ @bullet = Bullet.new(label, value, message: message &.markup(false))
1146
+ end
1147
+ def render mode
1148
+ @bullet.render.dom(:div, class: "p indent anchor", id: @bullet.id)
1149
+ end
1150
+ end
1151
+
1152
+ module ChartTag
1153
+ def initialize onclick, label, value = nil, klass: nil, modal: false
1154
+ @onclick = onclick
1155
+ @label = label
1156
+ @value = value
1157
+ @klass = klass || value
1158
+ @modal = modal
1159
+ end
1160
+ def count mode
1161
+ return unless @value
1162
+ count = Manager.counts[@modal ? [mode, @value.to_sym] : @value.to_sym]
1163
+ count unless count.zero?
1164
+ end
1165
+ def render mode
1166
+ [
1167
+ @label.dom(:span, class: "tag #@klass", onclick: @onclick),
1168
+ *if @value
1169
+ return unless count = count(mode)
1170
+ ["\u2002", count.to_s.dom(:span, class: @value, id: "tagsum-#@value"), "\u2002"]
1171
+ end,
1172
+ ].dom
1173
+ end
1174
+ end
1175
+
1176
+ class UserChartTag < UserItem
1177
+ include ChartTag
1178
+ end
1179
+
1180
+ class DevChartTag < DevItem
1181
+ include ChartTag
1182
+ end
1183
+
1184
+ module Navigation
1185
+ def initialize level, onclick, names, classes = names.map{|s| s.tr(" ", "_").downcase},
1186
+ modal: false
1187
+ @onclick, @names, @classes = onclick, names, classes
1188
+ @id = "#{level}#{Manager.counts[level] += 1}"
1189
+ @modal = modal
1190
+ end
1191
+ def render mode
1192
+ chart_klass = case self
1193
+ when UserItem then UserChartTag
1194
+ when DevItem then DevChartTag
1195
+ end
1196
+ tags = @names.zip(@classes).map do
1197
+ |name, klass|
1198
+ chart_klass.new(@onclick % [klass, @id], name, klass, modal: @modal).render(mode)
1199
+ end
1200
+ [
1201
+ *tags,
1202
+ [
1203
+ "\u25c2"
1204
+ .dom(:span, class: "tag arrow", onclick: "navigateTag(this.parentNode, -1)"),
1205
+ "\u25b8"
1206
+ .dom(:span, class: "tag arrow", onclick: "navigateTag(this.parentNode, 1)"),
1207
+ "\u2002",
1208
+ [
1209
+ "1".dom(:span, class: "current"),
1210
+ "/",
1211
+ "".dom(:span, class: "sum"),
1212
+ "+",
1213
+ "".dom(:span, class: "excluded"),
1214
+ ].dom(:span, class: "statistics"),
1215
+ "\u2002",
1216
+ ].dom(:span, id: @id, "data-tags": @classes.join(" ")),
1217
+ ].dom(:span, class: "navigation", style: ("display: none;" unless tags.any?))
1218
+ end
1219
+ end
1220
+
1221
+ class UserNavigation < UserItem
1222
+ include Navigation
1223
+ end
1224
+
1225
+ class DevNavigation < DevItem
1226
+ include Navigation
1227
+ end
1228
+ end