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