sirens 0.0.1

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.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/lib/components/component.rb +46 -0
  3. data/lib/components/component_behaviour.rb +160 -0
  4. data/lib/components/containers/splitter.rb +98 -0
  5. data/lib/components/containers/stack.rb +42 -0
  6. data/lib/components/containers/tabs.rb +24 -0
  7. data/lib/components/containers/window.rb +20 -0
  8. data/lib/components/primitive_component.rb +155 -0
  9. data/lib/components/widgets/button.rb +16 -0
  10. data/lib/components/widgets/checkbox.rb +39 -0
  11. data/lib/components/widgets/column_props.rb +23 -0
  12. data/lib/components/widgets/input_text.rb +27 -0
  13. data/lib/components/widgets/list.rb +68 -0
  14. data/lib/components/widgets/list_choice.rb +89 -0
  15. data/lib/components/widgets/radio_button.rb +33 -0
  16. data/lib/components/widgets/text.rb +31 -0
  17. data/lib/components/widgets/tree_choice.rb +82 -0
  18. data/lib/layouts/columns_builder.rb +63 -0
  19. data/lib/layouts/layout_builder.rb +203 -0
  20. data/lib/layouts/radio_button_group_builder.rb +28 -0
  21. data/lib/models/choice_model.rb +41 -0
  22. data/lib/models/list_model.rb +173 -0
  23. data/lib/models/tree_choice_model.rb +102 -0
  24. data/lib/models/value_model.rb +56 -0
  25. data/lib/models/virtual_tree_model.rb +150 -0
  26. data/lib/sirens/browsers/module_browser.rb +67 -0
  27. data/lib/sirens/browsers/object_browser.rb +103 -0
  28. data/lib/sirens/components/ancestors_list.rb +22 -0
  29. data/lib/sirens/components/class_browser.rb +38 -0
  30. data/lib/sirens/components/constants_list.rb +22 -0
  31. data/lib/sirens/components/method_source_code.rb +22 -0
  32. data/lib/sirens/components/methods_list.rb +63 -0
  33. data/lib/sirens/components/modules_list.rb +23 -0
  34. data/lib/sirens/components/namespaces_list.rb +21 -0
  35. data/lib/sirens/models/constant_model.rb +21 -0
  36. data/lib/sirens/models/method_model.rb +111 -0
  37. data/lib/sirens/models/module_browser_model.rb +225 -0
  38. data/lib/sirens/models/object_browser_model.rb +125 -0
  39. data/lib/sirens.rb +90 -0
  40. data/lib/views/button_view.rb +56 -0
  41. data/lib/views/check_button_view.rb +65 -0
  42. data/lib/views/entry_view.rb +25 -0
  43. data/lib/views/list_view.rb +230 -0
  44. data/lib/views/menu_view.rb +46 -0
  45. data/lib/views/notebook_view.rb +27 -0
  46. data/lib/views/paned_view.rb +26 -0
  47. data/lib/views/radio_button_view.rb +67 -0
  48. data/lib/views/stack_view.rb +26 -0
  49. data/lib/views/text_view.rb +112 -0
  50. data/lib/views/tree_view.rb +314 -0
  51. data/lib/views/view.rb +206 -0
  52. data/lib/views/window_view.rb +51 -0
  53. metadata +110 -0
@@ -0,0 +1,173 @@
1
+ require 'observer'
2
+
3
+ module Sirens
4
+ class ListModel
5
+ include Observable
6
+
7
+ class << self
8
+ def with(item)
9
+ self.on([items])
10
+ end
11
+
12
+ def with_all(items)
13
+ self.on(items.clone)
14
+ end
15
+
16
+ def on(list)
17
+ self.new(list)
18
+ end
19
+ end
20
+
21
+ # Initializing
22
+
23
+ def initialize(list = [])
24
+ super()
25
+
26
+ @list = list
27
+ end
28
+
29
+ # Accessing
30
+
31
+ def list()
32
+ @list
33
+ end
34
+
35
+ def set_list(new_list)
36
+ return if @list == new_list
37
+
38
+ old_list = @list
39
+
40
+ @list = new_list
41
+
42
+ changed
43
+
44
+ notify_observers(
45
+ ListChanged.new(new_list: new_list, old_list: old_list)
46
+ )
47
+ end
48
+
49
+ def value()
50
+ list
51
+ end
52
+
53
+ def set_value(new_list)
54
+ set_list(new_list)
55
+ end
56
+
57
+ # Adding
58
+
59
+ ##
60
+ # Adds the given item at the end of the list.
61
+ #
62
+ def <<(item)
63
+ add_at(items: [item], index: -1)
64
+ end
65
+
66
+ ##
67
+ # Adds the given items at the end of the list.
68
+ #
69
+ def add_at(index:, items:)
70
+ list.insert(index, *items)
71
+
72
+ changed
73
+
74
+ notify_observers(
75
+ ItemsAdded.new(list: list, index: index, items: items)
76
+ )
77
+ end
78
+
79
+ # Updating
80
+
81
+ ##
82
+ # Updates the items at the given indices in the list.
83
+ #
84
+ def update_at(indices:, items:)
85
+ (0 ... items.size).each do |i|
86
+ list[ indices[i] ] = items[i]
87
+ end
88
+
89
+ changed
90
+
91
+ notify_observers(
92
+ ItemsUpdated.new(list: list, indices: indices, items: items)
93
+ )
94
+ end
95
+
96
+ # Removing
97
+
98
+ ##
99
+ # Removes the items at the given indices in the list.
100
+ #
101
+ def remove_at(indices:)
102
+ indices = indices.sort.reverse
103
+ items = []
104
+
105
+ indices.sort.reverse.each do |i|
106
+ items << list.delete_at(i)
107
+ end
108
+
109
+ changed
110
+
111
+ notify_observers(
112
+ ItemsRemoved.new(list: list, indices: indices, items: items)
113
+ )
114
+ end
115
+
116
+ # Iterating
117
+
118
+ include Enumerable
119
+
120
+ def each(&block)
121
+ @list.each(&block)
122
+ end
123
+
124
+ def [](index)
125
+ @list[index]
126
+ end
127
+
128
+ def []=(index, value)
129
+ @list[index] = value
130
+ end
131
+ end
132
+
133
+ # Announcements
134
+
135
+ class ListChanged
136
+ def initialize(new_list:, old_list:)
137
+ @new_list = new_list
138
+ @old_list = old_list
139
+ end
140
+
141
+ attr_reader :new_list, :old_list
142
+ end
143
+
144
+ class ItemsAdded
145
+ def initialize(list:, index:, items:)
146
+ @list = list
147
+ @index = index
148
+ @items = items
149
+ end
150
+
151
+ attr_reader :list, :index, :items
152
+ end
153
+
154
+ class ItemsUpdated
155
+ def initialize(list:, indices:, items:)
156
+ @list = list
157
+ @indices = indices
158
+ @items = items
159
+ end
160
+
161
+ attr_reader :list, :indices, :items
162
+ end
163
+
164
+ class ItemsRemoved
165
+ def initialize(list:, indices:, items:)
166
+ @list = list
167
+ @indices = indices
168
+ @items = items
169
+ end
170
+
171
+ attr_reader :list, :indices, :items
172
+ end
173
+ end
@@ -0,0 +1,102 @@
1
+ module Sirens
2
+ class TreeChoiceModel
3
+
4
+ # Initializing
5
+
6
+ def initialize(selection: nil, roots: [], get_children_block:)
7
+ super()
8
+
9
+ @selection = ValueModel.on(selection)
10
+ @tree = VirtualTreeModel.on(
11
+ roots: roots,
12
+ get_children_block: get_children_block
13
+ )
14
+ end
15
+
16
+ # Accessing
17
+
18
+ ##
19
+ # Returns the selection model, not the selected item.
20
+ # This model can be observed for changes.
21
+ # This model value is an array with the objects path from the tree root object to the selected item.
22
+ #
23
+ def selection()
24
+ @selection
25
+ end
26
+
27
+ ##
28
+ # Sets the item currently selected.
29
+ # The objects_hierarchy is an array with the objects path from the tree root object to the
30
+ # selected item.
31
+ # The selection model triggers an announcement that its value changed.
32
+ #
33
+ def set_selection(objects_hierarchy)
34
+ @selection.set_value(objects_hierarchy)
35
+ end
36
+
37
+ ##
38
+ # Sets the item currently selected.
39
+ # The objects_hierarchy is an array with the objects path from the tree root object to the
40
+ # selected item.
41
+ # The selection model triggers an announcement that its value changed.
42
+ #
43
+ def set_selection_from_path(path:)
44
+ objects_hierarchies = objects_hierarchy_at(path: path)
45
+
46
+ set_selection(objects_hierarchies)
47
+ end
48
+
49
+ ##
50
+ # Returns the tree model.
51
+ # The tree model holds the tree items and announces changes on it.
52
+ #
53
+ def tree()
54
+ @tree
55
+ end
56
+
57
+ ##
58
+ # Sets the tree model roots.
59
+ # Announces that the tree changed.
60
+ #
61
+ def set_tree_roots(items)
62
+ @tree.set_roots(items)
63
+ end
64
+
65
+ ##
66
+ # Returns the item in the tree at the given path.
67
+ # The path is an array of Integers, each Integer being the index in each level of the tree.
68
+ # Example:
69
+ # [1, 0, 3] returns the item taken from the second root, its first child and its fourth child.
70
+ #
71
+ def item_at(path:)
72
+ @tree.item_at(path: path)
73
+ end
74
+
75
+ ##
76
+ # Returns an array with the children of the item in the tree at the given path.
77
+ #
78
+ def children_at(path:)
79
+ @tree.children_at(path: path)
80
+ end
81
+
82
+ ##
83
+ # Given a hierarchy of objects in the tree, returns an array with the path indices.
84
+ #
85
+ def path_of(objects_hierarchy)
86
+ @tree.path_of(objects_hierarchy)
87
+ end
88
+
89
+ ##
90
+ # Given a path returns an array with the objects on each tree level corresponding to each index in the path.
91
+ #
92
+ def objects_hierarchy_at(path:)
93
+ @tree.objects_hierarchy_at(path: path)
94
+ end
95
+
96
+ # Asking
97
+
98
+ def has_selection()
99
+ ! @selection.value.nil? && ! @selection.value.empty?
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,56 @@
1
+ require 'observer'
2
+
3
+ module Sirens
4
+ class ValueModel
5
+ include Observable
6
+
7
+ class << self
8
+ def on(value)
9
+ self.new(value)
10
+ end
11
+ end
12
+
13
+ # Initializing
14
+
15
+ def initialize(value = nil)
16
+ super()
17
+
18
+ @value = value
19
+ end
20
+
21
+ # Accessing
22
+
23
+ def value()
24
+ @value
25
+ end
26
+
27
+ def set_value(new_value)
28
+ return if value == new_value
29
+
30
+ old_value = value
31
+
32
+ @value = new_value
33
+
34
+ announce_value_changed(new_value: new_value, old_value: old_value)
35
+ end
36
+
37
+ def announce_value_changed(new_value:, old_value:)
38
+ changed
39
+
40
+ notify_observers(
41
+ ValueChanged.new(new_value: new_value, old_value: old_value)
42
+ )
43
+ end
44
+ end
45
+
46
+ # Events
47
+
48
+ class ValueChanged
49
+ def initialize(new_value:, old_value:)
50
+ @new_value = new_value
51
+ @old_value = old_value
52
+ end
53
+
54
+ attr_reader :new_value, :old_value
55
+ end
56
+ end
@@ -0,0 +1,150 @@
1
+ require 'observer'
2
+
3
+ module Sirens
4
+ class VirtualTreeModel
5
+ include Observable
6
+
7
+ class << self
8
+ def with(root_item)
9
+ self.on([root_item])
10
+ end
11
+
12
+ def with_all(root_items)
13
+ self.on(root_items.clone)
14
+ end
15
+
16
+ def on(root_items)
17
+ self.new(root_items)
18
+ end
19
+ end
20
+
21
+ # Initializing
22
+
23
+ def initialize(roots: [], get_children_block:)
24
+ super()
25
+
26
+ @get_children_block = get_children_block
27
+
28
+ @roots = roots.collect{ |item| new_tree_node_on(item) }
29
+ end
30
+
31
+ def new_tree_node_on(item)
32
+ TreeNode.new(value: item, get_children_block: @get_children_block)
33
+ end
34
+
35
+ # Accessing
36
+
37
+ def roots()
38
+ @roots.collect{ |node| node.value }
39
+ end
40
+
41
+ def set_roots(new_roots)
42
+ old_roots = roots
43
+
44
+ @roots = new_roots.collect{ |item| new_tree_node_on(item) }
45
+
46
+ changed
47
+
48
+ notify_observers(
49
+ TreeChanged.new(new_roots: new_roots, old_roots: old_roots)
50
+ )
51
+ end
52
+
53
+ def node_at(path:)
54
+ node = @roots[path.first]
55
+
56
+ path[1..-1].each do |child_index|
57
+ node = node.child_at(index: child_index)
58
+ end
59
+
60
+ node
61
+ end
62
+
63
+ def item_at(path:)
64
+ node_at(path: path).value
65
+ end
66
+
67
+ def children_at(path:)
68
+ node_at(path: path).children.collect{ |node| node.value }
69
+ end
70
+
71
+ ##
72
+ # Given a hierarchy of objects in the tree, returns an array with the path indices.
73
+ #
74
+ def path_of(objects_hierarchy)
75
+ objects = @roots
76
+
77
+ objects_hierarchy.inject([]) { |path, each_object|
78
+ index = objects.index { |node| node.value == each_object }
79
+
80
+ path << index
81
+
82
+ objects = objects[index].children
83
+
84
+ path
85
+ }
86
+ end
87
+
88
+ ##
89
+ # Given a path returns an array with the objects on each tree level corresponding to each index in the path.
90
+ #
91
+ def objects_hierarchy_at(path:)
92
+ return [] if path.nil?
93
+
94
+ nodes = @roots
95
+
96
+ path.inject([]) { |hierarchy, index|
97
+ node = nodes[index]
98
+
99
+ hierarchy << node.value
100
+
101
+ nodes = node.children
102
+
103
+ hierarchy
104
+ }
105
+ end
106
+
107
+ # Announcements
108
+
109
+ class TreeChanged
110
+ def initialize(new_roots:, old_roots:)
111
+ @new_roots = new_roots
112
+ @old_roots = old_roots
113
+ end
114
+
115
+ attr_reader :new_roots, :old_roots
116
+ end
117
+
118
+ # Tree node class
119
+
120
+ class TreeNode
121
+ def initialize(value:, get_children_block:)
122
+ @value = value
123
+ @children = nil
124
+ @get_children_block = get_children_block
125
+ end
126
+
127
+ def value()
128
+ @value
129
+ end
130
+
131
+ def children()
132
+ if @children.nil?
133
+ @children = get_children.collect{ |item|
134
+ self.class.new(value: item, get_children_block: @get_children_block)
135
+ }
136
+ end
137
+
138
+ @children
139
+ end
140
+
141
+ def child_at(index:)
142
+ children[index]
143
+ end
144
+
145
+ def get_children()
146
+ @get_children_block.call(value)
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,67 @@
1
+ module Sirens
2
+
3
+ class ModuleBrowser < Sirens::Component
4
+
5
+ def self.open_on(klass: nil)
6
+ self.open.tap { |browser|
7
+ if klass.kind_of?(Class)
8
+ browser.model.select_class(klass)
9
+ else
10
+ browser.model.select_namespace(namespace)
11
+ end
12
+ }
13
+ end
14
+
15
+ # Model
16
+
17
+ def default_model()
18
+ ModuleBrowserModel.new
19
+ end
20
+
21
+ # Building
22
+
23
+ def renderWith(layout)
24
+ browser_model = model
25
+
26
+ layout.render do |component|
27
+
28
+ window do
29
+
30
+ styles title: 'Namespace Browser',
31
+ width: 900,
32
+ height: 400
33
+
34
+ vertical_splitter do
35
+
36
+ horizontal_splitter do
37
+
38
+ styles splitter_proportion: 1.0/2.0
39
+
40
+ component NamespacesList.new(
41
+ model: browser_model.namespaces,
42
+ splitter_proportion: 1.0/4.0
43
+ )
44
+
45
+ component ModulesList.new(
46
+ model: browser_model.modules,
47
+ splitter_proportion: 1.0/4.0,
48
+ )
49
+
50
+ component ClassBrowser.new(
51
+ model: browser_model,
52
+ splitter_proportion: 2.0/4.0
53
+ )
54
+ end
55
+
56
+ component MethodSourceCode.new(
57
+ model: browser_model.method_source_code,
58
+ splitter_proportion: 1.0/2.0
59
+ )
60
+
61
+ end
62
+
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,103 @@
1
+ module Sirens
2
+
3
+ class ObjectBrowser < Sirens::Component
4
+
5
+ def self.open_on(object:)
6
+ self.open.tap { |browser|
7
+ browser.model.object.set_value(object)
8
+ }
9
+ end
10
+
11
+ # Model
12
+
13
+ def default_model()
14
+ ObjectBrowserModel.new
15
+ end
16
+
17
+ # Building
18
+
19
+ def renderWith(layout)
20
+ browser_model = model
21
+
22
+ layout.render do |component|
23
+
24
+ window do
25
+
26
+ styles title: 'Object Browser',
27
+ width: 400,
28
+ height: 400
29
+
30
+ vertical_splitter do
31
+
32
+ choices_tree do
33
+ model browser_model.object_instance_variables
34
+
35
+ styles splitter_proportion: 2.0/3.0
36
+
37
+ handlers on_selection_action: proc { |index_path:, item:|
38
+ component.browse(object: item.value)
39
+ }
40
+
41
+ column label: 'Instance variables',
42
+ get_text_block: proc { |inst_var| inst_var.display_string }
43
+
44
+ popup_menu { |menu:, menu_owner:|
45
+ selected_object = component.model.selected_inst_var_value
46
+
47
+ menu.item label: 'Browse it', enabled: ! selected_object.nil?,
48
+ action: proc{ component.browse(object: selected_object) }
49
+
50
+ menu.separator
51
+
52
+ menu.item label: 'Browse class', enabled: ! selected_object.nil?,
53
+ action: proc{ component.browse_class_of(object: selected_object) }
54
+ }
55
+ end
56
+
57
+ text do
58
+ model browser_model.text
59
+
60
+ styles(
61
+ splitter_proportion: 1.0/3.0,
62
+ wrap_mode: :char
63
+ )
64
+
65
+ popup_menu { |menu:, menu_owner:|
66
+ selected_text = menu_owner.selected_text
67
+
68
+ menu.separator
69
+
70
+ menu.item label: 'Browse it', enabled: ! selected_text.nil?,
71
+ action: proc{ component.browse_text_selection(text: selected_text) }
72
+ }
73
+ end
74
+
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ # Actions
81
+
82
+ def browse(object:)
83
+ Sirens.browse(object: object)
84
+ end
85
+
86
+ def browse_class_of(object:)
87
+ Sirens.browse(klass: object.class)
88
+ end
89
+
90
+ def browse_text_selection(text:)
91
+ selected_object = model.selected_inst_var_value
92
+
93
+ evaluation_result =
94
+ begin
95
+ selected_object.instance_exec { eval(text) }
96
+ rescue Exception => e
97
+ e
98
+ end
99
+
100
+ browse(object: evaluation_result)
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,22 @@
1
+ module Sirens
2
+ class AncestorsList < Sirens::Component
3
+
4
+ # Building
5
+
6
+ def renderWith(layout)
7
+
8
+ layout.render do |component|
9
+ choices_list do
10
+ model component.model
11
+
12
+ styles(
13
+ show_headers: true
14
+ )
15
+
16
+ column label: 'Module ancestors',
17
+ get_text_block: proc{ |a_module| a_module.name }
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,38 @@
1
+ module Sirens
2
+ class ClassBrowser < Sirens::Component
3
+
4
+ # Building
5
+
6
+ def renderWith(layout)
7
+ browser_model = model
8
+
9
+ layout.render do |component|
10
+ horizontal_splitter do
11
+ component AncestorsList.new(
12
+ model: browser_model.module_ancestors,
13
+ splitter_proportion: 1.0/3.0
14
+ )
15
+
16
+ tabs do
17
+ styles splitter_proportion: 2.0/3.0
18
+
19
+ component MethodsList.new(
20
+ model: browser_model.methods,
21
+ instance_or_class_methods_chooser: browser_model.instance_or_class_methods_chooser,
22
+ show_inherit_methods: browser_model.show_inherit_methods,
23
+ show_public_methods: browser_model.show_public_methods,
24
+ show_protected_methods: browser_model.show_protected_methods,
25
+ show_private_methods: browser_model.show_private_methods,
26
+ tab_label: 'Methods'
27
+ )
28
+
29
+ component ConstantsList.new(
30
+ model: browser_model.constants,
31
+ tab_label: 'Constants'
32
+ )
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end