tk_inspect 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 +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +67 -0
- data/LICENSE.txt +21 -0
- data/README.md +43 -0
- data/Rakefile +6 -0
- data/bin/console +13 -0
- data/bin/setup +8 -0
- data/bin/tk_console +14 -0
- data/lib/base_inspectors.rb +65 -0
- data/lib/cli_support.rb +9 -0
- data/lib/tk_inspect.rb +12 -0
- data/lib/tk_inspect/canvas_window.rb +2 -0
- data/lib/tk_inspect/canvas_window/base.rb +39 -0
- data/lib/tk_inspect/canvas_window/root_component.rb +17 -0
- data/lib/tk_inspect/class_browser.rb +6 -0
- data/lib/tk_inspect/class_browser/base.rb +80 -0
- data/lib/tk_inspect/class_browser/class_namespace_data_source.rb +55 -0
- data/lib/tk_inspect/class_browser/class_tree_data_source.rb +57 -0
- data/lib/tk_inspect/class_browser/code_component.rb +75 -0
- data/lib/tk_inspect/class_browser/module_method_data_source.rb +86 -0
- data/lib/tk_inspect/class_browser/root_component.rb +88 -0
- data/lib/tk_inspect/console.rb +2 -0
- data/lib/tk_inspect/console/base.rb +118 -0
- data/lib/tk_inspect/console/root_component.rb +104 -0
- data/lib/tk_inspect/inspector.rb +2 -0
- data/lib/tk_inspect/inspector/base.rb +68 -0
- data/lib/tk_inspect/inspector/root_component.rb +65 -0
- data/lib/tk_inspect/version.rb +3 -0
- data/tk_inspect.gemspec +46 -0
- metadata +154 -0
@@ -0,0 +1,6 @@
|
|
1
|
+
require_relative './class_browser/base'
|
2
|
+
require_relative './class_browser/root_component'
|
3
|
+
require_relative './class_browser/code_component'
|
4
|
+
require_relative './class_browser/class_tree_data_source'
|
5
|
+
require_relative './class_browser/class_namespace_data_source'
|
6
|
+
require_relative './class_browser/module_method_data_source'
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module TkInspect
|
2
|
+
module ClassBrowser
|
3
|
+
class Base
|
4
|
+
attr_accessor :tk_root
|
5
|
+
attr_accessor :main_component
|
6
|
+
attr_accessor :selected_class_path
|
7
|
+
attr_accessor :selected_module
|
8
|
+
attr_accessor :selected_method
|
9
|
+
attr_accessor :browsing_method
|
10
|
+
attr_accessor :class_data_sources
|
11
|
+
attr_accessor :module_method_data_source
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@tk_root = nil
|
15
|
+
@main_component = nil
|
16
|
+
@selected_class_path = []
|
17
|
+
@loaded_code = {}
|
18
|
+
@class_filter = nil
|
19
|
+
@browsing_method = 'hierarchy'
|
20
|
+
@class_data_sources = {
|
21
|
+
hierarchy: ClassTreeDataSource.new,
|
22
|
+
namespaces: ClassNamespaceDataSource.new
|
23
|
+
}.with_indifferent_access
|
24
|
+
@module_method_data_source = ModuleMethodDataSource.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def refresh
|
28
|
+
@main_component.nil? ? create_root : @main_component.regenerate
|
29
|
+
end
|
30
|
+
|
31
|
+
def create_root
|
32
|
+
@tk_root = TkComponent::Window.new(title: "Class Browser")
|
33
|
+
@main_component = RootComponent.new
|
34
|
+
@main_component.class_browser = self
|
35
|
+
@tk_root.place_root_component(@main_component)
|
36
|
+
end
|
37
|
+
|
38
|
+
def class_data_source
|
39
|
+
@class_data_sources[browsing_method]
|
40
|
+
end
|
41
|
+
|
42
|
+
def select_class_name(class_name)
|
43
|
+
self.browsing_method = 'hierarchy'
|
44
|
+
class_path = class_data_source.path_for_class(class_name)
|
45
|
+
select_class_path(class_path)
|
46
|
+
end
|
47
|
+
|
48
|
+
def show_current_path
|
49
|
+
@main_component.show_current_selection
|
50
|
+
end
|
51
|
+
|
52
|
+
def select_class_path(class_path)
|
53
|
+
self.selected_class_path = class_path
|
54
|
+
module_method_data_source.selected_class = selected_class_name
|
55
|
+
end
|
56
|
+
|
57
|
+
def selected_class_name
|
58
|
+
browsing_method.to_s == 'hierarchy' ? self.selected_class_path.last : self.selected_class_path.join('::')
|
59
|
+
end
|
60
|
+
|
61
|
+
def code_for_method(meth)
|
62
|
+
return nil unless meth.present?
|
63
|
+
class_name = selected_class_name
|
64
|
+
return nil unless class_name.present?
|
65
|
+
klass = Object.const_get(class_name)
|
66
|
+
method = klass.instance_method(meth)
|
67
|
+
file, line = method.source_location
|
68
|
+
return nil unless file && line
|
69
|
+
return code_for_file(file), line, file
|
70
|
+
end
|
71
|
+
|
72
|
+
def code_for_file(file)
|
73
|
+
@loaded_code[file] || begin
|
74
|
+
@loaded_code[file] = IO.read(file)
|
75
|
+
@loaded_code[file]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module TkInspect
|
2
|
+
module ClassBrowser
|
3
|
+
class ClassNamespaceDataSource
|
4
|
+
attr_accessor :class_filter
|
5
|
+
|
6
|
+
def items_for_path(path)
|
7
|
+
path = [] if path.blank?
|
8
|
+
namespace = path.join('::')
|
9
|
+
classes = classes_in_namespace(namespace)
|
10
|
+
class_names = classes
|
11
|
+
.map { |k| name_for_class(k).gsub(/^#{namespace}(::)?/, '').gsub(/::.*/, '') }
|
12
|
+
.reject { |class_name| class_name.blank? }
|
13
|
+
.uniq
|
14
|
+
.sort
|
15
|
+
class_names
|
16
|
+
end
|
17
|
+
|
18
|
+
def title_for_path(path, items)
|
19
|
+
"#{items.count} #{'class'.pluralize(items.count)}"
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def classes_in_namespace(namespace)
|
25
|
+
filtered_classes.select do |k|
|
26
|
+
name_for_class(k).match(/^#{namespace}/)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def filtered_classes
|
31
|
+
list = ObjectSpace.each_object(Class).select do |k|
|
32
|
+
k.is_a?(Class) && !black_list?(k)
|
33
|
+
end
|
34
|
+
return list if @class_filter.blank?
|
35
|
+
list.select! do |k|
|
36
|
+
name_for_class(k).match(/#{@class_filter}/i)
|
37
|
+
end
|
38
|
+
ancestors = list.reduce(Set.new) do |acum, k|
|
39
|
+
acum += name_for_class(k).split('::')
|
40
|
+
acum
|
41
|
+
end
|
42
|
+
(list.to_set + ancestors).to_a
|
43
|
+
end
|
44
|
+
|
45
|
+
def name_for_class(klass)
|
46
|
+
klass.to_s
|
47
|
+
end
|
48
|
+
|
49
|
+
def black_list?(k)
|
50
|
+
k.to_s.blank? || k.to_s.match(/^#<Class/) || !k.to_s.match(/^[A-Z]/)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module TkInspect
|
2
|
+
module ClassBrowser
|
3
|
+
class ClassTreeDataSource
|
4
|
+
attr_accessor :class_filter
|
5
|
+
|
6
|
+
def items_for_path(path)
|
7
|
+
path = [] if path.blank?
|
8
|
+
parent_class = path.last
|
9
|
+
subclasses_of(parent_class).sort
|
10
|
+
end
|
11
|
+
|
12
|
+
def title_for_path(path, items)
|
13
|
+
"#{items.count} #{'class'.pluralize(items.count)}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def path_for_class(class_name)
|
17
|
+
return unless (klass = Object.const_get(class_name))
|
18
|
+
path = [class_name]
|
19
|
+
while (klass = klass.superclass) do
|
20
|
+
path.unshift(name_for_class(klass))
|
21
|
+
end
|
22
|
+
path
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def subclasses_of(class_name)
|
28
|
+
filtered_classes.select do |k|
|
29
|
+
k.superclass&.to_s == class_name
|
30
|
+
end.map { |k| name_for_class(k) }
|
31
|
+
end
|
32
|
+
|
33
|
+
def filtered_classes
|
34
|
+
list = ObjectSpace.each_object(Class).select do |k|
|
35
|
+
!k.name.nil?
|
36
|
+
end
|
37
|
+
return list if @class_filter.blank?
|
38
|
+
list.select! do |k|
|
39
|
+
k.name.match(/#{@class_filter}/i)
|
40
|
+
end
|
41
|
+
ancestors = list.reduce(Set.new) do |acum, k|
|
42
|
+
an = k
|
43
|
+
while !(an = an.superclass).nil? do
|
44
|
+
acum << an
|
45
|
+
end
|
46
|
+
acum
|
47
|
+
end
|
48
|
+
(list.to_set + ancestors).to_a
|
49
|
+
end
|
50
|
+
|
51
|
+
def name_for_class(klass)
|
52
|
+
klass.respond_to?(:name) ? klass.name : klass.to_s
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'active_support/all'
|
2
|
+
require 'rouge'
|
3
|
+
|
4
|
+
module TkInspect
|
5
|
+
module ClassBrowser
|
6
|
+
TOKEN_TAGS = {
|
7
|
+
'Rouge::Token::Tokens::Comment::Single' => :comment,
|
8
|
+
'Rouge::Token::Tokens::Keyword' => :keyword,
|
9
|
+
'Rouge::Token::Tokens::Name::Class' => :constant,
|
10
|
+
'Rouge::Token::Tokens::Name::Constant' => :constant,
|
11
|
+
'Rouge::Token::Tokens::Name::Function' => :method,
|
12
|
+
'Rouge::Token::Tokens::Str::Double' => :string,
|
13
|
+
'Rouge::Token::Tokens::Str::Single' => :string,
|
14
|
+
'Rouge::Token::Tokens::Str::Interpol' => :string,
|
15
|
+
'Rouge::Token::Tokens::Str::Symbol' => :symbol,
|
16
|
+
'Rouge::Token::Tokens::Str::Heredoc' => :string,
|
17
|
+
'Rouge::Token::Tokens::Name::Variable::Instance' => :ivar
|
18
|
+
}
|
19
|
+
|
20
|
+
TAG_STYLES = {
|
21
|
+
string: { foreground: 'red' },
|
22
|
+
comment: { foreground: 'gray' },
|
23
|
+
keyword: { foreground: 'blue' },
|
24
|
+
constant: { foreground: 'green' },
|
25
|
+
symbol: { foreground: 'purple' },
|
26
|
+
method: { foreground: 'brown' },
|
27
|
+
ivar: { foreground: 'brown' }
|
28
|
+
}
|
29
|
+
|
30
|
+
class CodeComponent < TkComponent::Base
|
31
|
+
attr_accessor :code
|
32
|
+
attr_accessor :filename
|
33
|
+
attr_accessor :method_name
|
34
|
+
attr_accessor :method_line
|
35
|
+
|
36
|
+
def generate(parent_component, options = {})
|
37
|
+
parse_component(parent_component, options) do |p|
|
38
|
+
p.vframe(padding: "0 0 0 0", sticky: 'nsew', x_flex: 1, y_flex: 1) do |vf|
|
39
|
+
@filename_label = vf.label(font: 'TkSmallCaptionFont', sticky: 'ewn', x_flex: 1, y_flex: 0)
|
40
|
+
@code_text = vf.text(sticky: 'nswe', x_flex: 1, y_flex: 1, wrap: 'none', scrollers: 'xy')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def component_did_build
|
46
|
+
TAG_STYLES.each do |k,v|
|
47
|
+
@code_text.native_item.tag_configure(k.to_s, v)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def update
|
52
|
+
if @code && @method_line
|
53
|
+
highlight_code(@code_text.native_item, @code, @filename)
|
54
|
+
@code_text.tk_item.select_range("#{@method_line}.0", "#{@method_line}.end")
|
55
|
+
@code_text.native_item.see("#{@method_line}.0")
|
56
|
+
@filename_label.native_item.text(@filename)
|
57
|
+
else
|
58
|
+
@filename_label.native_item.text('')
|
59
|
+
@code_text.tk_item.value = "Source code not available for #{@method_name}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
@@lexers = {}
|
64
|
+
|
65
|
+
def highlight_code(native_item, code, filename)
|
66
|
+
lexer = (@@lexers[filename] ||= Rouge::Lexers::Ruby.lex(code))
|
67
|
+
native_item.replace('1.0', 'end', '')
|
68
|
+
lexer.each do |token, chunk|
|
69
|
+
tag = TOKEN_TAGS[token.to_s]
|
70
|
+
native_item.insert('end', chunk, tag.to_s)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module TkInspect
|
2
|
+
module ClassBrowser
|
3
|
+
class ModuleMethodDataSource
|
4
|
+
attr_accessor :selected_class
|
5
|
+
|
6
|
+
def items_for_path(path)
|
7
|
+
path = [] if path.blank?
|
8
|
+
case path.size
|
9
|
+
when 0
|
10
|
+
return modules_of(selected_class)
|
11
|
+
when 1
|
12
|
+
return methods_of(path.first)
|
13
|
+
else
|
14
|
+
return []
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def title_for_path(path, items)
|
19
|
+
path = [] if path.blank?
|
20
|
+
case path.size
|
21
|
+
when 0
|
22
|
+
return "#{selected_class} -> #{items.size} #{'module'.pluralize(items.size)}"
|
23
|
+
when 1
|
24
|
+
if (module_name = path.first).present?
|
25
|
+
return "#{module_name} -> #{items.size} #{'method'.pluralize(items.size)}"
|
26
|
+
else
|
27
|
+
"#{items.size} #{'method'.pluralize(items.size)} in total"
|
28
|
+
end
|
29
|
+
else
|
30
|
+
return ''
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def modules_of(class_name)
|
37
|
+
return [] unless class_name.present?
|
38
|
+
begin
|
39
|
+
klass = Object.const_get(class_name)
|
40
|
+
rescue NameError
|
41
|
+
return []
|
42
|
+
end
|
43
|
+
return [] unless klass.is_a?(Class)
|
44
|
+
# We start with the empty module (to show all methods) and the class itself
|
45
|
+
modules = [ label_for_all_modules, klass ]
|
46
|
+
# Now we add the ancestor classes
|
47
|
+
parent = klass.superclass
|
48
|
+
while parent do
|
49
|
+
modules << parent
|
50
|
+
parent = parent.superclass
|
51
|
+
end
|
52
|
+
# Last, we add the included modules
|
53
|
+
(modules + klass.singleton_class.included_modules).map { |m| name_for_module(m) }
|
54
|
+
end
|
55
|
+
|
56
|
+
def methods_of(mod)
|
57
|
+
methods = nil
|
58
|
+
if mod == label_for_all_modules
|
59
|
+
# We want all the methods of the selected class
|
60
|
+
mod = Object.const_get(selected_class)
|
61
|
+
methods = mod&.instance_methods(true)
|
62
|
+
else
|
63
|
+
mod = Object.const_get(mod)
|
64
|
+
methods = mod&.instance_methods(false)
|
65
|
+
end
|
66
|
+
methods
|
67
|
+
.map { |m| name_for_method(m) }
|
68
|
+
.sort
|
69
|
+
end
|
70
|
+
|
71
|
+
def name_for_module(mod)
|
72
|
+
return mod if mod.nil?
|
73
|
+
mod.respond_to?(:name) ? mod.name : mod.to_s
|
74
|
+
end
|
75
|
+
|
76
|
+
def name_for_method(meth)
|
77
|
+
meth.to_s
|
78
|
+
end
|
79
|
+
|
80
|
+
def label_for_all_modules
|
81
|
+
'<all>'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'active_support/all'
|
2
|
+
|
3
|
+
module TkInspect
|
4
|
+
module ClassBrowser
|
5
|
+
class RootComponent < TkComponent::Base
|
6
|
+
attr_accessor :class_browser
|
7
|
+
|
8
|
+
def generate(parent_component, options = {})
|
9
|
+
parse_component(parent_component, options) do |p|
|
10
|
+
p.vframe(sticky: 'nsew', x_flex: 1, y_flex: 1) do |vf|
|
11
|
+
vf.hframe(padding: "12", sticky: 'nsew', x_flex: 1, y_flex: 0) do |hf|
|
12
|
+
hf.hframe(sticky: 'nw') do |f|
|
13
|
+
f.label(text: "Browse by: ")
|
14
|
+
f.radio_set(value: "hierarchy", on_change: :browse_by_changed) do |rs|
|
15
|
+
rs.radio_button(text: "Class hierarchy", value: 'hierarchy')
|
16
|
+
rs.radio_button(text: "Namespaces", value: 'namespaces')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
hf.hframe(sticky: 'ne', x_flex: 1) do |f|
|
20
|
+
f.label(text: "Filter")
|
21
|
+
f.entry(value: class_browser.class_data_source.class_filter, on_change: :filter_changed)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
vf.vpaned(sticky: 'nsew', x_flex: 1, y_flex: 1) do |vp|
|
25
|
+
@class_browser_comp = vp.insert_component(TkComponent::BrowserComponent, self,
|
26
|
+
data_source: class_browser.class_data_source,
|
27
|
+
selected_path: class_browser.selected_class_path,
|
28
|
+
paned: true,
|
29
|
+
sticky: 'nsew', x_flex: 1, y_flex: 1) do |bc|
|
30
|
+
bc.on_event'PathChanged', ->(e) { class_selected(e) }
|
31
|
+
end
|
32
|
+
@module_method_browser_component = vp.insert_component(TkComponent::RBrowserComponent, self,
|
33
|
+
data_source: class_browser.module_method_data_source,
|
34
|
+
paned: true,
|
35
|
+
max_columns: 2,
|
36
|
+
sticky: 'nsew', x_flex: 1, y_flex: 1) do |bc|
|
37
|
+
bc.on_event'PathChanged', ->(e) { module_method_selected(e) }
|
38
|
+
end
|
39
|
+
@code = vp.insert_component(CodeComponent, self, sticky: 'nsew', x_flex: 1, y_flex: 1)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def show_current_selection
|
46
|
+
@class_browser_comp.show_current_selection
|
47
|
+
end
|
48
|
+
|
49
|
+
def class_selected(e)
|
50
|
+
@class_browser.select_class_path(e.data_object.selected_path)
|
51
|
+
@module_method_browser_component.selected_path = []
|
52
|
+
@module_method_browser_component.regenerate
|
53
|
+
end
|
54
|
+
|
55
|
+
def module_method_selected(e)
|
56
|
+
path = e.data_object.selected_path || []
|
57
|
+
module_name = path[0]
|
58
|
+
method_name = path[1]
|
59
|
+
code, line, file = @class_browser.code_for_method(method_name)
|
60
|
+
@code.method_name = method_name
|
61
|
+
@code.code = code
|
62
|
+
@code.method_line = line
|
63
|
+
@code.filename = file
|
64
|
+
@code.update
|
65
|
+
end
|
66
|
+
|
67
|
+
def filter_changed(e)
|
68
|
+
return if class_browser.class_data_source.class_filter == e.sender.s_value
|
69
|
+
class_browser.class_data_source.class_filter = e.sender.s_value
|
70
|
+
@class_browser.select_class_path([])
|
71
|
+
@class_browser_comp.selected_path = []
|
72
|
+
@module_method_browser_component.selected_path = []
|
73
|
+
@class_browser_comp.regenerate
|
74
|
+
@module_method_browser_component.regenerate
|
75
|
+
end
|
76
|
+
|
77
|
+
def browse_by_changed(e)
|
78
|
+
@class_browser.browsing_method = e.sender.value
|
79
|
+
@class_browser_comp.data_source = @class_browser.class_data_source
|
80
|
+
@class_browser.select_class_path([])
|
81
|
+
@class_browser_comp.selected_path = []
|
82
|
+
@module_method_browser_component.selected_path = []
|
83
|
+
@class_browser_comp.regenerate
|
84
|
+
@module_method_browser_component.regenerate
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|