gloo 5.0.1 → 5.2.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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/lib/VERSION +1 -1
  3. data/lib/VERSION_NOTES +20 -1
  4. data/lib/gloo/app/args.rb +9 -0
  5. data/lib/gloo/app/engine.rb +42 -33
  6. data/lib/gloo/app/log.rb +0 -19
  7. data/lib/gloo/app/mode.rb +1 -1
  8. data/lib/gloo/core/it.rb +14 -0
  9. data/lib/gloo/core/obj.rb +22 -1
  10. data/lib/gloo/core/obj_finder.rb +21 -0
  11. data/lib/gloo/expr/op_eq.rb +5 -0
  12. data/lib/gloo/expr/op_mult.rb +8 -0
  13. data/lib/gloo/objs/basic/string.rb +5 -228
  14. data/lib/gloo/objs/basic/string_msgs.rb +269 -0
  15. data/lib/gloo/objs/basic/text.rb +7 -1
  16. data/lib/gloo/objs/ctrl/each.rb +2 -0
  17. data/lib/gloo/objs/ctrl/each_dir.rb +72 -0
  18. data/lib/gloo/persist/disc_mech.rb +10 -1
  19. data/lib/gloo/persist/persist_man.rb +11 -2
  20. data/lib/gloo/plugin/ext_manager.rb +8 -2
  21. data/lib/gloo/plugin/lib_manager.rb +8 -2
  22. data/lib/gloo/verbs/eval.rb +43 -0
  23. data/lib/gloo/verbs/exists.rb +13 -0
  24. data/lib/gloo/verbs/reload.rb +2 -0
  25. data/lib/gloo/verbs/unload.rb +1 -0
  26. data/test.gloo/basic.test.gloo +53 -0
  27. data/test.gloo/lang/gloo_sys.test.gloo +91 -0
  28. data/test.gloo/lang/ops.test.gloo +79 -0
  29. data/test.gloo/math/add.test.gloo +46 -0
  30. data/test.gloo/math/div.test.gloo +37 -0
  31. data/test.gloo/math/mult.test.gloo +37 -0
  32. data/test.gloo/math/sub.test.gloo +37 -0
  33. data/test.gloo/objs/bool.test.gloo +60 -0
  34. data/test.gloo/objs/can.test.gloo +47 -0
  35. data/test.gloo/objs/int.test.gloo +56 -0
  36. data/test.gloo/objs/obj.test.gloo +52 -0
  37. data/test.gloo/objs/script.test.gloo +23 -0
  38. data/test.gloo/objs/text.test.gloo +84 -0
  39. data/test.gloo/objs/untyped.test.gloo +25 -0
  40. data/test.gloo/string/str.test.gloo +118 -0
  41. data/test.gloo/string/str_gen.test.gloo +76 -0
  42. data/test.gloo/verbs/break.test.gloo +22 -0
  43. data/test.gloo/verbs/check.test.gloo +35 -0
  44. data/test.gloo/verbs/eval.test.gloo +19 -0
  45. data/test.gloo/verbs/exists.test.gloo +49 -0
  46. data/test.gloo/verbs/if.test.gloo +16 -0
  47. data/test.gloo/verbs/log.test.gloo +14 -0
  48. data/test.gloo/verbs/move.test.gloo +20 -0
  49. data/test.gloo/verbs/put.test.gloo +19 -0
  50. data/test.gloo/verbs/run.test.gloo +16 -0
  51. data/test.gloo/verbs/tell.test.gloo +16 -0
  52. metadata +31 -3
  53. data/lib/gloo/objs/system/eval.rb +0 -107
@@ -0,0 +1,269 @@
1
+ # Author:: Eric Crane (mailto:eric.crane@mac.com)
2
+ # Copyright:: Copyright (c) 2026 Eric Crane. All rights reserved.
3
+ #
4
+ # Some message implementations shared by String and Text objs.
5
+ #
6
+ require 'base64'
7
+ require 'uri'
8
+
9
+ module Gloo
10
+ module Objs
11
+ module StringMsgs
12
+
13
+ #
14
+ # Does the string start with the given string?
15
+ #
16
+ def msg_starts_with?
17
+ if @params&.token_count&.positive?
18
+ expr = Gloo::Expr::Expression.new( @engine, @params.tokens )
19
+ data = expr.evaluate
20
+
21
+ result = self.value.start_with?( data )
22
+ @engine.heap.it.set_to result
23
+ return result
24
+ else
25
+ # Error
26
+ @engine.log.error MISSING_PARAM_MSG
27
+ @engine.heap.it.set_to false
28
+ return false
29
+ end
30
+ end
31
+
32
+ #
33
+ # Does the string end with the given string?
34
+ #
35
+ def msg_ends_with?
36
+ if @params&.token_count&.positive?
37
+ expr = Gloo::Expr::Expression.new( @engine, @params.tokens )
38
+ data = expr.evaluate
39
+
40
+ result = value.end_with?( data )
41
+ @engine.heap.it.set_to result
42
+ return result
43
+ else
44
+ # Error
45
+ @engine.log.error MISSING_PARAM_MSG
46
+ @engine.heap.it.set_to false
47
+ return false
48
+ end
49
+ end
50
+
51
+ #
52
+ # Does the string contain the given string?
53
+ #
54
+ # This was formerly an overload of obj.contains?
55
+ # Contains? for the Obj checks for the presense of children.
56
+ #
57
+ def msg_substring?
58
+ if @params&.token_count&.positive?
59
+ expr = Gloo::Expr::Expression.new( @engine, @params.tokens )
60
+ data = expr.evaluate
61
+
62
+ result = value.include?( data )
63
+ @engine.heap.it.set_to result
64
+ return result
65
+ else
66
+ @engine.heap.it.set_to false
67
+ return false
68
+ end
69
+ end
70
+
71
+ #
72
+ # Get the size of the string.
73
+ #
74
+ def msg_size
75
+ s = value.size
76
+ @engine.heap.it.set_to s
77
+ return s
78
+ end
79
+
80
+ #
81
+ # Get the number of characters in the string.
82
+ #
83
+ def msg_count_chars
84
+ s = value.chars.count
85
+ @engine.heap.it.set_to s
86
+ return s
87
+ end
88
+
89
+ #
90
+ # Get the number of words in the string.
91
+ #
92
+ def msg_count_words
93
+ s = value.split( " " ).count
94
+ @engine.heap.it.set_to s
95
+ return s
96
+ end
97
+
98
+ #
99
+ # Get the number of lines in the string.
100
+ #
101
+ def msg_count_lines
102
+ s = value.split( "\n" ).count
103
+ @engine.heap.it.set_to s
104
+ return s
105
+ end
106
+
107
+ #
108
+ # Convert whitespace to HTML friendly spaces.
109
+ #
110
+ def msg_format_for_html
111
+ text = self.value
112
+ out = ""
113
+ return out unless text
114
+
115
+ # indentation
116
+ text.each_line do |line|
117
+ i = 0
118
+ while line[i] == ' '
119
+ i += 1
120
+ out << "&nbsp;"
121
+ end
122
+
123
+ i = 0
124
+ while line[i] == "\t"
125
+ i += 1
126
+ out << "&nbsp;&nbsp;&nbsp;&nbsp;"
127
+ end
128
+ out << line
129
+ end
130
+
131
+ self.value = out.gsub( "\n", "<br/>" )
132
+ end
133
+
134
+ #
135
+ # Escape the string.
136
+ # Make it URL safe.
137
+ # The value of the string is changed.
138
+ #
139
+ def msg_escape
140
+ s = URI::DEFAULT_PARSER.escape( value )
141
+ set_value s
142
+ @engine.heap.it.set_to s
143
+ return s
144
+ end
145
+
146
+ #
147
+ # Unescape the string.
148
+ # The value of the string is changed.
149
+ #
150
+ def msg_unescape
151
+ s = URI::DEFAULT_PARSER.unescape( value )
152
+ set_value s
153
+ @engine.heap.it.set_to s
154
+ return s
155
+ end
156
+
157
+ #
158
+ # Encode the string as base64.
159
+ # Changes the value of the string.
160
+ #
161
+ def msg_encode64
162
+ s = Base64.encode64( value )
163
+ set_value s
164
+ @engine.heap.it.set_to s
165
+ return s
166
+ end
167
+
168
+ #
169
+ # Decode the string from base64.
170
+ # Changes the value of the string.
171
+ #
172
+ def msg_decode64
173
+ s = Base64.decode64( value )
174
+ set_value s
175
+ @engine.heap.it.set_to s
176
+ return s
177
+ end
178
+
179
+ #
180
+ # Generate a new UUID in the string.
181
+ #
182
+ def msg_gen_uuid
183
+ s = StringGenerator.uuid
184
+ set_value s
185
+ @engine.heap.it.set_to s
186
+ return s
187
+ end
188
+
189
+ #
190
+ # Generate a random alphanumeric string.
191
+ # By default the length is 10 characters.
192
+ # Set the length with an optional parameter.
193
+ #
194
+ def msg_gen_alphanumeric
195
+ len = 10
196
+ if @params&.token_count&.positive?
197
+ expr = Gloo::Expr::Expression.new( @engine, @params.tokens )
198
+ data = expr.evaluate
199
+ len = data.to_i
200
+ end
201
+
202
+ s = StringGenerator.alphanumeric( len )
203
+ set_value s
204
+ @engine.heap.it.set_to s
205
+ return s
206
+ end
207
+
208
+ #
209
+ # Generate a random hex string.
210
+ # By default the length is 10 hex characters.
211
+ # Set the length with an optional parameter.
212
+ #
213
+ def msg_gen_hex
214
+ len = 10
215
+ if @params&.token_count&.positive?
216
+ expr = Gloo::Expr::Expression.new( @engine, @params.tokens )
217
+ data = expr.evaluate
218
+ len = data.to_i
219
+ end
220
+
221
+ s = StringGenerator.hex( len )
222
+ set_value s
223
+ @engine.heap.it.set_to s
224
+ return s
225
+ end
226
+
227
+ #
228
+ # Generate a random base64 string.
229
+ # By default the length is 12 characters.
230
+ # Set the length with an optional parameter.
231
+ #
232
+ def msg_gen_base64
233
+ len = 12
234
+ if @params&.token_count&.positive?
235
+ expr = Gloo::Expr::Expression.new( @engine, @params.tokens )
236
+ data = expr.evaluate
237
+ len = data.to_i
238
+ end
239
+
240
+ s = StringGenerator.base64( len )
241
+ set_value s
242
+ @engine.heap.it.set_to s
243
+ return s
244
+ end
245
+
246
+ #
247
+ # Convert string to upper case
248
+ #
249
+ def msg_up
250
+ s = value.upcase
251
+ set_value s
252
+ @engine.heap.it.set_to s
253
+ return s
254
+ end
255
+
256
+ #
257
+ # Convert string to lower case
258
+ #
259
+ def msg_down
260
+ s = value.downcase
261
+ set_value s
262
+ @engine.heap.it.set_to s
263
+ return s
264
+ end
265
+
266
+ end
267
+ end
268
+ end
269
+
@@ -3,11 +3,14 @@
3
3
  #
4
4
  # A [multiline] block of text.
5
5
  #
6
+ require_relative 'string_msgs'
6
7
 
7
8
  module Gloo
8
9
  module Objs
9
10
  class Text < Gloo::Core::Obj
10
11
 
12
+ include StringMsgs
13
+
11
14
  KEYWORD = 'text'.freeze
12
15
  KEYWORD_SHORT = 'txt'.freeze
13
16
 
@@ -55,7 +58,10 @@ module Gloo
55
58
  # Get a list of message names that this object receives.
56
59
  #
57
60
  def self.messages
58
- return super
61
+ return super + %w[up down size starts_with? ends_with? substring?
62
+ count_lines count_words count_chars
63
+ format_for_html encode64 decode64 escape unescape
64
+ gen_alphanumeric gen_uuid gen_hex gen_base64]
59
65
  end
60
66
 
61
67
  end
@@ -100,6 +100,8 @@ module Gloo
100
100
  EachLine.new( @engine, self ).run
101
101
  elsif EachFile.use_for?( self )
102
102
  EachFile.new( @engine, self ).run
103
+ elsif EachDir.use_for?( self )
104
+ EachDir.new( @engine, self ).run
103
105
  # elsif EachRepo.use_for?( self )
104
106
  # EachRepo.new( @engine, self ).run
105
107
  else
@@ -0,0 +1,72 @@
1
+ # Author:: Eric Crane (mailto:eric.crane@mac.com)
2
+ # Copyright:: Copyright (c) 2026 Eric Crane. All rights reserved.
3
+ #
4
+ # Iterate over each directory in a folder.
5
+ #
6
+
7
+ module Gloo
8
+ module Objs
9
+ class EachDir
10
+
11
+ DIR = 'dir'.freeze
12
+
13
+ # ---------------------------------------------------------------------
14
+ # Create Iterator
15
+ # ---------------------------------------------------------------------
16
+
17
+ def initialize( engine, iterator_obj )
18
+ @engine = engine
19
+ @iterator_obj = iterator_obj
20
+ end
21
+
22
+
23
+ # ---------------------------------------------------------------------
24
+ # Check if this is the right iterator
25
+ # ---------------------------------------------------------------------
26
+
27
+ #
28
+ # Use this iterator for each loop?
29
+ #
30
+ def self.use_for?( iterator_obj )
31
+ return true if iterator_obj.find_child DIR
32
+
33
+ return false
34
+ end
35
+
36
+
37
+ # ---------------------------------------------------------------------
38
+ # Iterate
39
+ # ---------------------------------------------------------------------
40
+
41
+ #
42
+ # Run for each directory.
43
+ #
44
+ def run
45
+ folder = @iterator_obj.in_value
46
+ return unless folder
47
+
48
+ unless Dir.exist?( folder )
49
+ @engine.err "Folder does not exist: #{folder}"
50
+ end
51
+
52
+ Dir.glob( "#{folder}*" ).each do |f|
53
+ if Dir.exist?( f )
54
+ set_dir f
55
+ @iterator_obj.run_do
56
+ end
57
+ end
58
+ end
59
+
60
+ #
61
+ # Set the value of the dir.
62
+ #
63
+ def set_dir( f )
64
+ o = @iterator_obj.find_child DIR
65
+ return unless o
66
+
67
+ o.set_value f
68
+ end
69
+
70
+ end
71
+ end
72
+ end
@@ -69,11 +69,20 @@ module Gloo
69
69
  # Expand a single file path.
70
70
  #
71
71
  def expand( name )
72
+ # Try full path
72
73
  ext_path = File.expand_path( name )
73
74
  return [ ext_path ] if self.valid?( ext_path )
74
75
 
76
+ # Try in user root, projects
75
77
  full_name = "#{name}#{file_ext}"
76
- return [ File.join( @engine.settings.project_path, full_name ) ]
78
+ pn = File.join( @engine.settings.project_path, full_name )
79
+ return [ pn ] if self.valid?( pn )
80
+
81
+ # Try in user root
82
+ pn = File.join( @engine.settings.user_root, full_name )
83
+ return [ pn ] if self.valid?( pn )
84
+
85
+ return nil
77
86
  end
78
87
 
79
88
  #
@@ -67,15 +67,19 @@ module Gloo
67
67
 
68
68
  #
69
69
  # Unload all loaded objects.
70
+ # The engine is reset to a clean state.
70
71
  #
71
72
  def unload_all
72
73
  objs = self.maps.map { |fs| fs.obj }
73
74
  objs.each { |o| o.msg_unload }
75
+ @engine.reset_state
74
76
  end
75
77
 
76
78
  #
77
79
  # The given object is unloading.
78
80
  # Do any necessary clean up here.
81
+ # This is used to unload a single object and comes from a
82
+ # message sent to the object.
79
83
  #
80
84
  def unload( obj )
81
85
  @engine.event_manager.on_unload obj
@@ -90,19 +94,24 @@ module Gloo
90
94
 
91
95
  #
92
96
  # Reload all objects.
97
+ # First send a message to each object to let it know it is being reloaded.
98
+ # Then let the engine restart to reload the files.
93
99
  #
94
100
  def reload_all
95
101
  return unless @maps
96
102
 
97
103
  @maps.each do |fs|
98
104
  @engine.event_manager.on_reload fs.obj
99
- @engine.heap.unload fs.obj
100
- fs.load
101
105
  end
106
+
107
+ # Acutal unloading is done in the engine.restart.
108
+ @engine.restart
102
109
  end
103
110
 
104
111
  #
105
112
  # Re-load the given object from file.
113
+ # This is used to reload a single object and comes from a
114
+ # message sent to the object.
106
115
  #
107
116
  def reload( obj )
108
117
  fs = find_file_storage( obj )
@@ -54,6 +54,11 @@ module Gloo
54
54
  # within the gloo extensions directory.
55
55
  #
56
56
  def load_ext( name )
57
+ if @extensions.key?( name )
58
+ @engine.log.info "Extension #{name} is already loaded."
59
+ return
60
+ end
61
+
57
62
  @engine.log.debug "Loading extension: #{name}"
58
63
  fn = ext_start_file name
59
64
 
@@ -80,8 +85,9 @@ module Gloo
80
85
  inst = plugin_class.new
81
86
  ext_cb = Callback.new( @engine )
82
87
  inst.register( ext_cb )
83
- rescue NameError
84
- @engine.log.error "Warning: Could not find class #{class_name} in file #{full_path}"
88
+ rescue NameError => ex
89
+ @engine.log_exception ex
90
+ @engine.log.error "Could not find class #{class_name} in file #{full_path}"
85
91
  end
86
92
  end
87
93
 
@@ -49,6 +49,12 @@ module Gloo
49
49
  # within the gloo libraries directory.
50
50
  #
51
51
  def load_lib( name )
52
+ # Check to see if the library is already loaded.
53
+ if @libraries.key?( name )
54
+ @engine.log.info "Library #{name} is already loaded."
55
+ return
56
+ end
57
+
52
58
  @engine.log.debug "Loading core library: #{name}"
53
59
  gem_name = core_lib_name name
54
60
 
@@ -87,8 +93,8 @@ module Gloo
87
93
  inst = plugin_class.new
88
94
  lib_cb = Callback.new( @engine )
89
95
  inst.register( lib_cb )
90
- rescue NameError
91
- @engine.log.error "Warning: Could not find class #{class_name}"
96
+ rescue NameError => ex
97
+ @engine.log.error "Warning: Could not find class #{class_name}", ex
92
98
  end
93
99
  end
94
100
 
@@ -0,0 +1,43 @@
1
+ # Author:: Eric Crane (mailto:eric.crane@mac.com)
2
+ # Copyright:: Copyright (c) 2026 Eric Crane. All rights reserved.
3
+ #
4
+ # Evaluate an expression and put the result into [it].
5
+ #
6
+
7
+ module Gloo
8
+ module Verbs
9
+ class Eval < Gloo::Core::Verb
10
+
11
+ KEYWORD = 'eval'.freeze
12
+ KEYWORD_SHORT = 'noop'.freeze
13
+
14
+ #
15
+ # Get the Verb's keyword.
16
+ #
17
+ def self.keyword
18
+ return KEYWORD
19
+ end
20
+
21
+ #
22
+ # Get the Verb's keyword shortcut.
23
+ #
24
+ def self.keyword_shortcut
25
+ return KEYWORD_SHORT
26
+ end
27
+
28
+ #
29
+ # Run the verb.
30
+ #
31
+ def run
32
+ if @tokens.token_count > 1
33
+ expr = Gloo::Expr::Expression.new( @engine, @tokens.params )
34
+ result = expr.evaluate
35
+ @engine.heap.it.set_to result
36
+ else
37
+ @engine.heap.it.set_to true
38
+ end
39
+ end
40
+
41
+ end
42
+ end
43
+ end
@@ -13,6 +13,7 @@ module Gloo
13
13
  VERB_TYPE = 'verb'.freeze
14
14
  OBJ_TYPE = 'object'.freeze
15
15
  ANY_TYPE = 'any'.freeze
16
+ INSTANCE = 'instance'.freeze
16
17
  WRONG_NUM_ARGS_ERR = 'Wrong number of arguments! 1 or 2 expected.'.freeze
17
18
 
18
19
  #
@@ -64,11 +65,23 @@ module Gloo
64
65
  return @engine.dictionary.obj?(keyword)
65
66
  elsif type == ANY_TYPE
66
67
  return @engine.dictionary.verb?(keyword) || @engine.dictionary.obj?(keyword)
68
+ elsif type == INSTANCE
69
+ return instance_exists?(keyword)
67
70
  end
68
71
 
69
72
  return false
70
73
  end
71
74
 
75
+ #
76
+ # Check to see if an instance of an object exists.
77
+ #
78
+ def instance_exists?( pn )
79
+ pn = Gloo::Core::Pn.new( @engine, pn )
80
+ o = pn.resolve
81
+ return false if o.nil?
82
+ return true
83
+ end
84
+
72
85
  end
73
86
  end
74
87
  end
@@ -13,6 +13,8 @@ module Gloo
13
13
 
14
14
  #
15
15
  # Run the verb.
16
+ # First all objects will be notified of the event.
17
+ # Then the engine will restart with the original parameters.
16
18
  #
17
19
  def run
18
20
  @engine.persist_man.reload_all
@@ -13,6 +13,7 @@ module Gloo
13
13
 
14
14
  #
15
15
  # Run the verb.
16
+ # This will unload all loaded objects and reset the engine state.
16
17
  #
17
18
  def run
18
19
  return unless @engine.persist_man.maps
@@ -0,0 +1,53 @@
1
+ #
2
+ # Basic tests
3
+ #
4
+
5
+ tests [can] :
6
+ basic [can] :
7
+
8
+ on_load [script] :
9
+ log 'Include functionality for all test in on_load' (debug)
10
+
11
+ # show_message [test] :
12
+ # description [string] : Show a message
13
+ # on_test [script] :
14
+ # show 'hello'
15
+ # eval it = 'hello'
16
+ # assert "expected it to be 'hello'"
17
+ # eval it = 'not hello'
18
+ # refute "expected it to not be 'not hello'"
19
+
20
+ assert_noop [test] :
21
+ description [string] : Assert no operation
22
+ on_test [script] :
23
+ noop
24
+ assert 'noop should be true'
25
+
26
+ refute [test] :
27
+ description [string] : Refute an operation
28
+ on_test [script] :
29
+ eval false
30
+ refute 'false should be false'
31
+
32
+ #
33
+ # Use this test to verify that errors cause the test to fail.
34
+ #
35
+ # refute_true [test] :
36
+ # description [string] : Refute true
37
+ # on_test [script] :
38
+ # noop
39
+ # refute 'expected this to fail!'
40
+ # refute 'also this'
41
+
42
+ # eval false
43
+ # assert
44
+
45
+ # eval true
46
+ # refute
47
+
48
+ write_log [test] :
49
+ description [string] : Write a debug log message
50
+ on_test [script] :
51
+ log 'Test debug log' (debug)
52
+ eval it = 'Test debug log'
53
+ assert "expected it to be 'Test debug log'"