gloo 6.7.2 → 7.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.
- checksums.yaml +4 -4
- data/CLAUDE.md +1 -0
- data/docs/application.md +3 -1
- data/docs/iterators.md +39 -3
- data/docs/language_syntax.md +53 -5
- data/docs/objects.md +59 -2
- data/docs/operators.md +16 -0
- data/docs/plugins.md +9 -1
- data/docs/verbs.md +4 -2
- data/lib/VERSION +1 -1
- data/lib/VERSION_NOTES +24 -0
- data/lib/gloo/app/engine.rb +64 -8
- data/lib/gloo/app/log.rb +47 -6
- data/lib/gloo/convert/converter.rb +63 -2
- data/lib/gloo/convert/string_to_date.rb +14 -0
- data/lib/gloo/convert/string_to_datetime.rb +16 -0
- data/lib/gloo/convert/string_to_decimal.rb +13 -0
- data/lib/gloo/convert/string_to_integer.rb +9 -2
- data/lib/gloo/convert/string_to_time.rb +28 -0
- data/lib/gloo/core/error.rb +11 -2
- data/lib/gloo/core/event_manager.rb +7 -4
- data/lib/gloo/core/factory.rb +16 -11
- data/lib/gloo/core/gloo_system.rb +3 -2
- data/lib/gloo/core/here.rb +6 -1
- data/lib/gloo/core/invoker.rb +2 -3
- data/lib/gloo/core/not_found.rb +42 -0
- data/lib/gloo/core/obj.rb +30 -3
- data/lib/gloo/core/op.rb +2 -0
- data/lib/gloo/core/parser.rb +1 -1
- data/lib/gloo/core/pn.rb +33 -12
- data/lib/gloo/core/tokens.rb +38 -0
- data/lib/gloo/core/verb.rb +54 -0
- data/lib/gloo/exec/dispatch.rb +5 -6
- data/lib/gloo/exec/exec_env.rb +13 -0
- data/lib/gloo/exec/runner.rb +10 -2
- data/lib/gloo/exec/script.rb +13 -1
- data/lib/gloo/expr/expression.rb +39 -1
- data/lib/gloo/expr/op_plus.rb +3 -0
- data/lib/gloo/objs/basic/boolean.rb +16 -0
- data/lib/gloo/objs/basic/container.rb +154 -6
- data/lib/gloo/objs/basic/decimal.rb +1 -0
- data/lib/gloo/objs/basic/integer.rb +1 -0
- data/lib/gloo/objs/basic/string.rb +0 -1
- data/lib/gloo/objs/basic/string_msgs.rb +68 -32
- data/lib/gloo/objs/ctrl/each.rb +22 -3
- data/lib/gloo/objs/ctrl/each_dir.rb +20 -7
- data/lib/gloo/objs/ctrl/each_file.rb +78 -9
- data/lib/gloo/objs/ctrl/function.rb +6 -5
- data/lib/gloo/objs/dt/date.rb +28 -11
- data/lib/gloo/objs/dt/datetime.rb +72 -21
- data/lib/gloo/objs/dt/dt_tools.rb +46 -2
- data/lib/gloo/objs/dt/time.rb +28 -11
- data/lib/gloo/objs/str_utils/cipher.rb +17 -4
- data/lib/gloo/objs/str_utils/password.rb +6 -2
- data/lib/gloo/objs/system/erb.rb +9 -3
- data/lib/gloo/objs/system/file_handle.rb +57 -21
- data/lib/gloo/objs/system/system.rb +6 -2
- data/lib/gloo/objs/web/http_get.rb +12 -2
- data/lib/gloo/objs/web/http_post.rb +6 -2
- data/lib/gloo/objs/web/json.rb +38 -18
- data/lib/gloo/objs/web/uri.rb +36 -16
- data/lib/gloo/persist/file_loader.rb +72 -11
- data/lib/gloo/persist/file_saver.rb +2 -1
- data/lib/gloo/persist/indent_stack.rb +49 -33
- data/lib/gloo/persist/line_splitter.rb +40 -3
- data/lib/gloo/persist/persist_man.rb +2 -4
- data/lib/gloo/plugin/ext_manager.rb +6 -9
- data/lib/gloo/plugin/lib_manager.rb +2 -2
- data/lib/gloo/verbs/break.rb +1 -0
- data/lib/gloo/verbs/check.rb +1 -1
- data/lib/gloo/verbs/cls.rb +1 -0
- data/lib/gloo/verbs/create.rb +32 -4
- data/lib/gloo/verbs/execute.rb +1 -1
- data/lib/gloo/verbs/exists.rb +27 -15
- data/lib/gloo/verbs/files.rb +2 -1
- data/lib/gloo/verbs/help.rb +1 -0
- data/lib/gloo/verbs/if.rb +1 -1
- data/lib/gloo/verbs/invoke.rb +1 -1
- data/lib/gloo/verbs/list.rb +4 -3
- data/lib/gloo/verbs/load.rb +3 -3
- data/lib/gloo/verbs/move.rb +10 -8
- data/lib/gloo/verbs/put.rb +20 -8
- data/lib/gloo/verbs/quit.rb +1 -0
- data/lib/gloo/verbs/redirect.rb +8 -2
- data/lib/gloo/verbs/reload.rb +9 -1
- data/lib/gloo/verbs/run.rb +2 -2
- data/lib/gloo/verbs/save.rb +4 -2
- data/lib/gloo/verbs/tell.rb +1 -1
- data/lib/gloo/verbs/unless.rb +1 -1
- data/lib/gloo/verbs/unload.rb +8 -0
- data/test.gloo/ctrl/each.test.gloo +88 -0
- data/test.gloo/dt/datetime.test.gloo +5 -3
- data/test.gloo/lang/convert.test.gloo +1 -0
- data/test.gloo/lang/declaration.test.gloo +38 -0
- data/test.gloo/lang/exceptions.test.gloo +68 -0
- data/test.gloo/lang/failures.test.gloo +143 -0
- data/test.gloo/lang/gloo_sys.test.gloo +14 -9
- data/test.gloo/lang/it.test.gloo +139 -0
- data/test.gloo/lang/naming.test.gloo +21 -0
- data/test.gloo/lang/ops.test.gloo +30 -0
- data/test.gloo/math/div.test.gloo +2 -2
- data/test.gloo/math/mult.test.gloo +4 -4
- data/test.gloo/objs/can.test.gloo +104 -0
- data/test.gloo/objs/function.test.gloo +1 -0
- data/test.gloo/objs/string.test.gloo +24 -0
- data/test.gloo/string/str.test.gloo +28 -0
- data/test.gloo/verbs/check.test.gloo +10 -1
- data/test.gloo/verbs/create.test.gloo +37 -0
- data/test.gloo/verbs/exists.test.gloo +34 -1
- data/test.gloo/verbs/invoke.test.gloo +1 -0
- data/test.gloo/verbs/list.test.gloo +1 -0
- data/test.gloo/verbs/load.test.gloo +2 -0
- data/test.gloo/verbs/reload.test.gloo +1 -0
- data/test.gloo/verbs/save.test.gloo +2 -0
- data/test.gloo/verbs/throw.test.gloo +2 -0
- data/test.gloo/verbs/unload.test.gloo +35 -0
- metadata +4 -1
|
@@ -8,6 +8,16 @@ module Gloo
|
|
|
8
8
|
module Convert
|
|
9
9
|
class Converter
|
|
10
10
|
|
|
11
|
+
# How each type reads in a warning: "'x' is not an integer".
|
|
12
|
+
TYPE_NAMES = {
|
|
13
|
+
'Integer' => 'an integer',
|
|
14
|
+
'Decimal' => 'a decimal number',
|
|
15
|
+
'Date' => 'a date',
|
|
16
|
+
'Time' => 'a time',
|
|
17
|
+
'DateTime' => 'a date and time'
|
|
18
|
+
}.freeze
|
|
19
|
+
NO_CONVERSION_ERR = 'There is no conversion to '.freeze
|
|
20
|
+
|
|
11
21
|
#
|
|
12
22
|
# Initializer.
|
|
13
23
|
#
|
|
@@ -23,12 +33,30 @@ module Gloo
|
|
|
23
33
|
# Convert the given value to the specified type,
|
|
24
34
|
# or if no conversion is available, revert to default.
|
|
25
35
|
#
|
|
36
|
+
# If the value isn't really of that type (eg. 'x' for an
|
|
37
|
+
# integer), the conversion is still the best guess, and a warning
|
|
38
|
+
# says what was guessed. A kind of value there's no converter for
|
|
39
|
+
# is also warned about, using the default. Converting to a type
|
|
40
|
+
# that isn't known is an error.
|
|
41
|
+
#
|
|
26
42
|
def convert( value, to_type, default = nil )
|
|
27
43
|
begin
|
|
28
44
|
name = "Gloo::Convert::#{value.class}To#{to_type}"
|
|
29
|
-
clazz =
|
|
45
|
+
clazz = find_converter( name )
|
|
46
|
+
unless TYPE_NAMES.key?( to_type )
|
|
47
|
+
@engine.err "#{NO_CONVERSION_ERR}'#{to_type}'."
|
|
48
|
+
return default
|
|
49
|
+
end
|
|
50
|
+
unless clazz
|
|
51
|
+
@engine.warn "'#{value}' is not #{TYPE_NAMES[ to_type ]}; " \
|
|
52
|
+
"using #{default.nil? ? 'no value' : default}."
|
|
53
|
+
return default
|
|
54
|
+
end
|
|
55
|
+
|
|
30
56
|
o = clazz.new
|
|
31
|
-
|
|
57
|
+
result = o.convert( value )
|
|
58
|
+
warn_bad_value( o, value, result, to_type )
|
|
59
|
+
return result
|
|
32
60
|
rescue => e
|
|
33
61
|
@engine.log_exception e
|
|
34
62
|
end
|
|
@@ -36,6 +64,39 @@ module Gloo
|
|
|
36
64
|
return default
|
|
37
65
|
end
|
|
38
66
|
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
#
|
|
70
|
+
# Find the converter class by name, or nil if there isn't one for
|
|
71
|
+
# that kind of value.
|
|
72
|
+
#
|
|
73
|
+
def find_converter( name )
|
|
74
|
+
return name.split( '::' ).inject( Object ) { |o, c| o.const_get( c, false ) }
|
|
75
|
+
rescue NameError
|
|
76
|
+
return nil
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
#
|
|
80
|
+
# Warn if the converter says the value isn't valid for its type.
|
|
81
|
+
#
|
|
82
|
+
def warn_bad_value( converter, value, result, to_type )
|
|
83
|
+
return unless converter.respond_to?( :valid? )
|
|
84
|
+
return if converter.valid?( value, result )
|
|
85
|
+
|
|
86
|
+
@engine.warn "'#{value}' is not #{TYPE_NAMES[ to_type ]}; " \
|
|
87
|
+
"using #{describe( converter, result )}."
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
#
|
|
91
|
+
# Describe the guess for a warning.
|
|
92
|
+
#
|
|
93
|
+
def describe( converter, result )
|
|
94
|
+
return 'no value' if result.nil?
|
|
95
|
+
return converter.describe( result ) if converter.respond_to?( :describe )
|
|
96
|
+
|
|
97
|
+
return result.to_s
|
|
98
|
+
end
|
|
99
|
+
|
|
39
100
|
end
|
|
40
101
|
end
|
|
41
102
|
end
|
|
@@ -21,6 +21,20 @@ module Gloo
|
|
|
21
21
|
return Chronic.parse( value )
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
#
|
|
25
|
+
# Is the value really a date? A blank value is no value.
|
|
26
|
+
#
|
|
27
|
+
def valid?( value, result )
|
|
28
|
+
return value.blank? || !result.nil?
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
#
|
|
32
|
+
# Describe the guess for a warning.
|
|
33
|
+
#
|
|
34
|
+
def describe( result )
|
|
35
|
+
return result.strftime( Gloo::Objs::Date::DEFAULT_FORMAT )
|
|
36
|
+
end
|
|
37
|
+
|
|
24
38
|
end
|
|
25
39
|
end
|
|
26
40
|
end
|
|
@@ -16,6 +16,22 @@ module Gloo
|
|
|
16
16
|
return Chronic.parse( value )
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
#
|
|
20
|
+
# Is the value really a date and time? A blank value is no value.
|
|
21
|
+
#
|
|
22
|
+
def valid?( value, result )
|
|
23
|
+
return true if value.blank?
|
|
24
|
+
|
|
25
|
+
return !result.nil? && !StringToTime.clock_out_of_range?( value )
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
#
|
|
29
|
+
# Describe the guess for a warning.
|
|
30
|
+
#
|
|
31
|
+
def describe( result )
|
|
32
|
+
return result.strftime( Gloo::Objs::DateTime::DEFAULT_FORMAT )
|
|
33
|
+
end
|
|
34
|
+
|
|
19
35
|
end
|
|
20
36
|
end
|
|
21
37
|
end
|
|
@@ -15,6 +15,19 @@ module Gloo
|
|
|
15
15
|
return value.to_f
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
+
#
|
|
19
|
+
# Is the value really a decimal number? A blank value is no
|
|
20
|
+
# value (0.0).
|
|
21
|
+
#
|
|
22
|
+
def valid?( value, _result )
|
|
23
|
+
return true if value.blank?
|
|
24
|
+
|
|
25
|
+
Float( value.strip )
|
|
26
|
+
return true
|
|
27
|
+
rescue ArgumentError
|
|
28
|
+
return false
|
|
29
|
+
end
|
|
30
|
+
|
|
18
31
|
end
|
|
19
32
|
end
|
|
20
33
|
end
|
|
@@ -12,11 +12,18 @@ module Gloo
|
|
|
12
12
|
# Convert the given string value to an integer.
|
|
13
13
|
#
|
|
14
14
|
def convert( value )
|
|
15
|
-
return
|
|
16
|
-
|
|
15
|
+
return 0 if value.blank?
|
|
16
|
+
|
|
17
17
|
return value.to_i
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
+
#
|
|
21
|
+
# Is the value really an integer? A blank value is no value (0).
|
|
22
|
+
#
|
|
23
|
+
def valid?( value, _result )
|
|
24
|
+
return value.blank? || value.match?( /\A\s*[-+]?\d+\s*\z/ )
|
|
25
|
+
end
|
|
26
|
+
|
|
20
27
|
end
|
|
21
28
|
end
|
|
22
29
|
end
|
|
@@ -16,6 +16,34 @@ module Gloo
|
|
|
16
16
|
return Chronic.parse( value )
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
#
|
|
20
|
+
# Is the value really a time? A blank value is no value. A clock
|
|
21
|
+
# time out of range (eg. 25:99) is parsed, but rolls over.
|
|
22
|
+
#
|
|
23
|
+
def valid?( value, result )
|
|
24
|
+
return true if value.blank?
|
|
25
|
+
|
|
26
|
+
return !result.nil? && !StringToTime.clock_out_of_range?( value )
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
#
|
|
30
|
+
# Describe the guess for a warning.
|
|
31
|
+
#
|
|
32
|
+
def describe( result )
|
|
33
|
+
return result.strftime( Gloo::Objs::Time::DEFAULT_FORMAT )
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
#
|
|
37
|
+
# Does the value have a clock time (h:mm or h:mm:ss) with an hour,
|
|
38
|
+
# minute or second out of range?
|
|
39
|
+
#
|
|
40
|
+
def self.clock_out_of_range?( value )
|
|
41
|
+
m = value.match( /(\d{1,2}):(\d{2})(?::(\d{2}))?/ )
|
|
42
|
+
return false unless m
|
|
43
|
+
|
|
44
|
+
return m[ 1 ].to_i > 24 || m[ 2 ].to_i > 59 || m[ 3 ].to_i > 59
|
|
45
|
+
end
|
|
46
|
+
|
|
19
47
|
end
|
|
20
48
|
end
|
|
21
49
|
end
|
data/lib/gloo/core/error.rb
CHANGED
|
@@ -8,7 +8,12 @@ module Gloo
|
|
|
8
8
|
module Core
|
|
9
9
|
class Error
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
# The kind of error: the command couldn't be understood (syntax),
|
|
12
|
+
# or it was understood but couldn't be done (runtime).
|
|
13
|
+
SYNTAX = 'syntax'.freeze
|
|
14
|
+
RUNTIME = 'runtime'.freeze
|
|
15
|
+
|
|
16
|
+
attr_accessor :value, :error_count, :kind, :location
|
|
12
17
|
|
|
13
18
|
#
|
|
14
19
|
# Set up the error object.
|
|
@@ -23,14 +28,18 @@ module Gloo
|
|
|
23
28
|
def clear
|
|
24
29
|
@error_count = 0
|
|
25
30
|
@value = nil
|
|
31
|
+
@kind = nil
|
|
32
|
+
@location = nil
|
|
26
33
|
end
|
|
27
34
|
|
|
28
35
|
#
|
|
29
36
|
# Set the value of error.
|
|
30
37
|
#
|
|
31
|
-
def set_to( new_value )
|
|
38
|
+
def set_to( new_value, kind = RUNTIME, location = nil )
|
|
32
39
|
@error_count += 1
|
|
33
40
|
@value = new_value
|
|
41
|
+
@kind = kind
|
|
42
|
+
@location = location
|
|
34
43
|
end
|
|
35
44
|
|
|
36
45
|
#
|
|
@@ -75,16 +75,19 @@ module Gloo
|
|
|
75
75
|
#
|
|
76
76
|
# Run the on_error scripts in any open objects.
|
|
77
77
|
# For each on_error script found, look for the error_data container
|
|
78
|
-
# and set the error message
|
|
78
|
+
# and set the error message, backtrace, kind (syntax or runtime)
|
|
79
|
+
# and location -- each only if error_data has that child.
|
|
79
80
|
#
|
|
80
|
-
def on_error msg, backtrace
|
|
81
|
+
def on_error msg, backtrace, kind = Gloo::Core::Error::RUNTIME, location = nil
|
|
81
82
|
@engine.log.debug 'on_error event'
|
|
82
83
|
arr = Gloo::Core::ObjFinder.by_name( @engine, 'on_error' )
|
|
83
84
|
arr.each do |o|
|
|
84
85
|
data = o.parent.find_child 'error_data'
|
|
85
86
|
if data
|
|
86
|
-
data.find_child( 'message' )
|
|
87
|
-
data.find_child( 'backtrace' )
|
|
87
|
+
data.find_child( 'message' )&.set_value msg
|
|
88
|
+
data.find_child( 'backtrace' )&.set_value backtrace
|
|
89
|
+
data.find_child( 'kind' )&.set_value kind
|
|
90
|
+
data.find_child( 'location' )&.set_value location
|
|
88
91
|
end
|
|
89
92
|
|
|
90
93
|
Gloo::Exec::Dispatch.message( @engine, 'run', o )
|
data/lib/gloo/core/factory.rb
CHANGED
|
@@ -8,6 +8,9 @@ module Gloo
|
|
|
8
8
|
module Core
|
|
9
9
|
class Factory < Baseo
|
|
10
10
|
|
|
11
|
+
UNTYPED = 'untyped'.freeze
|
|
12
|
+
UNKNOWN_TYPE_ERR = 'Unknown type '.freeze
|
|
13
|
+
|
|
11
14
|
# ---------------------------------------------------------------------
|
|
12
15
|
# Initializer
|
|
13
16
|
# ---------------------------------------------------------------------
|
|
@@ -155,6 +158,12 @@ module Gloo
|
|
|
155
158
|
return self.update_existing pn, params[ :value ]
|
|
156
159
|
end
|
|
157
160
|
|
|
161
|
+
unless parent
|
|
162
|
+
missing = pn.elements[ 0..-2 ].join( '.' )
|
|
163
|
+
@engine.err "Could not create '#{params[ :name ]}': #{Gloo::Core::NotFound.object( missing )}"
|
|
164
|
+
return nil
|
|
165
|
+
end
|
|
166
|
+
|
|
158
167
|
@engine.log.debug "Creating new object: #{obj_name}"
|
|
159
168
|
return create_new obj_name, params[ :value ], objtype, parent
|
|
160
169
|
end
|
|
@@ -163,11 +172,6 @@ module Gloo
|
|
|
163
172
|
# Create a new object.
|
|
164
173
|
#
|
|
165
174
|
def create_new( name, value, type, parent )
|
|
166
|
-
unless parent
|
|
167
|
-
@engine.err "Could not create object. Bad path: #{name}"
|
|
168
|
-
return nil
|
|
169
|
-
end
|
|
170
|
-
|
|
171
175
|
# Check to see if the object exists already
|
|
172
176
|
return parent.find_child( name ) if parent.contains_child?( name )
|
|
173
177
|
|
|
@@ -189,20 +193,21 @@ module Gloo
|
|
|
189
193
|
|
|
190
194
|
#
|
|
191
195
|
# Find the object type by name.
|
|
192
|
-
#
|
|
193
|
-
#
|
|
196
|
+
# An unknown type is a syntax error; the best guess is untyped,
|
|
197
|
+
# so the object (and anything nested under it) is still created.
|
|
198
|
+
# Return nil if the object type cannot be created.
|
|
194
199
|
#
|
|
195
200
|
def find_type( type_name )
|
|
196
|
-
type_name =
|
|
201
|
+
type_name = UNTYPED if type_name.nil? || type_name.strip.empty?
|
|
197
202
|
t = @engine.dictionary.find_obj( type_name )
|
|
198
203
|
|
|
199
204
|
if t.nil?
|
|
200
|
-
@engine.
|
|
201
|
-
|
|
205
|
+
@engine.syntax_err "#{UNKNOWN_TYPE_ERR}'#{type_name}'; using untyped."
|
|
206
|
+
t = @engine.dictionary.find_obj( UNTYPED )
|
|
202
207
|
end
|
|
203
208
|
|
|
204
209
|
unless t.can_create?
|
|
205
|
-
@engine.
|
|
210
|
+
@engine.syntax_err "'#{type_name}' cannot be created."
|
|
206
211
|
return nil
|
|
207
212
|
end
|
|
208
213
|
|
|
@@ -246,7 +246,7 @@ module Gloo
|
|
|
246
246
|
|
|
247
247
|
# Is the platform Linux?
|
|
248
248
|
def msg_platform_linux?
|
|
249
|
-
return OS.
|
|
249
|
+
return OS.linux?
|
|
250
250
|
end
|
|
251
251
|
|
|
252
252
|
# Is the platform Mac?
|
|
@@ -322,7 +322,8 @@ module Gloo
|
|
|
322
322
|
'platform_windows? — Is the platform Windows?',
|
|
323
323
|
'platform_unix? — Is the platform Unix?',
|
|
324
324
|
'platform_linux? — Is the platform Linux?',
|
|
325
|
-
'platform_mac? — Is the platform Mac?'
|
|
325
|
+
'platform_mac? — Is the platform Mac?',
|
|
326
|
+
'platform_wsl? — Is the platform WSL (Linux on Windows)?'
|
|
326
327
|
],
|
|
327
328
|
:examples => <<~EXAMPLES.strip
|
|
328
329
|
> show gloo.user
|
data/lib/gloo/core/here.rb
CHANGED
|
@@ -23,11 +23,16 @@ module Gloo
|
|
|
23
23
|
#
|
|
24
24
|
def self.expand_here( engine, pn )
|
|
25
25
|
target = engine.exec_env.here_obj
|
|
26
|
+
# Outside a running script there's nothing for ^ to be
|
|
27
|
+
# relative to; leave the path as is, so the lookup finds nothing.
|
|
28
|
+
return unless target
|
|
26
29
|
|
|
27
30
|
here = pn.elements.first
|
|
28
31
|
remainder = pn.elements[ 1..-1 ].join( '.' )
|
|
29
32
|
|
|
30
|
-
here.length.times { target = target
|
|
33
|
+
here.length.times { target = target&.parent }
|
|
34
|
+
return unless target
|
|
35
|
+
|
|
31
36
|
full_path = target.pn.empty? ? remainder : "#{target.pn}.#{remainder}"
|
|
32
37
|
pn.set_to full_path
|
|
33
38
|
end
|
data/lib/gloo/core/invoker.rb
CHANGED
|
@@ -12,7 +12,6 @@ module Gloo
|
|
|
12
12
|
class Invoker
|
|
13
13
|
|
|
14
14
|
NO_TARGET_ERR = 'Missing function reference!'.freeze
|
|
15
|
-
NOT_FOUND_ERR = 'Object was not found: '.freeze
|
|
16
15
|
NOT_FUNCTION_ERR = 'Not a function: '.freeze
|
|
17
16
|
PARAM_COUNT_ERR = 'Wrong number of parameters for function: '.freeze
|
|
18
17
|
|
|
@@ -26,7 +25,7 @@ module Gloo
|
|
|
26
25
|
#
|
|
27
26
|
def self.invoke( engine, target, arg_tokens )
|
|
28
27
|
if target.nil? || target.to_s.strip.empty?
|
|
29
|
-
engine.
|
|
28
|
+
engine.syntax_err NO_TARGET_ERR
|
|
30
29
|
return nil
|
|
31
30
|
end
|
|
32
31
|
|
|
@@ -49,7 +48,7 @@ module Gloo
|
|
|
49
48
|
func = pn.resolve
|
|
50
49
|
|
|
51
50
|
unless func
|
|
52
|
-
engine.err
|
|
51
|
+
engine.err Gloo::Core::NotFound.object( target )
|
|
53
52
|
return nil
|
|
54
53
|
end
|
|
55
54
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Author:: Eric Crane (mailto:eric.crane@mac.com)
|
|
2
|
+
# Copyright:: Copyright (c) 2026 Eric Crane. All rights reserved.
|
|
3
|
+
#
|
|
4
|
+
# The one wording for "not found" errors, so every command reports
|
|
5
|
+
# a missing object, file, folder or verb the same way.
|
|
6
|
+
#
|
|
7
|
+
|
|
8
|
+
module Gloo
|
|
9
|
+
module Core
|
|
10
|
+
module NotFound
|
|
11
|
+
|
|
12
|
+
#
|
|
13
|
+
# An object path that doesn't resolve, shown as written.
|
|
14
|
+
#
|
|
15
|
+
def self.object( path )
|
|
16
|
+
return "Object '#{path}' was not found."
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
#
|
|
20
|
+
# A file that doesn't exist.
|
|
21
|
+
#
|
|
22
|
+
def self.file( name )
|
|
23
|
+
return "File '#{name}' was not found."
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
#
|
|
27
|
+
# A folder that doesn't exist.
|
|
28
|
+
#
|
|
29
|
+
def self.folder( name )
|
|
30
|
+
return "Folder '#{name}' was not found."
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
#
|
|
34
|
+
# A verb that isn't in the dictionary.
|
|
35
|
+
#
|
|
36
|
+
def self.verb( name )
|
|
37
|
+
return "Verb '#{name}' was not found."
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
data/lib/gloo/core/obj.rb
CHANGED
|
@@ -241,7 +241,7 @@ module Gloo
|
|
|
241
241
|
#
|
|
242
242
|
def contains_child?( name )
|
|
243
243
|
@children.each do |o|
|
|
244
|
-
return true if name.downcase == o.name.downcase
|
|
244
|
+
return true if name.to_s.downcase == o.name.downcase
|
|
245
245
|
end
|
|
246
246
|
return false
|
|
247
247
|
end
|
|
@@ -261,7 +261,7 @@ module Gloo
|
|
|
261
261
|
if is_alias?
|
|
262
262
|
ln = Gloo::Core::Pn.new( @engine, self.value )
|
|
263
263
|
redirect = ln.resolve
|
|
264
|
-
return redirect
|
|
264
|
+
return redirect&.find_child( name )
|
|
265
265
|
end
|
|
266
266
|
return nil
|
|
267
267
|
end
|
|
@@ -349,6 +349,29 @@ module Gloo
|
|
|
349
349
|
return msgs.include?( msg.strip.downcase )
|
|
350
350
|
end
|
|
351
351
|
|
|
352
|
+
#
|
|
353
|
+
# Run a step of a message that can fail on ordinary bad input or
|
|
354
|
+
# the outside world (invalid JSON, an unknown host, a wrong key).
|
|
355
|
+
# Returns true if the block ran. If it raises one of the given
|
|
356
|
+
# exception classes, report it as a gloo error ("Could not
|
|
357
|
+
# {action}: {reason}"), put false into it (unless set_it is
|
|
358
|
+
# false, for an action that doesn't set it), and return false.
|
|
359
|
+
# Any other exception is left alone, so a real bug still shows
|
|
360
|
+
# up as one.
|
|
361
|
+
#
|
|
362
|
+
# return false unless attempt( 'parse the JSON', JSON::ParserError ) do
|
|
363
|
+
# json = JSON.parse( value )
|
|
364
|
+
# end
|
|
365
|
+
#
|
|
366
|
+
def attempt( action, *errors, set_it: true )
|
|
367
|
+
yield
|
|
368
|
+
return true
|
|
369
|
+
rescue *errors => e
|
|
370
|
+
@engine.err "Could not #{action}: #{e.message}"
|
|
371
|
+
@engine.heap.it.set_to false if set_it
|
|
372
|
+
return false
|
|
373
|
+
end
|
|
374
|
+
|
|
352
375
|
#
|
|
353
376
|
# Sent this object the given message.
|
|
354
377
|
#
|
|
@@ -356,7 +379,9 @@ module Gloo
|
|
|
356
379
|
@params = params
|
|
357
380
|
return self.dispatch msg if self.can_receive_message? msg
|
|
358
381
|
|
|
382
|
+
# The message never ran, so there is no result.
|
|
359
383
|
@engine.err "Object #{self.name} cannot receive message #{msg}"
|
|
384
|
+
@engine.heap.it.set_to false
|
|
360
385
|
return false
|
|
361
386
|
end
|
|
362
387
|
|
|
@@ -370,6 +395,7 @@ module Gloo
|
|
|
370
395
|
return true
|
|
371
396
|
else
|
|
372
397
|
@engine.err "Message #{msg} not implemented"
|
|
398
|
+
@engine.heap.it.set_to false
|
|
373
399
|
return false
|
|
374
400
|
end
|
|
375
401
|
end
|
|
@@ -420,7 +446,8 @@ module Gloo
|
|
|
420
446
|
end
|
|
421
447
|
|
|
422
448
|
#
|
|
423
|
-
# Check to see if the value is blank.
|
|
449
|
+
# Check to see if the simple value is blank.
|
|
450
|
+
# Children aren't considered; use contains? for those.
|
|
424
451
|
#
|
|
425
452
|
def msg_blank?
|
|
426
453
|
val_blank = value.blank?
|
data/lib/gloo/core/op.rb
CHANGED
|
@@ -15,6 +15,7 @@ module Gloo
|
|
|
15
15
|
def self.op?( token )
|
|
16
16
|
return [
|
|
17
17
|
Gloo::Expr::OpPlus::SYMBOL,
|
|
18
|
+
Gloo::Expr::OpPlus::ALT_SYMBOL,
|
|
18
19
|
Gloo::Expr::OpMinus::SYMBOL,
|
|
19
20
|
Gloo::Expr::OpMult::SYMBOL,
|
|
20
21
|
Gloo::Expr::OpDiv::SYMBOL,
|
|
@@ -34,6 +35,7 @@ module Gloo
|
|
|
34
35
|
def self.create_op( token )
|
|
35
36
|
case token
|
|
36
37
|
when Gloo::Expr::OpPlus::SYMBOL then Gloo::Expr::OpPlus.new
|
|
38
|
+
when Gloo::Expr::OpPlus::ALT_SYMBOL then Gloo::Expr::OpPlus.new
|
|
37
39
|
when Gloo::Expr::OpMinus::SYMBOL then Gloo::Expr::OpMinus.new
|
|
38
40
|
when Gloo::Expr::OpMult::SYMBOL then Gloo::Expr::OpMult.new
|
|
39
41
|
when Gloo::Expr::OpDiv::SYMBOL then Gloo::Expr::OpDiv.new
|
data/lib/gloo/core/parser.rb
CHANGED
data/lib/gloo/core/pn.rb
CHANGED
|
@@ -14,6 +14,8 @@ module Gloo
|
|
|
14
14
|
ERROR = 'error'.freeze
|
|
15
15
|
CONTEXT = '@'.freeze
|
|
16
16
|
NAMED_COLORS = %w[red blue green white black yellow].freeze
|
|
17
|
+
IT_NOT_AN_OBJECT = "it isn't an object and can't be %s; %s.".freeze
|
|
18
|
+
IT_HINT = 'put it into an object first (put it into x)'.freeze
|
|
17
19
|
|
|
18
20
|
attr_reader :src, :elements
|
|
19
21
|
|
|
@@ -61,6 +63,19 @@ module Gloo
|
|
|
61
63
|
return @src.downcase == IT
|
|
62
64
|
end
|
|
63
65
|
|
|
66
|
+
#
|
|
67
|
+
# it is a read-only result value, not an object, so it can't
|
|
68
|
+
# be the target of a verb or message. If the path refers to it,
|
|
69
|
+
# report that (what it can't be: 'sent messages', 'run', ...,
|
|
70
|
+
# and what to do instead) and return true.
|
|
71
|
+
#
|
|
72
|
+
def it_target_err?( what, hint = IT_HINT )
|
|
73
|
+
return false unless self.it?
|
|
74
|
+
|
|
75
|
+
@engine.err format( IT_NOT_AN_OBJECT, what, hint )
|
|
76
|
+
return true
|
|
77
|
+
end
|
|
78
|
+
|
|
64
79
|
#
|
|
65
80
|
# Does the pathname reference refer to error?
|
|
66
81
|
#
|
|
@@ -141,19 +156,29 @@ module Gloo
|
|
|
141
156
|
self.set_to( "#{@engine.heap.context}#{@src[1..-1]}" )
|
|
142
157
|
end
|
|
143
158
|
|
|
159
|
+
#
|
|
160
|
+
# Expand any here (^) or context (@) reference into the
|
|
161
|
+
# full path, so the lookup below starts from the heap root.
|
|
162
|
+
#
|
|
163
|
+
def expand_refs
|
|
164
|
+
Here.expand_here( @engine, self ) if Here.includes_here_ref?( @elements )
|
|
165
|
+
expand_context if self.includes_context?
|
|
166
|
+
end
|
|
167
|
+
|
|
144
168
|
#
|
|
145
169
|
# Get the parent that contains the object referenced.
|
|
146
170
|
#
|
|
171
|
+
# This is a lookup, not a use of the object: a missing element
|
|
172
|
+
# returns nil without reporting an error. A caller that needs the
|
|
173
|
+
# object reports its own error.
|
|
174
|
+
#
|
|
147
175
|
def get_parent
|
|
148
176
|
o = @engine.heap.root
|
|
149
177
|
|
|
150
178
|
if self.includes_path?
|
|
151
179
|
@elements[ 0..-2 ].each do |e|
|
|
152
180
|
o = o.find_child( e )
|
|
153
|
-
if o.nil?
|
|
154
|
-
@engine.err "Object '#{e}' was not found."
|
|
155
|
-
return nil
|
|
156
|
-
end
|
|
181
|
+
return nil if o.nil?
|
|
157
182
|
end
|
|
158
183
|
end
|
|
159
184
|
|
|
@@ -162,12 +187,14 @@ module Gloo
|
|
|
162
187
|
|
|
163
188
|
#
|
|
164
189
|
# Does the object at the path exist?
|
|
190
|
+
# A question, so a missing object is an answer, not an error.
|
|
165
191
|
#
|
|
166
192
|
def exists?
|
|
167
193
|
return true if self.root?
|
|
168
194
|
return true if self.it?
|
|
169
195
|
return true if self.error?
|
|
170
196
|
|
|
197
|
+
expand_refs
|
|
171
198
|
parent = self.get_parent
|
|
172
199
|
return false unless parent
|
|
173
200
|
|
|
@@ -186,6 +213,7 @@ module Gloo
|
|
|
186
213
|
#
|
|
187
214
|
# Resolve the pathname reference.
|
|
188
215
|
# Find the object referenced or return nil if it is not found.
|
|
216
|
+
# Like get_parent, a missing object is not reported here.
|
|
189
217
|
#
|
|
190
218
|
def resolve
|
|
191
219
|
return @engine.heap.root if self.root?
|
|
@@ -194,14 +222,7 @@ module Gloo
|
|
|
194
222
|
return Gloo::Core::GlooSystem.new(
|
|
195
223
|
@engine, self ) if self.gloo_sys?
|
|
196
224
|
|
|
197
|
-
|
|
198
|
-
Here.expand_here( @engine, self )
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
if self.includes_context?
|
|
202
|
-
expand_context
|
|
203
|
-
end
|
|
204
|
-
|
|
225
|
+
expand_refs
|
|
205
226
|
parent = self.get_parent
|
|
206
227
|
return nil unless parent
|
|
207
228
|
|