looksee 3.0.0-universal-java-1.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG +66 -0
- data/LICENSE +22 -0
- data/README.markdown +175 -0
- data/Rakefile +13 -0
- data/ext/extconf.rb +19 -0
- data/ext/mri/1.9.2/debug.h +36 -0
- data/ext/mri/1.9.2/id.h +170 -0
- data/ext/mri/1.9.2/method.h +103 -0
- data/ext/mri/1.9.2/node.h +483 -0
- data/ext/mri/1.9.2/thread_pthread.h +27 -0
- data/ext/mri/1.9.2/vm_core.h +707 -0
- data/ext/mri/1.9.2/vm_opts.h +51 -0
- data/ext/mri/1.9.3/atomic.h +56 -0
- data/ext/mri/1.9.3/debug.h +41 -0
- data/ext/mri/1.9.3/id.h +175 -0
- data/ext/mri/1.9.3/internal.h +227 -0
- data/ext/mri/1.9.3/internal_falcon.h +248 -0
- data/ext/mri/1.9.3/method.h +105 -0
- data/ext/mri/1.9.3/node.h +503 -0
- data/ext/mri/1.9.3/thread_pthread.h +51 -0
- data/ext/mri/1.9.3/vm_core.h +755 -0
- data/ext/mri/1.9.3/vm_opts.h +51 -0
- data/ext/mri/2.0.0/internal.h +378 -0
- data/ext/mri/2.0.0/method.h +138 -0
- data/ext/mri/2.1.0/internal.h +889 -0
- data/ext/mri/2.1.0/method.h +142 -0
- data/ext/mri/2.2.0/internal.h +1182 -0
- data/ext/mri/2.2.0/method.h +141 -0
- data/ext/mri/env-1.8.h +27 -0
- data/ext/mri/eval_c-1.8.h +27 -0
- data/ext/mri/mri.c +309 -0
- data/ext/mri/node-1.9.h +35 -0
- data/ext/rbx/rbx.c +13 -0
- data/lib/looksee.rb +2 -0
- data/lib/looksee/JRuby.jar +0 -0
- data/lib/looksee/adapter.rb +8 -0
- data/lib/looksee/adapter/base.rb +105 -0
- data/lib/looksee/adapter/rubinius.rb +84 -0
- data/lib/looksee/clean.rb +169 -0
- data/lib/looksee/columnizer.rb +73 -0
- data/lib/looksee/core_ext.rb +48 -0
- data/lib/looksee/editor.rb +64 -0
- data/lib/looksee/help.rb +54 -0
- data/lib/looksee/inspector.rb +70 -0
- data/lib/looksee/lookup_path.rb +95 -0
- data/lib/looksee/rbx.bundle +0 -0
- data/lib/looksee/version.rb +11 -0
- data/spec/looksee/adapter_spec.rb +588 -0
- data/spec/looksee/clean_spec.rb +41 -0
- data/spec/looksee/columnizer_spec.rb +52 -0
- data/spec/looksee/core_ext_spec.rb +17 -0
- data/spec/looksee/editor_spec.rb +107 -0
- data/spec/looksee/inspector_spec.rb +179 -0
- data/spec/looksee/lookup_path_spec.rb +87 -0
- data/spec/spec_helper.rb +29 -0
- data/spec/support/core_ext.rb +25 -0
- data/spec/support/temporary_classes.rb +78 -0
- data/spec/support/test_adapter.rb +83 -0
- metadata +116 -0
data/ext/mri/node-1.9.h
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
/* MRI 1.9 does not install node.h. This is the part we need. */
|
2
|
+
|
3
|
+
typedef struct RNode {
|
4
|
+
unsigned long flags;
|
5
|
+
char *nd_file;
|
6
|
+
union {
|
7
|
+
struct RNode *node;
|
8
|
+
ID id;
|
9
|
+
VALUE value;
|
10
|
+
VALUE (*cfunc)(ANYARGS);
|
11
|
+
ID *tbl;
|
12
|
+
} u1;
|
13
|
+
union {
|
14
|
+
struct RNode *node;
|
15
|
+
ID id;
|
16
|
+
long argc;
|
17
|
+
VALUE value;
|
18
|
+
} u2;
|
19
|
+
union {
|
20
|
+
struct RNode *node;
|
21
|
+
ID id;
|
22
|
+
long state;
|
23
|
+
struct global_entry *entry;
|
24
|
+
long cnt;
|
25
|
+
VALUE value;
|
26
|
+
} u3;
|
27
|
+
} NODE;
|
28
|
+
|
29
|
+
#define nd_body u2.node
|
30
|
+
#define nd_noex u3.id
|
31
|
+
|
32
|
+
#define NOEX_PUBLIC 0x00
|
33
|
+
#define NOEX_PRIVATE 0x02
|
34
|
+
#define NOEX_PROTECTED 0x04
|
35
|
+
#define NOEX_MASK 0x06
|
data/ext/rbx/rbx.c
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#include "ruby.h"
|
2
|
+
|
3
|
+
VALUE Looksee_internal_class(VALUE self, VALUE object) {
|
4
|
+
return CLASS_OF(object);
|
5
|
+
}
|
6
|
+
|
7
|
+
void Init_rbx(void) {
|
8
|
+
VALUE mLooksee = rb_const_get(rb_cObject, rb_intern("Looksee"));
|
9
|
+
VALUE mAdapter = rb_const_get(mLooksee, rb_intern("Adapter"));
|
10
|
+
VALUE mBase = rb_const_get(mAdapter, rb_intern("Base"));
|
11
|
+
VALUE mRubinius = rb_define_class_under(mAdapter, "Rubinius", mBase);
|
12
|
+
rb_define_method(mRubinius, "internal_class", Looksee_internal_class, 1);
|
13
|
+
}
|
data/lib/looksee.rb
ADDED
Binary file
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module Looksee
|
2
|
+
module Adapter
|
3
|
+
class Base
|
4
|
+
#
|
5
|
+
# Return the chain of classes and modules which comprise the
|
6
|
+
# object's method lookup path.
|
7
|
+
#
|
8
|
+
def lookup_modules(object)
|
9
|
+
modules = []
|
10
|
+
klass = internal_class(object)
|
11
|
+
while klass
|
12
|
+
modules << klass
|
13
|
+
klass = internal_superclass(klass)
|
14
|
+
end
|
15
|
+
modules
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# Return a description of the given module.
|
20
|
+
#
|
21
|
+
# This is used for the module labels in the Inspector output.
|
22
|
+
#
|
23
|
+
def describe_module(mod)
|
24
|
+
num_brackets = 0
|
25
|
+
object = mod
|
26
|
+
while singleton_class?(object)
|
27
|
+
num_brackets += 1
|
28
|
+
object = singleton_instance(object)
|
29
|
+
end
|
30
|
+
|
31
|
+
if included_class?(mod) || object.is_a?(Module)
|
32
|
+
description = module_name(object)
|
33
|
+
if description.empty?
|
34
|
+
is_class = real_module(object).is_a?(Class)
|
35
|
+
description = "unnamed #{is_class ? 'Class' : 'Module'}"
|
36
|
+
end
|
37
|
+
else
|
38
|
+
description = "#{module_name(object.class)} instance"
|
39
|
+
end
|
40
|
+
|
41
|
+
if num_brackets == 0
|
42
|
+
description
|
43
|
+
else
|
44
|
+
"#{'['*num_brackets}#{description}#{']'*num_brackets}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def real_module(module_or_included_class)
|
49
|
+
module_or_included_class
|
50
|
+
end
|
51
|
+
|
52
|
+
def internal_superclass(klass)
|
53
|
+
raise NotImplementedError, "abstract"
|
54
|
+
end
|
55
|
+
|
56
|
+
def internal_class(object)
|
57
|
+
raise NotImplementedError, "abstract"
|
58
|
+
end
|
59
|
+
|
60
|
+
def included_class?(object)
|
61
|
+
raise NotImplementedError, "abstract"
|
62
|
+
end
|
63
|
+
|
64
|
+
def internal_public_instance_methods(mod)
|
65
|
+
raise NotImplementedError, "abstract"
|
66
|
+
end
|
67
|
+
|
68
|
+
def internal_protected_instance_methods(mod)
|
69
|
+
raise NotImplementedError, "abstract"
|
70
|
+
end
|
71
|
+
|
72
|
+
def internal_private_instance_methods(mod)
|
73
|
+
raise NotImplementedError, "abstract"
|
74
|
+
end
|
75
|
+
|
76
|
+
def internal_undefined_instance_methods(mod)
|
77
|
+
raise NotImplementedError, "abstract"
|
78
|
+
end
|
79
|
+
|
80
|
+
def singleton_class?(object)
|
81
|
+
raise NotImplementedError, "abstract"
|
82
|
+
end
|
83
|
+
|
84
|
+
def singleton_instance(singleton_class)
|
85
|
+
raise NotImplementedError, "abstract"
|
86
|
+
end
|
87
|
+
|
88
|
+
def module_name(mod)
|
89
|
+
raise NotImplementedError, "abstract"
|
90
|
+
end
|
91
|
+
|
92
|
+
if RUBY_VERSION >= '1.9.0' || Looksee.ruby_engine == 'rbx'
|
93
|
+
def source_location(method)
|
94
|
+
method.is_a?(UnboundMethod) or
|
95
|
+
raise TypeError, "expected UnboundMethod, got #{method.class}"
|
96
|
+
method.source_location
|
97
|
+
end
|
98
|
+
else
|
99
|
+
def source_location(method)
|
100
|
+
raise NotImplementedError, 'abstract'
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'looksee/adapter/base'
|
2
|
+
require 'looksee/rbx'
|
3
|
+
|
4
|
+
module Looksee
|
5
|
+
module Adapter
|
6
|
+
class Rubinius < Base
|
7
|
+
def internal_superclass(klass)
|
8
|
+
klass.direct_superclass
|
9
|
+
end
|
10
|
+
|
11
|
+
def internal_public_instance_methods(mod)
|
12
|
+
return [] if !mod.origin.equal?(mod)
|
13
|
+
mod.method_table.public_names
|
14
|
+
end
|
15
|
+
|
16
|
+
def internal_protected_instance_methods(mod)
|
17
|
+
return [] if !mod.origin.equal?(mod)
|
18
|
+
mod.method_table.protected_names
|
19
|
+
end
|
20
|
+
|
21
|
+
def internal_private_instance_methods(mod)
|
22
|
+
return [] if !mod.origin.equal?(mod)
|
23
|
+
mod.method_table.private_names
|
24
|
+
end
|
25
|
+
|
26
|
+
def internal_undefined_instance_methods(mod)
|
27
|
+
return [] if !mod.origin.equal?(mod)
|
28
|
+
names = []
|
29
|
+
mod.method_table.entries.each do |(name, method, visibility)|
|
30
|
+
names << name if visibility.equal?(:undef)
|
31
|
+
end
|
32
|
+
names
|
33
|
+
end
|
34
|
+
|
35
|
+
def included_class?(object)
|
36
|
+
object.is_a?(::Rubinius::IncludedModule)
|
37
|
+
end
|
38
|
+
|
39
|
+
def singleton_class?(object)
|
40
|
+
object.is_a?(Class) && !!::Rubinius::Type.singleton_class_object(object)
|
41
|
+
end
|
42
|
+
|
43
|
+
def singleton_instance(singleton_class)
|
44
|
+
singleton_class?(singleton_class) or
|
45
|
+
raise TypeError, "expected singleton class, got #{singleton_class.class}"
|
46
|
+
::Rubinius::Type.singleton_class_object(singleton_class)
|
47
|
+
end
|
48
|
+
|
49
|
+
def module_name(mod)
|
50
|
+
mod.is_a?(Module) or
|
51
|
+
raise TypeError, "expected module, got #{mod.class}"
|
52
|
+
|
53
|
+
if ::Rubinius::IncludedModule === mod
|
54
|
+
if Class === mod.module
|
55
|
+
"#{module_name(mod.module)} (origin)"
|
56
|
+
else
|
57
|
+
module_name(mod.module)
|
58
|
+
end
|
59
|
+
elsif ::Rubinius::Type.respond_to?(:module_name)
|
60
|
+
::Rubinius::Type.module_name(mod) || ''
|
61
|
+
else
|
62
|
+
mod.__name__
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def source_location(method)
|
67
|
+
method.is_a?(UnboundMethod) or
|
68
|
+
raise TypeError, "expected UnboundMethod, got #{method.class}"
|
69
|
+
source_location = method.source_location and
|
70
|
+
return source_location
|
71
|
+
|
72
|
+
# #source_location doesn't always work. If it returns nil, try
|
73
|
+
# a little harder.
|
74
|
+
case (executable = method.executable)
|
75
|
+
when ::Rubinius::BlockEnvironment::AsMethod
|
76
|
+
method = executable.instance_variable_get(:@block_env).method
|
77
|
+
[method.file.to_s, method.lines[1]]
|
78
|
+
when ::Rubinius::DelegatedMethod
|
79
|
+
executable.instance_variable_get(:@receiver).source_location
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require "rbconfig"
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module Looksee
|
5
|
+
Config = Object.const_defined?(:RbConfig) ? ::RbConfig : ::Config
|
6
|
+
|
7
|
+
NoMethodError = Class.new(RuntimeError)
|
8
|
+
NoSourceLocationError = Class.new(RuntimeError)
|
9
|
+
NoSourceFileError = Class.new(RuntimeError)
|
10
|
+
|
11
|
+
autoload :VERSION, 'looksee/version'
|
12
|
+
autoload :Adapter, 'looksee/adapter'
|
13
|
+
autoload :Columnizer, 'looksee/columnizer'
|
14
|
+
autoload :Editor, 'looksee/editor'
|
15
|
+
autoload :Help, 'looksee/help'
|
16
|
+
autoload :Inspector, 'looksee/inspector'
|
17
|
+
autoload :LookupPath, 'looksee/lookup_path'
|
18
|
+
autoload :WirbleCompatibility, 'looksee/wirble_compatibility'
|
19
|
+
|
20
|
+
class << self
|
21
|
+
#
|
22
|
+
# The default options passed to #ls.
|
23
|
+
#
|
24
|
+
# Default: <tt>[:public, :protected, :private, :undefined,
|
25
|
+
# :overridden]</tt>
|
26
|
+
#
|
27
|
+
attr_accessor :default_specifiers
|
28
|
+
|
29
|
+
#
|
30
|
+
# The width to use for displaying output, when not available in
|
31
|
+
# the COLUMNS environment variable.
|
32
|
+
#
|
33
|
+
# Default: 80
|
34
|
+
#
|
35
|
+
attr_accessor :default_width
|
36
|
+
|
37
|
+
#
|
38
|
+
# The default styles to use for the +inspect+ strings.
|
39
|
+
#
|
40
|
+
# This is a hash with keys:
|
41
|
+
#
|
42
|
+
# * :module
|
43
|
+
# * :public
|
44
|
+
# * :protected
|
45
|
+
# * :private
|
46
|
+
# * :undefined
|
47
|
+
# * :overridden
|
48
|
+
#
|
49
|
+
# The values are format strings. They should all contain a single
|
50
|
+
# "%s", which is where the name is inserted.
|
51
|
+
#
|
52
|
+
# Default:
|
53
|
+
#
|
54
|
+
# {
|
55
|
+
# :module => "\e[1;37m%s\e[0m", # white
|
56
|
+
# :public => "\e[1;32m%s\e[0m", # green
|
57
|
+
# :protected => "\e[1;33m%s\e[0m", # yellow
|
58
|
+
# :private => "\e[1;31m%s\e[0m", # red
|
59
|
+
# :undefined => "\e[1;34m%s\e[0m", # blue
|
60
|
+
# :overridden => "\e[1;30m%s\e[0m", # black
|
61
|
+
# }
|
62
|
+
#
|
63
|
+
attr_accessor :styles
|
64
|
+
|
65
|
+
#
|
66
|
+
# The editor command, used for Object#edit.
|
67
|
+
#
|
68
|
+
# This string should contain a "%f", which is replaced with the
|
69
|
+
# file name, and/or "%l" which is replaced with the line number. A
|
70
|
+
# "%%" is replaced with "%".
|
71
|
+
#
|
72
|
+
# If the LOOKSEE_EDITOR environment variable is set, it is used as
|
73
|
+
# the default. Otherwise, we use the following heuristic:
|
74
|
+
#
|
75
|
+
# If EDITOR is set, we use that. If it looks like vi, emacs, or
|
76
|
+
# textmate, we also append options to position the cursor on the
|
77
|
+
# appropriate line. If EDITOR is not set, we use "vi +%l %f".
|
78
|
+
#
|
79
|
+
attr_accessor :editor
|
80
|
+
|
81
|
+
#
|
82
|
+
# The interpreter adapter.
|
83
|
+
#
|
84
|
+
# Encapsulates the interpreter-specific functionality.
|
85
|
+
#
|
86
|
+
attr_accessor :adapter
|
87
|
+
|
88
|
+
#
|
89
|
+
# Wrapper around RUBY_ENGINE that's always defined.
|
90
|
+
#
|
91
|
+
attr_accessor :ruby_engine
|
92
|
+
|
93
|
+
#
|
94
|
+
# Return a Looksee::Inspector for the given +object+.
|
95
|
+
#
|
96
|
+
# +args+ is an optional list of specifiers.
|
97
|
+
#
|
98
|
+
# * +:public+ - include public methods
|
99
|
+
# * +:protected+ - include public methods
|
100
|
+
# * +:private+ - include public methods
|
101
|
+
# * +:undefined+ - include public methods (see Module#undef_method)
|
102
|
+
# * +:overridden+ - include public methods
|
103
|
+
# * +:nopublic+ - include public methods
|
104
|
+
# * +:noprotected+ - include public methods
|
105
|
+
# * +:noprivate+ - include public methods
|
106
|
+
# * +:noundefined+ - include public methods (see Module#undef_method)
|
107
|
+
# * +:nooverridden+ - include public methods
|
108
|
+
# * a string - only include methods containing this string (may
|
109
|
+
# be used multiple times)
|
110
|
+
# * a regexp - only include methods matching this regexp (may
|
111
|
+
# be used multiple times)
|
112
|
+
#
|
113
|
+
# The default (if options is nil or omitted) is given by
|
114
|
+
# #default_lookup_path_options.
|
115
|
+
#
|
116
|
+
def [](object, *args)
|
117
|
+
options = {:visibilities => Set[], :filters => Set[]}
|
118
|
+
(Looksee.default_specifiers + args).each do |arg|
|
119
|
+
case arg
|
120
|
+
when String, Regexp
|
121
|
+
options[:filters] << arg
|
122
|
+
when :public, :protected, :private, :undefined, :overridden
|
123
|
+
options[:visibilities].add(arg)
|
124
|
+
when :nopublic, :noprotected, :noprivate, :noundefined, :nooverridden
|
125
|
+
visibility = arg.to_s.sub(/\Ano/, '').to_sym
|
126
|
+
options[:visibilities].delete(visibility)
|
127
|
+
else
|
128
|
+
raise ArgumentError, "invalid specifier: #{arg.inspect}"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
lookup_path = LookupPath.new(object)
|
132
|
+
Inspector.new(lookup_path, options)
|
133
|
+
end
|
134
|
+
|
135
|
+
#
|
136
|
+
# Show a quick reference.
|
137
|
+
#
|
138
|
+
def help
|
139
|
+
Help.new
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
self.default_specifiers = [:public, :protected, :private, :undefined, :overridden]
|
144
|
+
self.default_width = 80
|
145
|
+
self.styles = {
|
146
|
+
:module => "\e[1;37m%s\e[0m", # white
|
147
|
+
:public => "\e[1;32m%s\e[0m", # green
|
148
|
+
:protected => "\e[1;33m%s\e[0m", # yellow
|
149
|
+
:private => "\e[1;31m%s\e[0m", # red
|
150
|
+
:undefined => "\e[1;34m%s\e[0m", # blue
|
151
|
+
:overridden => "\e[1;30m%s\e[0m", # black
|
152
|
+
}
|
153
|
+
self.editor = ENV['LOOKSEE_EDITOR'] || ENV['EDITOR'] || 'vi'
|
154
|
+
|
155
|
+
if Object.const_defined?(:RUBY_ENGINE)
|
156
|
+
self.ruby_engine = RUBY_ENGINE
|
157
|
+
else
|
158
|
+
self.ruby_engine = 'ruby'
|
159
|
+
end
|
160
|
+
|
161
|
+
case ruby_engine
|
162
|
+
when 'jruby'
|
163
|
+
self.adapter = Adapter::JRuby.new
|
164
|
+
when 'rbx'
|
165
|
+
self.adapter = Adapter::Rubinius.new
|
166
|
+
else
|
167
|
+
self.adapter = Adapter::MRI.new
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Looksee
|
2
|
+
module Columnizer
|
3
|
+
class << self
|
4
|
+
#
|
5
|
+
# Arrange the given strings in columns, restricted to the given
|
6
|
+
# width. Smart enough to ignore content in terminal control
|
7
|
+
# sequences.
|
8
|
+
#
|
9
|
+
def columnize(strings, width)
|
10
|
+
return '' if strings.empty?
|
11
|
+
|
12
|
+
num_columns = 1
|
13
|
+
layout = [strings]
|
14
|
+
loop do
|
15
|
+
break if layout.first.length <= 1
|
16
|
+
next_layout = layout_in_columns(strings, num_columns + 1)
|
17
|
+
break if layout_width(next_layout) > width
|
18
|
+
layout = next_layout
|
19
|
+
num_columns += 1
|
20
|
+
end
|
21
|
+
|
22
|
+
pad_strings(layout)
|
23
|
+
rectangularize_layout(layout)
|
24
|
+
layout.transpose.map do |row|
|
25
|
+
' ' + row.compact.join(' ')
|
26
|
+
end.join("\n") << "\n"
|
27
|
+
end
|
28
|
+
|
29
|
+
private # -----------------------------------------------------
|
30
|
+
|
31
|
+
def layout_in_columns(strings, num_columns)
|
32
|
+
strings_per_column = (strings.length / num_columns.to_f).ceil
|
33
|
+
(0...num_columns).map{|i| strings[i*strings_per_column...(i+1)*strings_per_column] || []}
|
34
|
+
end
|
35
|
+
|
36
|
+
def layout_width(layout)
|
37
|
+
widths = layout_column_widths(layout)
|
38
|
+
widths.inject(0){|sum, w| sum + w} + 2*layout.length
|
39
|
+
end
|
40
|
+
|
41
|
+
def layout_column_widths(layout)
|
42
|
+
layout.map do |column|
|
43
|
+
column.map{|string| display_width(string)}.max || 0
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def display_width(string)
|
48
|
+
# remove terminal control sequences
|
49
|
+
string.gsub(/\e\[.*?m/, '').length
|
50
|
+
end
|
51
|
+
|
52
|
+
def pad_strings(layout)
|
53
|
+
widths = layout_column_widths(layout)
|
54
|
+
layout.each_with_index do |column, i|
|
55
|
+
column_width = widths[i]
|
56
|
+
column.each do |string|
|
57
|
+
padding = column_width - display_width(string)
|
58
|
+
string << ' '*padding
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def rectangularize_layout(layout)
|
64
|
+
return if layout.length == 1
|
65
|
+
height = layout[0].length
|
66
|
+
layout[1..-1].each do |column|
|
67
|
+
column.length == height or
|
68
|
+
column[height - 1] = nil
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|