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,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