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.
- checksums.yaml +4 -4
- data/CHART.html +1270 -0
- data/MANUAL.html +1252 -0
- data/bin/manager +43 -0
- data/examples/array/CHART.html +1376 -0
- data/examples/array/MANUAL.html +1126 -0
- data/examples/array/spec +3438 -0
- data/lib/manager.rb +528 -0
- data/lib/manager/annotation +96 -0
- data/lib/manager/input +189 -0
- data/lib/manager/js +257 -0
- data/lib/manager/refine_module +142 -0
- data/lib/manager/refine_object_mapping +143 -0
- data/lib/manager/refine_test +97 -0
- data/lib/manager/render +1228 -0
- data/lib/manager/spell_check +49 -0
- data/lib/manager/test +404 -0
- data/lib/manager/test_helper +9 -0
- data/license +9 -0
- data/manager.gemspec +21 -0
- data/spec/alternatives_implemented.png +0 -0
- data/spec/alternatives_unimplemented.png +0 -0
- data/spec/annotations.png +0 -0
- data/spec/benchmark_test.png +0 -0
- data/spec/citation.png +0 -0
- data/spec/code_block.png +0 -0
- data/spec/context_module.png +0 -0
- data/spec/documentation +1289 -0
- data/spec/external_link.png +0 -0
- data/spec/image.png +0 -0
- data/spec/list.png +0 -0
- data/spec/long.png +0 -0
- data/spec/main_and_object.png +0 -0
- data/spec/markup.png +0 -0
- data/spec/module_diagram.png +0 -0
- data/spec/navigation.png +0 -0
- data/spec/nested_section_headers.png +0 -0
- data/spec/ruby.png +0 -0
- data/spec/setup_teardown.png +0 -0
- data/spec/short.png +0 -0
- data/spec/signature.png +0 -0
- data/spec/spec +76 -0
- data/spec/spec_example.png +0 -0
- data/spec/table.png +0 -0
- data/spec/test_header.png +0 -0
- data/spec/test_non_unit_spec +184 -0
- data/spec/test_program +71 -0
- data/spec/test_unit_spec +790 -0
- data/spec/tutorial_1.png +0 -0
- data/spec/tutorial_2.png +0 -0
- data/spec/tutorial_3.png +0 -0
- data/spec/tutorial_4.png +0 -0
- data/spec/tutorial_5.png +0 -0
- data/spec/tutorial_6.png +0 -0
- data/spec/tutorial_7.png +0 -0
- data/spec/tutorial_8.png +0 -0
- data/spec/unambiguous_links.png +0 -0
- data/spec/unit_test_failure.png +0 -0
- data/spec/unit_test_raise.png +0 -0
- data/spec/unit_test_receiver.png +0 -0
- data/spec/unit_test_succeed.png +0 -0
- data/spec/unit_test_success.png +0 -0
- data/spec/unit_test_throw.png +0 -0
- data/spec/valid_heading.png +0 -0
- data/spec/with_expr.png +0 -0
- data/spec/without_expr.png +0 -0
- data/theme/2016a.css +670 -0
- data/theme/coderay_github.css +132 -0
- metadata +140 -11
@@ -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
|
data/lib/manager/render
ADDED
@@ -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(" <\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
|