glimmer-dsl-libui 0.5.5 → 0.5.8

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.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.5
1
+ 0.5.8
@@ -0,0 +1,31 @@
1
+ require 'glimmer-dsl-libui'
2
+
3
+ class BasicCodeArea
4
+ include Glimmer::LibUI::Application
5
+
6
+ before_body do
7
+ @code = <<~CODE
8
+ # Greets target with greeting
9
+ def greet(greeting: 'Hello', target: 'World')
10
+
11
+ puts "\#{greeting}, \#{target}!"
12
+ end
13
+
14
+ greet
15
+ greet(target: 'Robert')
16
+ greet(greeting: 'Aloha')
17
+ greet(greeting: 'Aloha', target: 'Nancy')
18
+ greet(greeting: 'Howdy', target: 'Doodle')
19
+ CODE
20
+ end
21
+
22
+ body {
23
+ window('Basic Code Area', 400, 300) {
24
+ margined true
25
+
26
+ code_area(language: 'ruby', code: @code)
27
+ }
28
+ }
29
+ end
30
+
31
+ BasicCodeArea.launch
@@ -0,0 +1,121 @@
1
+ require 'glimmer-dsl-libui'
2
+ require 'facets'
3
+
4
+ Address = Struct.new(:street, :p_o_box, :city, :state, :zip_code)
5
+
6
+ class FormField
7
+ include Glimmer::LibUI::CustomControl
8
+
9
+ options :model, :attribute
10
+
11
+ body {
12
+ entry { |e|
13
+ label attribute.to_s.underscore.split('_').map(&:capitalize).join(' ')
14
+ text <=> [model, attribute]
15
+ }
16
+ }
17
+ end
18
+
19
+ class AddressForm
20
+ include Glimmer::LibUI::CustomControl
21
+
22
+ options :address
23
+
24
+ body {
25
+ form {
26
+ form_field(model: address, attribute: :street)
27
+ form_field(model: address, attribute: :p_o_box)
28
+ form_field(model: address, attribute: :city)
29
+ form_field(model: address, attribute: :state)
30
+ form_field(model: address, attribute: :zip_code)
31
+ }
32
+ }
33
+ end
34
+
35
+ class LabelPair
36
+ include Glimmer::LibUI::CustomControl
37
+
38
+ options :model, :attribute, :value
39
+
40
+ body {
41
+ horizontal_box {
42
+ label(attribute.to_s.underscore.split('_').map(&:capitalize).join(' '))
43
+ label(value.to_s) {
44
+ text <= [model, attribute]
45
+ }
46
+ }
47
+ }
48
+ end
49
+
50
+ class AddressView
51
+ include Glimmer::LibUI::CustomControl
52
+
53
+ options :address
54
+
55
+ body {
56
+ vertical_box {
57
+ address.each_pair do |attribute, value|
58
+ label_pair(model: address, attribute: attribute, value: value)
59
+ end
60
+ }
61
+ }
62
+ end
63
+
64
+ class ClassBasedCustomControls
65
+ include Glimmer::LibUI::Application # alias: Glimmer::LibUI::CustomWindow
66
+
67
+ before_body do
68
+ @address1 = Address.new('123 Main St', '23923', 'Denver', 'Colorado', '80014')
69
+ @address2 = Address.new('2038 Park Ave', '83272', 'Boston', 'Massachusetts', '02101')
70
+ end
71
+
72
+ body {
73
+ window('Class-Based Custom Keyword') {
74
+ margined true
75
+
76
+ horizontal_box {
77
+ vertical_box {
78
+ label('Address 1') {
79
+ stretchy false
80
+ }
81
+
82
+ address_form(address: @address1)
83
+
84
+ horizontal_separator {
85
+ stretchy false
86
+ }
87
+
88
+ label('Address 1 (Saved)') {
89
+ stretchy false
90
+ }
91
+
92
+ address_view(address: @address1)
93
+ }
94
+
95
+ vertical_separator {
96
+ stretchy false
97
+ }
98
+
99
+ vertical_box {
100
+ label('Address 2') {
101
+ stretchy false
102
+ }
103
+
104
+ address_form(address: @address2)
105
+
106
+ horizontal_separator {
107
+ stretchy false
108
+ }
109
+
110
+ label('Address 2 (Saved)') {
111
+ stretchy false
112
+ }
113
+
114
+ address_view(address: @address2)
115
+ }
116
+ }
117
+ }
118
+ }
119
+ end
120
+
121
+ ClassBasedCustomControls.launch
@@ -32,7 +32,7 @@ def label_pair(model, attribute, value)
32
32
  }
33
33
  end
34
34
 
35
- def address(address_model)
35
+ def address_view(address_model)
36
36
  vertical_box {
37
37
  address_model.each_pair do |attribute, value|
38
38
  label_pair(address_model, attribute, value)
@@ -43,7 +43,7 @@ end
43
43
  address1 = Address.new('123 Main St', '23923', 'Denver', 'Colorado', '80014')
44
44
  address2 = Address.new('2038 Park Ave', '83272', 'Boston', 'Massachusetts', '02101')
45
45
 
46
- window('Method-Based Custom Keyword') {
46
+ window('Method-Based Custom Controls') {
47
47
  margined true
48
48
 
49
49
  horizontal_box {
@@ -62,7 +62,7 @@ window('Method-Based Custom Keyword') {
62
62
  stretchy false
63
63
  }
64
64
 
65
- address(address1)
65
+ address_view(address1)
66
66
  }
67
67
 
68
68
  vertical_separator {
@@ -84,7 +84,7 @@ window('Method-Based Custom Keyword') {
84
84
  stretchy false
85
85
  }
86
86
 
87
- address(address2)
87
+ address_view(address2)
88
88
  }
89
89
  }
90
90
  }.show
@@ -39,7 +39,7 @@ def label_pair(model, attribute, value)
39
39
  end
40
40
  end
41
41
 
42
- def address(address_model)
42
+ def address_view(address_model)
43
43
  vertical_box {
44
44
  address_model.each_pair do |attribute, value|
45
45
  label_pair(address_model, attribute, value)
@@ -50,7 +50,7 @@ end
50
50
  address1 = Address.new('123 Main St', '23923', 'Denver', 'Colorado', '80014')
51
51
  address2 = Address.new('2038 Park Ave', '83272', 'Boston', 'Massachusetts', '02101')
52
52
 
53
- window('Method-Based Custom Keyword') {
53
+ window('Method-Based Custom Controls') {
54
54
  margined true
55
55
 
56
56
  horizontal_box {
@@ -69,7 +69,7 @@ window('Method-Based Custom Keyword') {
69
69
  stretchy false
70
70
  }
71
71
 
72
- address(address1)
72
+ address_view(address1)
73
73
  }
74
74
 
75
75
  vertical_separator {
@@ -91,7 +91,7 @@ window('Method-Based Custom Keyword') {
91
91
  stretchy false
92
92
  }
93
93
 
94
- address(address2)
94
+ address_view(address2)
95
95
  }
96
96
  }
97
97
  }.show
Binary file
@@ -0,0 +1,59 @@
1
+ # Copyright (c) 2021-2022 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'glimmer'
23
+ require 'glimmer/dsl/expression'
24
+ require 'glimmer/dsl/parent_expression'
25
+ require 'glimmer/dsl/top_level_expression'
26
+ require 'glimmer/libui/custom_control'
27
+ require 'glimmer/libui/custom_window'
28
+
29
+ module Glimmer
30
+ module DSL
31
+ module Libui
32
+ class CustomControlExpression < Expression
33
+ # TODO Consider making custom controls automatically generate static expressions
34
+ include ParentExpression
35
+ include TopLevelExpression
36
+
37
+ def can_interpret?(parent, keyword, *args, &block)
38
+ LibUI::CustomControl.for(keyword)
39
+ end
40
+
41
+ def interpret(parent, keyword, *args, &block)
42
+ options = args.last.is_a?(Hash) ? args.pop : {}
43
+ LibUI::CustomControl.for(keyword).new(keyword, parent, args, options, &block)
44
+ end
45
+
46
+ def add_content(custom_control, keyword, *args, &block)
47
+ options = args.last.is_a?(Hash) ? args.last : {post_add_content: true}
48
+ # TODO consider avoiding source_location
49
+ if block.source_location == custom_control.content&.__getobj__&.source_location
50
+ custom_control.content.call(custom_control) unless custom_control.content.called?
51
+ else
52
+ super
53
+ end
54
+ custom_control.post_add_content if options[:post_add_content]
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -43,6 +43,7 @@ module Glimmer
43
43
  string
44
44
  operation
45
45
  control
46
+ custom_control
46
47
  shape
47
48
  ]
48
49
  )
@@ -32,7 +32,8 @@ module Glimmer
32
32
  (
33
33
  parent.is_a?(Glimmer::LibUI::ControlProxy) or
34
34
  parent.is_a?(Glimmer::LibUI::Shape) or
35
- parent.is_a?(Glimmer::LibUI::AttributedString)
35
+ parent.is_a?(Glimmer::LibUI::AttributedString) or
36
+ parent.is_a?(Glimmer::LibUI::CustomControl)
36
37
  ) and
37
38
  block.nil? and
38
39
  parent.respond_to?("#{keyword}=", *args)
@@ -85,7 +85,7 @@ module Glimmer
85
85
  end
86
86
 
87
87
  def post_add_content
88
- unless parent_proxy.is_a?(Box)
88
+ if OS.linux? && parent_proxy.is_a?(WindowProxy)
89
89
  unless @content_added
90
90
  original_parent_proxy = @parent_proxy
91
91
  @vertical_box_parent_proxy = ControlProxy.create('vertical_box', parent_proxy, []) {} # block prevents calling post add content
@@ -401,4 +401,4 @@ module Glimmer
401
401
  end
402
402
  end
403
403
 
404
- Dir[File.expand_path('./control_proxy/*.rb', __dir__)].each {|f| require f}
404
+ Dir[File.expand_path("./#{File.basename(__FILE__, '.rb')}/*.rb", __dir__)].each {|f| require f}
@@ -0,0 +1,65 @@
1
+ class CodeArea
2
+ class << self
3
+ def languages
4
+ require 'rouge'
5
+ Rouge::Lexer.all.map {|lexer| lexer.tag}.sort
6
+ end
7
+
8
+ def lexers
9
+ require 'rouge'
10
+ Rouge::Lexer.all.sort_by(&:title)
11
+ end
12
+ end
13
+
14
+ include Glimmer::LibUI::CustomControl
15
+
16
+ REGEX_COLOR_HEX6 = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/
17
+
18
+ option :language, default: 'ruby'
19
+ option :theme, default: 'glimmer'
20
+ option :code
21
+
22
+ body {
23
+ area {
24
+ rectangle(0, 0, 8000, 8000) {
25
+ fill :white
26
+ }
27
+ text {
28
+ default_font family: OS.mac? ? 'Consolas' : 'Courier', size: 13, weight: :medium, italic: :normal, stretch: :normal
29
+
30
+ syntax_highlighting(code).each do |token|
31
+ style_data = Rouge::Theme.find(theme).new.style_for(token[:token_type])
32
+
33
+ string(token[:token_text]) {
34
+ color style_data[:fg] || :black
35
+ background style_data[:bg] || :white
36
+ }
37
+ end
38
+ }
39
+ }
40
+ }
41
+
42
+ def lexer
43
+ require 'rouge'
44
+ require 'glimmer-dsl-libui/ext/rouge/theme/glimmer'
45
+ # TODO Try to use Rouge::Lexer.find_fancy('guess', code) in the future to guess the language or otherwise detect it from file extension
46
+ @lexer ||= Rouge::Lexer.find_fancy(language)
47
+ @lexer ||= Rouge::Lexer.find_fancy('ruby') # default to Ruby if no lexer is found
48
+ end
49
+
50
+ def syntax_highlighting(text)
51
+ return [] if text.to_s.strip.empty?
52
+ @syntax_highlighting ||= {}
53
+ unless @syntax_highlighting.keys.include?(text)
54
+ lex = lexer.lex(text).to_a
55
+ text_size = 0
56
+ @syntax_highlighting[text] = lex.map do |pair|
57
+ {token_type: pair.first, token_text: pair.last}
58
+ end.each do |hash|
59
+ hash[:token_index] = text_size
60
+ text_size += hash[:token_text].size
61
+ end
62
+ end
63
+ @syntax_highlighting[text]
64
+ end
65
+ end
@@ -0,0 +1,255 @@
1
+ # Copyright (c) 2021-2022 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'super_module'
23
+ require 'glimmer'
24
+ require 'glimmer/error'
25
+ require 'glimmer/proc_tracker'
26
+ require 'glimmer/data_binding/observer'
27
+ require 'glimmer/data_binding/observable_model'
28
+
29
+ module Glimmer
30
+ module LibUI
31
+ module CustomControl
32
+ include SuperModule
33
+ include DataBinding::ObservableModel
34
+
35
+ super_module_included do |klass|
36
+ # TODO clear memoization of WidgetProxy.libui_class_for for a keyword if a custom control was defined with that keyword
37
+ unless klass.name.include?('Glimmer::LibUI::CustomWindow')
38
+ klass.include(Glimmer)
39
+ Glimmer::LibUI::CustomControl.add_custom_control_namespaces_for(klass)
40
+ end
41
+ end
42
+
43
+ class << self
44
+ def for(keyword)
45
+ unless flyweight_custom_control_classes.keys.include?(keyword)
46
+ begin
47
+ extracted_namespaces = keyword.
48
+ to_s.
49
+ split(/__/).map do |namespace|
50
+ namespace.camelcase(:upper)
51
+ end
52
+ custom_control_namespaces.each do |base|
53
+ extracted_namespaces.reduce(base) do |result, namespace|
54
+ if !result.constants.include?(namespace)
55
+ namespace = result.constants.detect {|c| c.to_s.upcase == namespace.to_s.upcase } || namespace
56
+ end
57
+ begin
58
+ flyweight_custom_control_classes[keyword] = constant = result.const_get(namespace)
59
+ return constant if constant.ancestors.include?(Glimmer::LibUI::CustomControl)
60
+ flyweight_custom_control_classes[keyword] = constant
61
+ rescue => e
62
+ # Glimmer::Config.logger.debug {"#{e.message}\n#{e.backtrace.join("\n")}"}
63
+ flyweight_custom_control_classes[keyword] = result
64
+ end
65
+ end
66
+ end
67
+ raise "#{keyword} has no custom control class!"
68
+ rescue => e
69
+ Glimmer::Config.logger.debug {e.message}
70
+ Glimmer::Config.logger.debug {"#{e.message}\n#{e.backtrace.join("\n")}"}
71
+ flyweight_custom_control_classes[keyword] = nil
72
+ end
73
+ end
74
+ flyweight_custom_control_classes[keyword]
75
+ end
76
+
77
+ # Flyweight Design Pattern memoization cache. Can be cleared if memory is needed.
78
+ def flyweight_custom_control_classes
79
+ @flyweight_custom_control_classes ||= {}
80
+ end
81
+
82
+ # Returns keyword to use for this custom control
83
+ def keyword
84
+ self.name.underscore.gsub('::', '__')
85
+ end
86
+
87
+ # Returns shortcut keyword to use for this custom control (keyword minus namespace)
88
+ def shortcut_keyword
89
+ self.name.underscore.gsub('::', '__').split('__').last
90
+ end
91
+
92
+ def add_custom_control_namespaces_for(klass)
93
+ Glimmer::LibUI::CustomControl.namespaces_for_class(klass).drop(1).each do |namespace|
94
+ Glimmer::LibUI::CustomControl.custom_control_namespaces << namespace
95
+ end
96
+ end
97
+
98
+ def namespaces_for_class(m)
99
+ return [m] if m.name.nil?
100
+ namespace_constants = m.name.split(/::/).map(&:to_sym)
101
+ namespace_constants.reduce([Object]) do |output, namespace_constant|
102
+ output += [output.last.const_get(namespace_constant)]
103
+ end[1..-1].uniq.reverse
104
+ end
105
+
106
+ def custom_control_namespaces
107
+ @custom_control_namespaces ||= reset_custom_control_namespaces
108
+ end
109
+
110
+ def reset_custom_control_namespaces
111
+ @custom_control_namespaces = Set[Object, Glimmer::LibUI]
112
+ end
113
+
114
+ # Allows defining convenience option accessors for an array of option names
115
+ # Example: `options :color1, :color2` defines `#color1` and `#color2`
116
+ # where they return the instance values `options[:color1]` and `options[:color2]`
117
+ # respectively.
118
+ # Can be called multiple times to set more options additively.
119
+ # When passed no arguments, it returns list of all option names captured so far
120
+ def options(*new_options)
121
+ new_options = new_options.compact.map(&:to_s).map(&:to_sym)
122
+ if new_options.empty?
123
+ @options ||= {} # maps options to defaults
124
+ else
125
+ new_options = new_options.reduce({}) {|new_options_hash, new_option| new_options_hash.merge(new_option => nil)}
126
+ @options = options.merge(new_options)
127
+ def_option_attr_accessors(new_options)
128
+ end
129
+ end
130
+
131
+ def option(new_option, default: nil)
132
+ new_option = new_option.to_s.to_sym
133
+ new_options = {new_option => default}
134
+ @options = options.merge(new_options)
135
+ def_option_attr_accessors(new_options)
136
+ end
137
+
138
+ def def_option_attr_accessors(new_options)
139
+ new_options.each do |option, default|
140
+ class_eval <<-end_eval, __FILE__, __LINE__
141
+ def #{option}
142
+ options[:#{option}]
143
+ end
144
+
145
+ def #{option}=(option_value)
146
+ self.options[:#{option}] = option_value
147
+ end
148
+ end_eval
149
+ end
150
+ end
151
+
152
+ def before_body(&block)
153
+ @before_body_block = block
154
+ end
155
+
156
+ def body(&block)
157
+ @body_block = block
158
+ end
159
+
160
+ def after_body(&block)
161
+ @after_body_block = block
162
+ end
163
+ end
164
+
165
+ attr_reader :body_root, :libui, :parent, :parent_proxy, :args, :keyword, :content, :options
166
+
167
+ def initialize(keyword, parent, args, options, &content)
168
+ @parent_proxy = @parent = parent
169
+ options ||= {}
170
+ @options = self.class.options.merge(options)
171
+ @content = ProcTracker.new(content) if content
172
+ execute_hook('before_body')
173
+ body_block = self.class.instance_variable_get("@body_block")
174
+ raise Glimmer::Error, 'Invalid custom control for having no body! Please define body block!' if body_block.nil?
175
+ @body_root = instance_exec(&body_block)
176
+ raise Glimmer::Error, 'Invalid custom control for having an empty body! Please fill body block!' if @body_root.nil?
177
+ @libui = @body_root.libui
178
+ execute_hook('after_body')
179
+ # TODO deregister all observer_registrations on destroy of the control once that listener is supported
180
+ # (on_destroy) unless it is the last window closing, in which case exit faster
181
+ post_add_content if content.nil?
182
+ end
183
+
184
+ # Subclasses may override to perform post initialization work on an added child
185
+ def post_initialize_child(child)
186
+ # No Op by default
187
+ end
188
+
189
+ def post_add_content
190
+ # No Op by default
191
+ end
192
+
193
+ def observer_registrations
194
+ @observer_registrations ||= []
195
+ end
196
+
197
+ def can_handle_listener?(listener)
198
+ body_root&.can_handle_listener?(listener.to_s)
199
+ end
200
+
201
+ def handle_listener(listener, &block)
202
+ body_root.handle_listener(listener.to_s, &block)
203
+ end
204
+
205
+ # This method ensures it has an instance method not coming from Glimmer DSL
206
+ def has_instance_method?(method_name)
207
+ respond_to?(method_name) and
208
+ !@body_root.respond_to_libui?(method_name) and
209
+ (method(method_name) rescue nil) and
210
+ !method(method_name)&.source_location&.first&.include?('glimmer/dsl/engine.rb') and
211
+ !method(method_name)&.source_location&.first&.include?('glimmer/libui/control_proxy.rb')
212
+ end
213
+
214
+ # Returns content block if used as an attribute reader (no args)
215
+ # Otherwise, if a block is passed, it adds it as content to this custom control
216
+ def content(&block)
217
+ if block_given?
218
+ Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::Libui::CustomControlExpression.new, self.class.keyword, &block)
219
+ else
220
+ @content
221
+ end
222
+ end
223
+
224
+ def method_missing(method_name, *args, &block)
225
+ # TODO Consider supporting a glimmer error silencing option for methods defined here
226
+ # but fail the glimmer DSL for the right reason to avoid seeing noise in the log output
227
+ if block && can_handle_listener?(method_name)
228
+ handle_listener(method_name, &block)
229
+ else
230
+ @body_root.send(method_name, *args, &block)
231
+ end
232
+ end
233
+
234
+ def respond_to?(method_name, *args, &block)
235
+ result = false
236
+ result ||= super
237
+ result ||= can_handle_listener?(method_name)
238
+ result ||= @body_root.respond_to?(method_name, *args, &block)
239
+ end
240
+
241
+ private
242
+
243
+ def execute_hook(hook_name)
244
+ hook_block = self.class.instance_variable_get("@#{hook_name}_block")
245
+ return if hook_block.nil?
246
+ temp_method_name = "#{hook_name}_block_#{hook_block.hash.abs}_#{(Time.now.to_f * 1_000_000).to_i}"
247
+ singleton_class.define_method(temp_method_name, &hook_block)
248
+ send(temp_method_name)
249
+ singleton_class.send(:remove_method, temp_method_name)
250
+ end
251
+ end
252
+ end
253
+ end
254
+
255
+ Dir[File.expand_path("./#{File.basename(__FILE__, '.rb')}/*.rb", __dir__)].each {|f| require f}