gloo 7.1 → 7.1.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/docs/application.md +2 -0
- data/docs/iterators.md +24 -0
- data/docs/language_syntax.md +1 -1
- data/docs/plugins.md +1 -1
- data/lib/VERSION +1 -1
- data/lib/VERSION_NOTES +7 -0
- data/lib/gloo/app/log.rb +2 -2
- data/lib/gloo/core/invoker.rb +20 -7
- data/lib/gloo/core/verb.rb +14 -3
- data/lib/gloo/exec/exec_env.rb +6 -3
- data/lib/gloo/exec/script.rb +7 -0
- data/lib/gloo/objs/basic/script.rb +9 -0
- data/lib/gloo/objs/ctrl/each.rb +4 -2
- data/lib/gloo/objs/ctrl/each_child.rb +1 -1
- data/lib/gloo/objs/ctrl/each_dir.rb +1 -1
- data/lib/gloo/objs/ctrl/each_file.rb +1 -1
- data/lib/gloo/objs/ctrl/each_line.rb +1 -1
- data/lib/gloo/objs/ctrl/each_word.rb +1 -1
- data/lib/gloo/objs/ctrl/repeat.rb +5 -3
- data/lib/gloo/objs/str_utils/cipher.rb +10 -2
- data/lib/gloo/persist/declaration_ledger.rb +6 -6
- data/lib/gloo/persist/file_loader.rb +1 -1
- data/lib/gloo/persist/persist_man.rb +1 -1
- data/lib/gloo/verbs/break.rb +7 -2
- data/lib/gloo/verbs/eval.rb +5 -4
- data/lib/gloo/verbs/invoke.rb +7 -6
- data/lib/gloo/verbs/log.rb +8 -4
- data/lib/gloo/verbs/show.rb +15 -10
- data/test.gloo/lang/exceptions.test.gloo +22 -0
- data/test.gloo/objs/cipher.test.gloo +27 -0
- data/test.gloo/objs/int.test.gloo +6 -3
- data/test.gloo/verbs/break.test.gloo +66 -0
- data/test.gloo/verbs/eval.test.gloo +8 -0
- data/test.gloo/verbs/invoke.test.gloo +50 -0
- data/test.gloo/verbs/log.test.gloo +16 -0
- data/test.gloo/verbs/show.test.gloo +32 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d25bb60fb25bb1b42e44eeac62b0ec3bae86a9f3770c696caf4ab39b7f1c9784
|
|
4
|
+
data.tar.gz: 260731b8d5f1cd1c6f3718be0d26f17d39139795e7e5c13ff8908a4fc3f1c453
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3729efcd7dcfa5964a408fb28750bd2ac7caea3af323b1ea8c8b75503446f51805db7f20b05bfd705a9ac020bd06fe6fba5135013137e937f7dd6f499e134f40
|
|
7
|
+
data.tar.gz: d69918fdaf8ab491ac5bc21cd7a838048ef4eaf2299d8a867eea017b14c0f5ca3a314e7106e6fc0bf00fad8e949550efaae897121d8ae11de4d964a1a0231218
|
data/docs/application.md
CHANGED
|
@@ -158,6 +158,8 @@ Gloo writes to the `gloo.log` and `error.log` files as well as to the console.
|
|
|
158
158
|
|
|
159
159
|
Debug messages are written to the log only, but other messages are also written to the console unless the application is running in quiet mode.
|
|
160
160
|
|
|
161
|
+
On the console, errors and warnings go to stderr, while a script's own output (`show`) goes to stdout. So the two can be redirected separately: `gloo my_script.gloo 2>/dev/null` shows just the script's output.
|
|
162
|
+
|
|
161
163
|
Only error and warning level messages are written to the error log.
|
|
162
164
|
|
|
163
165
|
In test mode (`--test`), errors and warnings go to the log files only, not the console, so the test output is just the test results. An error a test expects (or one from a test file that deliberately contains a problem) is still in `error.log`. The summary at the end of a test run says how many errors and warnings were logged, and by which tests, leaving out those a test expects (`expect_errors`). See Testing.
|
data/docs/iterators.md
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
- Each Line
|
|
10
10
|
- Each Word
|
|
11
11
|
- Repeat
|
|
12
|
+
- Ending a Loop Early
|
|
12
13
|
|
|
13
14
|
## Each
|
|
14
15
|
|
|
@@ -328,3 +329,26 @@ repeat [can] :
|
|
|
328
329
|
```
|
|
329
330
|
|
|
330
331
|
See also: Object Base.
|
|
332
|
+
|
|
333
|
+
## Ending a Loop Early
|
|
334
|
+
|
|
335
|
+
`break` in a loop's `do` script ends the whole loop, not just that pass. This works for `repeat` and every kind of `each`. The script that ran the loop carries on with its next line:
|
|
336
|
+
|
|
337
|
+
```gloo
|
|
338
|
+
early [can] :
|
|
339
|
+
on_load [script] :
|
|
340
|
+
tell early.r to run
|
|
341
|
+
show 'after the loop'
|
|
342
|
+
r [repeat] :
|
|
343
|
+
times [int] : 5
|
|
344
|
+
index [int] :
|
|
345
|
+
do [script] :
|
|
346
|
+
if early.r.index = 2 then break
|
|
347
|
+
show early.r.index
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
This shows `0`, `1`, then `after the loop`.
|
|
351
|
+
|
|
352
|
+
`break` ends the script it's written in. So in nested loops, a `break` in the inner loop's `do` ends only the inner loop, and a `break` in a script the `do` script runs (with `run`, or a function it invokes) ends only that script, not the loop. An `each` with `group_by` still runs `on_group_end` for the group that was open when the loop ended.
|
|
353
|
+
|
|
354
|
+
See also: Break.
|
data/docs/language_syntax.md
CHANGED
|
@@ -75,7 +75,7 @@ Gloo makes a best guess and keeps going when it can, but it never does so silent
|
|
|
75
75
|
- **Warning** — the command was done, but probably not the way it was meant: a value that isn't really of its type, used as a best guess (`'x' is not an integer; using 0.`), or indentation that doesn't line up. A warning is only logged; it isn't an error.
|
|
76
76
|
- **Just a result** — a question whose answer is "no" is not an error at all: `exists?`, `contains?`, `substring?`, and `index_of` (which gives -1) answer in `it`.
|
|
77
77
|
|
|
78
|
-
Errors are logged with where they happened in front of the message: the file and line while a file is loading (`app.gloo:12: Unknown type 'strng'; using untyped.`),
|
|
78
|
+
Errors and warnings are logged with where they happened in front of the message: the file and line while a file is loading (`app.gloo:12: Unknown type 'strng'; using untyped.`), even when a script started the load; otherwise the script and its line (`app.on_load, line 3: Object 'app.nme' was not found.`). On the console both go to stderr, apart from a script's own output. "Not found" errors always read the same way: `Object 'x' was not found.`, `File 'x' was not found.`, `Folder 'x' was not found.`, `Verb 'x' was not found.`
|
|
79
79
|
|
|
80
80
|
What happens after an error:
|
|
81
81
|
|
data/docs/plugins.md
CHANGED
|
@@ -372,7 +372,7 @@ Register it from `stats_ext.rb` with `callback.register_obj( Stats )`.
|
|
|
372
372
|
|
|
373
373
|
### Custom `each` iterators
|
|
374
374
|
|
|
375
|
-
An extension's object can also plug into the built-in `each` verb by providing its own iterator. `extensions/git/src/each_repo.rb` shows the shape: a plain Ruby class (not a `Gloo::Core::Obj`) with `self.use_for?( iterator_obj )` — which returns `true` when the `each` loop's iterator object looks like a match (here, when it has a `repo` child) — and a `run` method that walks whatever it's iterating over, setting the loop's child value and calling `@iterator_obj.run_do` for each item. See the git extension for the full pattern; this hook only makes sense for extensions that also register a matching object type (here, `git_repo`).
|
|
375
|
+
An extension's object can also plug into the built-in `each` verb by providing its own iterator. `extensions/git/src/each_repo.rb` shows the shape: a plain Ruby class (not a `Gloo::Core::Obj`) with `self.use_for?( iterator_obj )` — which returns `true` when the `each` loop's iterator object looks like a match (here, when it has a `repo` child) — and a `run` method that walks whatever it's iterating over, setting the loop's child value and calling `@iterator_obj.run_do` for each item. `run_do` returns true when a `break` in the `do` script ended it, so stop the walk then: `break if @iterator_obj.run_do`. See the git extension for the full pattern; this hook only makes sense for extensions that also register a matching object type (here, `git_repo`).
|
|
376
376
|
|
|
377
377
|
### Documenting the extension
|
|
378
378
|
|
data/lib/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
7.1
|
|
1
|
+
7.1.1
|
data/lib/VERSION_NOTES
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
7.1.1 - 2026.10.26
|
|
2
|
+
- Fixes issues with the value in it after a failed command.
|
|
3
|
+
- Warnings go to stderr, like errors.
|
|
4
|
+
- Break in a loop's do script now ends the whole loop.
|
|
5
|
+
- Cipher doc and test updated.
|
|
6
|
+
|
|
7
|
+
|
|
1
8
|
7.1 - 2026.10.23
|
|
2
9
|
- Loading an empty gloo file is now allowed and only gives a warning.
|
|
3
10
|
- Adds configuration setting for tmp directory.
|
data/lib/gloo/app/log.rb
CHANGED
|
@@ -168,13 +168,13 @@ module Gloo
|
|
|
168
168
|
|
|
169
169
|
#
|
|
170
170
|
# Write a warning message to the log.
|
|
171
|
-
# Also write to the console unless quiet.
|
|
171
|
+
# Also write to the console (on stderr, like errors) unless quiet.
|
|
172
172
|
#
|
|
173
173
|
def warn( msg )
|
|
174
174
|
@warning_count += 1
|
|
175
175
|
@logger.warn msg
|
|
176
176
|
@error.warn msg
|
|
177
|
-
puts @theme.warn( msg ) if errors_to_console?
|
|
177
|
+
$stderr.puts @theme.warn( msg ) if errors_to_console?
|
|
178
178
|
end
|
|
179
179
|
|
|
180
180
|
#
|
data/lib/gloo/core/invoker.rb
CHANGED
|
@@ -18,26 +18,39 @@ module Gloo
|
|
|
18
18
|
#
|
|
19
19
|
# Resolve, validate and invoke the function at the given
|
|
20
20
|
# target path, with the given raw (unevaluated) arg tokens.
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
# unreliable result when that happens).
|
|
21
|
+
# For any failure, reports an error, puts false in `it` and
|
|
22
|
+
# returns nil - before the call (see failed), or inside the
|
|
23
|
+
# function itself (see Function#invoke).
|
|
25
24
|
#
|
|
26
25
|
def self.invoke( engine, target, arg_tokens )
|
|
27
26
|
if target.nil? || target.to_s.strip.empty?
|
|
28
27
|
engine.syntax_err NO_TARGET_ERR
|
|
29
|
-
return
|
|
28
|
+
return failed( engine )
|
|
30
29
|
end
|
|
31
30
|
|
|
32
31
|
func = resolve_function( engine, target )
|
|
33
|
-
return
|
|
32
|
+
return failed( engine ) unless func
|
|
34
33
|
|
|
34
|
+
# An arg that couldn't be worked out was already reported, but
|
|
35
|
+
# the function's own error tracking would clear it if the call
|
|
36
|
+
# went ahead.
|
|
37
|
+
errors_before = engine.heap.error.error_count
|
|
35
38
|
args = evaluate_arg_tokens( engine, arg_tokens )
|
|
36
|
-
return
|
|
39
|
+
return failed( engine ) if engine.heap.error.error_count > errors_before
|
|
40
|
+
return failed( engine ) unless params_count_ok?( engine, func, args )
|
|
37
41
|
|
|
38
42
|
return invoke_function( engine, func, args )
|
|
39
43
|
end
|
|
40
44
|
|
|
45
|
+
#
|
|
46
|
+
# The function couldn't be called (the problem was already
|
|
47
|
+
# reported): put false in `it`, and return nil.
|
|
48
|
+
#
|
|
49
|
+
def self.failed( engine )
|
|
50
|
+
engine.heap.it.set_to false
|
|
51
|
+
return nil
|
|
52
|
+
end
|
|
53
|
+
|
|
41
54
|
#
|
|
42
55
|
# Resolve the function object at the target path. Reports an
|
|
43
56
|
# error and returns nil if the path doesn't resolve to
|
data/lib/gloo/core/verb.rb
CHANGED
|
@@ -94,15 +94,26 @@ module Gloo
|
|
|
94
94
|
def take_evaluated_arg
|
|
95
95
|
return true unless @params
|
|
96
96
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
return false if @engine.heap.error.error_count > errors_before
|
|
97
|
+
value, ok = evaluate_expr( @params.tokens )
|
|
98
|
+
return false unless ok
|
|
100
99
|
|
|
101
100
|
@tokens = @tokens.with_token( value.to_s )
|
|
102
101
|
@params = nil
|
|
103
102
|
return true
|
|
104
103
|
end
|
|
105
104
|
|
|
105
|
+
#
|
|
106
|
+
# Work out an expression from the given tokens. Returns the value
|
|
107
|
+
# and whether it worked: false if the expression reported a
|
|
108
|
+
# problem (already reported), in which case the value is whatever
|
|
109
|
+
# it had worked out so far.
|
|
110
|
+
#
|
|
111
|
+
def evaluate_expr( tokens )
|
|
112
|
+
errors_before = @engine.heap.error.error_count
|
|
113
|
+
value = Gloo::Expr::Expression.new( @engine, tokens ).evaluate
|
|
114
|
+
return value, @engine.heap.error.error_count == errors_before
|
|
115
|
+
end
|
|
116
|
+
|
|
106
117
|
#
|
|
107
118
|
# Run the verb.
|
|
108
119
|
#
|
data/lib/gloo/exec/exec_env.rb
CHANGED
|
@@ -35,13 +35,16 @@ module Gloo
|
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
#
|
|
38
|
-
# Where the line now running is, for error messages: the
|
|
39
|
-
#
|
|
38
|
+
# Where the line now running is, for error messages: the file and
|
|
39
|
+
# line being loaded, or the running script and its line. While a
|
|
40
|
+
# file is being read, a problem is in that file, even when a
|
|
41
|
+
# script started the load.
|
|
40
42
|
#
|
|
41
43
|
def current_location
|
|
44
|
+
return @load_location if @load_location
|
|
42
45
|
return @running_script.location if @running_script
|
|
43
46
|
|
|
44
|
-
return
|
|
47
|
+
return nil
|
|
45
48
|
end
|
|
46
49
|
|
|
47
50
|
#
|
data/lib/gloo/exec/script.rb
CHANGED
|
@@ -115,6 +115,15 @@ module Gloo
|
|
|
115
115
|
def msg_run
|
|
116
116
|
s = Gloo::Exec::Script.new( @engine, self )
|
|
117
117
|
s.run
|
|
118
|
+
@broke_out = s.broke_out?
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
#
|
|
122
|
+
# Did a break end this script's last run? A loop uses this to
|
|
123
|
+
# stop when its do script breaks.
|
|
124
|
+
#
|
|
125
|
+
def broke_out?
|
|
126
|
+
return @broke_out == true
|
|
118
127
|
end
|
|
119
128
|
|
|
120
129
|
# ---------------------------------------------------------------------
|
data/lib/gloo/objs/ctrl/each.rb
CHANGED
|
@@ -43,13 +43,15 @@ module Gloo
|
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
#
|
|
46
|
-
# Run the do script once.
|
|
46
|
+
# Run the do script once. Returns true if a break in it ended the
|
|
47
|
+
# run, so the loop should stop.
|
|
47
48
|
#
|
|
48
49
|
def run_do
|
|
49
50
|
o = find_child DO
|
|
50
|
-
return unless o
|
|
51
|
+
return false unless o
|
|
51
52
|
|
|
52
53
|
Gloo::Exec::Dispatch.message( @engine, 'run', o )
|
|
54
|
+
return o.respond_to?( :broke_out? ) && o.broke_out?
|
|
53
55
|
end
|
|
54
56
|
|
|
55
57
|
|
|
@@ -37,13 +37,15 @@ module Gloo
|
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
#
|
|
40
|
-
# Run the do script once.
|
|
40
|
+
# Run the do script once. Returns true if a break in it ended the
|
|
41
|
+
# run, so the loop should stop.
|
|
41
42
|
#
|
|
42
43
|
def run_do
|
|
43
44
|
o = find_child DO
|
|
44
|
-
return unless o
|
|
45
|
+
return false unless o
|
|
45
46
|
|
|
46
47
|
Gloo::Exec::Dispatch.message( @engine, 'run', o )
|
|
48
|
+
return o.respond_to?( :broke_out? ) && o.broke_out?
|
|
47
49
|
end
|
|
48
50
|
|
|
49
51
|
#
|
|
@@ -98,7 +100,7 @@ module Gloo
|
|
|
98
100
|
def msg_run
|
|
99
101
|
times.times do |index|
|
|
100
102
|
set_index index
|
|
101
|
-
run_do
|
|
103
|
+
break if run_do
|
|
102
104
|
end
|
|
103
105
|
end
|
|
104
106
|
|
|
@@ -209,7 +209,8 @@ module Gloo
|
|
|
209
209
|
{
|
|
210
210
|
:name => KEYWORD,
|
|
211
211
|
:shortcut => KEYWORD_SHORT,
|
|
212
|
-
:description => 'Tool to encrypt and decrypt data
|
|
212
|
+
:description => 'Tool to encrypt and decrypt data, with ' \
|
|
213
|
+
'AES-256-CBC.',
|
|
213
214
|
:children => [
|
|
214
215
|
'key (string) — The encryption key. Base64 encoded.',
|
|
215
216
|
'init_vector (string) — The initialization vector. Base64 encoded.',
|
|
@@ -218,8 +219,15 @@ module Gloo
|
|
|
218
219
|
:messages => [
|
|
219
220
|
'generate_keys — Generate an encryption key and initialization vector. The keys are base64 encoded.',
|
|
220
221
|
'encrypt — Encrypt the data. The data child is changed. It will have the encrypted data.',
|
|
221
|
-
'decrypt — Decrypt the data. The data child is changed. It will have the decrypted data.'
|
|
222
|
+
'decrypt — Decrypt the data. The data child is changed. It will have the decrypted data. With the wrong key or init_vector, decrypt usually reports an error and it is false; but not always (see notes).'
|
|
222
223
|
],
|
|
224
|
+
:notes => 'AES-CBC can\'t always tell that a key is wrong. A ' \
|
|
225
|
+
'wrong key usually leaves the decrypted data with padding ' \
|
|
226
|
+
'that doesn\'t check out, which is reported as an error. But ' \
|
|
227
|
+
'about 1 time in 256 the padding happens to look right: then ' \
|
|
228
|
+
'decrypt reports nothing, and the data is garbage rather than ' \
|
|
229
|
+
'the original. To be sure the key is right, keep a value you ' \
|
|
230
|
+
'can check, such as encrypting a known word alongside the data.',
|
|
223
231
|
:examples => <<~EXAMPLES.strip
|
|
224
232
|
#
|
|
225
233
|
# Encrypt and Decrypt a string.
|
|
@@ -14,11 +14,10 @@ module Gloo
|
|
|
14
14
|
attr_reader :roots
|
|
15
15
|
|
|
16
16
|
#
|
|
17
|
-
# Set up a ledger for
|
|
17
|
+
# Set up a ledger for one file's load.
|
|
18
18
|
#
|
|
19
|
-
def initialize( engine
|
|
19
|
+
def initialize( engine )
|
|
20
20
|
@engine = engine
|
|
21
|
-
@pn = pn
|
|
22
21
|
@roots = []
|
|
23
22
|
@mine = {}.compare_by_identity
|
|
24
23
|
end
|
|
@@ -43,15 +42,16 @@ module Gloo
|
|
|
43
42
|
# `value`. If another file created it and the values differ, the
|
|
44
43
|
# new value is being ignored -- warn so it isn't a surprise. A
|
|
45
44
|
# duplicate name inside one file is left alone (a documented
|
|
46
|
-
# "first one wins").
|
|
45
|
+
# "first one wins"). The warning's location (this file and line)
|
|
46
|
+
# comes from the load.
|
|
47
47
|
#
|
|
48
48
|
def clash( prior, name, value )
|
|
49
49
|
return if @mine.key?( prior )
|
|
50
50
|
return if value.nil? || value.to_s.strip.empty?
|
|
51
51
|
return if prior.value.to_s == value.to_s
|
|
52
52
|
|
|
53
|
-
@engine.
|
|
54
|
-
"
|
|
53
|
+
@engine.warn(
|
|
54
|
+
"'#{name}' is already declared in another loaded file with a " \
|
|
55
55
|
"different value (#{prior.value.inspect} kept, #{value.inspect} ignored)."
|
|
56
56
|
)
|
|
57
57
|
end
|
|
@@ -53,7 +53,7 @@ module Gloo
|
|
|
53
53
|
@trivia = Gloo::Persist::TriviaBuffer.new
|
|
54
54
|
@body = Gloo::Persist::ScriptBodyCollector.new
|
|
55
55
|
@shorthand = Gloo::Persist::ShorthandExpander.new( engine )
|
|
56
|
-
@ledger = Gloo::Persist::DeclarationLedger.new( engine
|
|
56
|
+
@ledger = Gloo::Persist::DeclarationLedger.new( engine )
|
|
57
57
|
@in_block = false
|
|
58
58
|
@block_value = ''
|
|
59
59
|
@body_started = false
|
|
@@ -396,7 +396,7 @@ module Gloo
|
|
|
396
396
|
#
|
|
397
397
|
def warn_if_dirty( fs )
|
|
398
398
|
saver = Gloo::Persist::FileSaver.new( @engine, fs.pn, fs.obj, fs.source_doc )
|
|
399
|
-
@engine.
|
|
399
|
+
@engine.warn "#{RELOAD_DIRTY_WARNING}#{fs.pn}" if saver.dirty?
|
|
400
400
|
end
|
|
401
401
|
|
|
402
402
|
end
|
data/lib/gloo/verbs/break.rb
CHANGED
|
@@ -51,11 +51,16 @@ module Gloo
|
|
|
51
51
|
{
|
|
52
52
|
:name => KEYWORD,
|
|
53
53
|
:shortcut => KEYWORD_SHORT,
|
|
54
|
-
:description => 'Break out of the current script, stop running.'
|
|
54
|
+
:description => 'Break out of the current script, stop running. ' \
|
|
55
|
+
'If that script is the do script of a loop (repeat, or any ' \
|
|
56
|
+
'kind of each), the whole loop ends.',
|
|
55
57
|
:syntax => [ 'break' ],
|
|
56
58
|
:result => 'No more commands are executed in the currently ' \
|
|
57
59
|
'running script. If there are other scripts in the stack, ' \
|
|
58
|
-
'they will resume normally.',
|
|
60
|
+
'they will resume normally. In a loop\'s do script, the loop ' \
|
|
61
|
+
'ends too and runs no more passes; the script that ran the ' \
|
|
62
|
+
'loop carries on. A break in a script the do script runs ' \
|
|
63
|
+
'(eg. with run) ends only that script, not the loop.',
|
|
59
64
|
:examples => <<~EXAMPLES.strip
|
|
60
65
|
#
|
|
61
66
|
# Break out of a script.
|
data/lib/gloo/verbs/eval.rb
CHANGED
|
@@ -30,9 +30,8 @@ module Gloo
|
|
|
30
30
|
#
|
|
31
31
|
def run
|
|
32
32
|
if @tokens.token_count > 1
|
|
33
|
-
|
|
34
|
-
result
|
|
35
|
-
@engine.heap.it.set_to result
|
|
33
|
+
result, ok = evaluate_expr( @tokens.params )
|
|
34
|
+
@engine.heap.it.set_to( ok ? result : false )
|
|
36
35
|
else
|
|
37
36
|
@engine.heap.it.set_to true
|
|
38
37
|
end
|
|
@@ -61,7 +60,9 @@ module Gloo
|
|
|
61
60
|
'{expression} — The expression that will be evaluated. The expression is optional. If not provided, eval will result in true.'
|
|
62
61
|
],
|
|
63
62
|
:result => 'The result of the expression evaluation is put ' \
|
|
64
|
-
'into it. No other action is performed.'
|
|
63
|
+
'into it. No other action is performed. If the expression ' \
|
|
64
|
+
'fails (eg. it names a missing object), the error is ' \
|
|
65
|
+
'reported and it is false.',
|
|
65
66
|
:examples => <<~EXAMPLES.strip,
|
|
66
67
|
> eval "me"
|
|
67
68
|
> eval "hello " "world"
|
data/lib/gloo/verbs/invoke.rb
CHANGED
|
@@ -60,7 +60,8 @@ module Gloo
|
|
|
60
60
|
'{path.to.function} — The function we want to invoke.',
|
|
61
61
|
'{params} — The list of parameters to the function.'
|
|
62
62
|
],
|
|
63
|
-
:result => 'The result of the function is put into it
|
|
63
|
+
:result => 'The result of the function is put into it; if ' \
|
|
64
|
+
'anything goes wrong, it is false. The ' \
|
|
64
65
|
'inline forms (invoke(...), ~>(...)) can be used anywhere ' \
|
|
65
66
|
'an expression is evaluated - show, put ... into, if, ' \
|
|
66
67
|
'unless, eval, and more - and evaluate to the result ' \
|
|
@@ -69,13 +70,13 @@ module Gloo
|
|
|
69
70
|
"#{Gloo::Core::Invoker::NO_TARGET_ERR} — No function reference was given.",
|
|
70
71
|
"Object '{path.to.function}' was not found. — The path doesn't resolve to any object.",
|
|
71
72
|
"#{Gloo::Core::Invoker::NOT_FUNCTION_ERR}{path.to.function} — The path resolves to an object that isn't a function.",
|
|
72
|
-
"#{Gloo::Core::Invoker::PARAM_COUNT_ERR}{path.to.function} (expected N, got N) — The number of params given doesn't match what the function declares."
|
|
73
|
+
"#{Gloo::Core::Invoker::PARAM_COUNT_ERR}{path.to.function} (expected N, got N) — The number of params given doesn't match what the function declares.",
|
|
74
|
+
"Object '{param}' was not found. — A param names an object that doesn't exist. The function isn't run."
|
|
73
75
|
],
|
|
74
76
|
:notes => 'If the function itself fails during invocation ' \
|
|
75
|
-
'(its on_invoke script hits an error), it is
|
|
76
|
-
'
|
|
77
|
-
'
|
|
78
|
-
'failure inside the function. ' \
|
|
77
|
+
'(its on_invoke script hits an error), it is false rather ' \
|
|
78
|
+
'than an unreliable result. The underlying error is ' \
|
|
79
|
+
'whatever was reported by the failure inside the function. ' \
|
|
79
80
|
'Inline call params are space-separated, same as the ' \
|
|
80
81
|
'standalone verb - each one is evaluated as a single ' \
|
|
81
82
|
'token (a literal or object reference), not a multi-token ' \
|
data/lib/gloo/verbs/log.rb
CHANGED
|
@@ -50,11 +50,12 @@ module Gloo
|
|
|
50
50
|
# Write to the specified logger.
|
|
51
51
|
#
|
|
52
52
|
def write_to_log
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
# A failed expression is still logged as far as it got, but it
|
|
54
|
+
# is false.
|
|
55
|
+
result, ok = evaluate_expr( @tokens.params )
|
|
55
56
|
level = log_level_specified( result )
|
|
56
57
|
@engine.log.write result, level
|
|
57
|
-
@engine.heap.it.set_to result
|
|
58
|
+
@engine.heap.it.set_to( ok ? result : false )
|
|
58
59
|
end
|
|
59
60
|
|
|
60
61
|
#
|
|
@@ -106,7 +107,10 @@ module Gloo
|
|
|
106
107
|
'not specified, the message is written as DEBUG level.'
|
|
107
108
|
],
|
|
108
109
|
:result => 'The message is written to the log at the ' \
|
|
109
|
-
'specified level. It will contain the log message.'
|
|
110
|
+
'specified level. It will contain the log message. If the ' \
|
|
111
|
+
'expression fails, the error is reported and what was ' \
|
|
112
|
+
'worked out so far is still logged, but it is false. A ' \
|
|
113
|
+
'level that can\'t be worked out falls back to debug.',
|
|
110
114
|
:notes => "Use `log clear` from the CLI to clear out both the " \
|
|
111
115
|
"standard log and the error log.\n\n" \
|
|
112
116
|
"> log clear",
|
data/lib/gloo/verbs/show.rb
CHANGED
|
@@ -16,10 +16,11 @@ module Gloo
|
|
|
16
16
|
#
|
|
17
17
|
def run
|
|
18
18
|
if @tokens.token_count > 1
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
# A failed expression is still shown as far as it got (a best
|
|
20
|
+
# guess, and handy when debugging), but it is false.
|
|
21
|
+
result, ok = evaluate_expr( @tokens.params )
|
|
21
22
|
@engine.log.show get_formatted_string( result )
|
|
22
|
-
@engine.heap.it.set_to result
|
|
23
|
+
@engine.heap.it.set_to( ok ? result : false )
|
|
23
24
|
else
|
|
24
25
|
@engine.log.show ''
|
|
25
26
|
end
|
|
@@ -46,14 +47,16 @@ module Gloo
|
|
|
46
47
|
private
|
|
47
48
|
|
|
48
49
|
#
|
|
49
|
-
# Get the formatted string
|
|
50
|
+
# Get the formatted string: in the color given in the params, if
|
|
51
|
+
# any. A color that can't be worked out (already reported) shows
|
|
52
|
+
# the string plain.
|
|
50
53
|
#
|
|
51
54
|
def get_formatted_string( str )
|
|
52
55
|
if @params&.token_count&.positive?
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
return @engine.platform.get_colorized_string( str,
|
|
56
|
+
val, ok = evaluate_expr( @params.tokens )
|
|
57
|
+
return str unless ok && val
|
|
58
|
+
|
|
59
|
+
return @engine.platform.get_colorized_string( str, val.to_sym )
|
|
57
60
|
end
|
|
58
61
|
return str
|
|
59
62
|
end
|
|
@@ -79,11 +82,13 @@ module Gloo
|
|
|
79
82
|
'{target} — The object that we want to see. The target ' \
|
|
80
83
|
'might be the path to an object, a literal or an ' \
|
|
81
84
|
'expression to be evaluated before being shown.',
|
|
82
|
-
'{color} — Optional color for the text.'
|
|
85
|
+
'{color} — Optional color for the text. If the color can\'t be worked out, the error is reported and the text is shown plain.'
|
|
83
86
|
],
|
|
84
87
|
:result => "The object's value is shown, or the literal, or " \
|
|
85
88
|
'the result of the evaluated expression is shown. It will ' \
|
|
86
|
-
"contain the object's value."
|
|
89
|
+
"contain the object's value. If the expression fails (eg. " \
|
|
90
|
+
'it names a missing object), the error is reported and ' \
|
|
91
|
+
'what was worked out so far is still shown, but it is false.',
|
|
87
92
|
:examples => <<~EXAMPLES.strip,
|
|
88
93
|
> show "me"
|
|
89
94
|
> show "hello " "world"
|
|
@@ -23,6 +23,18 @@ tests.lang.exceptions [can] :
|
|
|
23
23
|
kind [string] :
|
|
24
24
|
location [string] :
|
|
25
25
|
|
|
26
|
+
# A scratch file with a mistake in it, for the location of an error in
|
|
27
|
+
# a loaded file.
|
|
28
|
+
broken_file [file] :
|
|
29
|
+
broken_name [string] : /gloo_exceptions_location.gloo
|
|
30
|
+
broken_src [text] : BEGIN
|
|
31
|
+
broken_location_demo [can] :
|
|
32
|
+
count [int : 3
|
|
33
|
+
END
|
|
34
|
+
|
|
35
|
+
on_load [script] :
|
|
36
|
+
put $.gloo_tmp ^.broken_name into ^.broken_file
|
|
37
|
+
|
|
26
38
|
confirm_throw_fires_on_exception [test] :
|
|
27
39
|
description [string] : Confirm throw fires on_exception and populates exception_data, but does not fire on_error.
|
|
28
40
|
expect_errors [bool] : true
|
|
@@ -127,3 +139,13 @@ tests.lang.exceptions [can] :
|
|
|
127
139
|
eval ^^.num = 0
|
|
128
140
|
assert "expected the best guess, 0"
|
|
129
141
|
|
|
142
|
+
confirm_error_location_in_a_loaded_file [test] :
|
|
143
|
+
description [string] : an error in a file a script loads names that file and line, not the script.
|
|
144
|
+
expect_errors [bool] : true
|
|
145
|
+
on_test [script] :
|
|
146
|
+
tell ^^.broken_file to write ( ^^.broken_src )
|
|
147
|
+
load ( ^^.broken_file )
|
|
148
|
+
check ^^.error_data.location for ends_with? ( 'gloo_exceptions_location.gloo:2' )
|
|
149
|
+
assert "expected the location to be the loaded file and line"
|
|
150
|
+
tell broken_location_demo to unload
|
|
151
|
+
tell ^^.broken_file to delete
|
|
@@ -41,3 +41,30 @@ tests.objs.cipher [can] :
|
|
|
41
41
|
tell tests.objs.cipher.b to decrypt
|
|
42
42
|
eval tests.objs.cipher.b.data = ^^.plaintext
|
|
43
43
|
assert "expected the second cipher to decrypt with the shared key"
|
|
44
|
+
|
|
45
|
+
# Fixed values, so the results never vary: 'hello' encrypted with key
|
|
46
|
+
# ('k' x 32) and iv ('i' x 16), and a wrong key ('0' x 32) whose
|
|
47
|
+
# garbage fails the padding check. (AES-CBC can't always tell a key is
|
|
48
|
+
# wrong: about 1 wrong key in 256 gives garbage with no error.)
|
|
49
|
+
fixed_iv [string] : aWlpaWlpaWlpaWlpaWlpaQ==
|
|
50
|
+
hello_encrypted [string] : aRyXOIIhJzBhh+BthfFstw==
|
|
51
|
+
wrong_key [string] : MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA=
|
|
52
|
+
err_fired [bool] : false
|
|
53
|
+
|
|
54
|
+
on_error [script] :
|
|
55
|
+
put true into ^.err_fired
|
|
56
|
+
|
|
57
|
+
wrong_key_is_an_error [test] :
|
|
58
|
+
description [string] : decrypt with a wrong key that fails the padding check reports an error.
|
|
59
|
+
expect_errors [bool] : true
|
|
60
|
+
on_test [script] :
|
|
61
|
+
put false into ^^.err_fired
|
|
62
|
+
create tests.objs.cipher.w as cipher
|
|
63
|
+
put ^^.wrong_key into tests.objs.cipher.w.key
|
|
64
|
+
put ^^.fixed_iv into tests.objs.cipher.w.init_vector
|
|
65
|
+
put ^^.hello_encrypted into tests.objs.cipher.w.data
|
|
66
|
+
tell tests.objs.cipher.w to decrypt
|
|
67
|
+
eval it = false
|
|
68
|
+
assert "expected it to be false"
|
|
69
|
+
eval ^^.err_fired = true
|
|
70
|
+
assert "expected on_error to fire for the wrong key"
|
|
@@ -48,10 +48,13 @@ tests.objs.int [can] :
|
|
|
48
48
|
eval ^^.a <= 100
|
|
49
49
|
assert "expected it to be less than or equal to 100"
|
|
50
50
|
|
|
51
|
+
# A random value is only checked for its range: two random values
|
|
52
|
+
# are equal now and then, so 'not equal' would fail at random.
|
|
51
53
|
tell ^^.a to randomize (10000)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
eval ^^.a >= 0
|
|
55
|
+
assert "expected it to be greater than or equal to 0"
|
|
56
|
+
eval ^^.a < 10000
|
|
57
|
+
assert "expected it to be less than 10000"
|
|
55
58
|
|
|
56
59
|
format_int [test] :
|
|
57
60
|
description [string] : Format an integer
|
|
@@ -18,3 +18,69 @@ tests.verbs.break [can] :
|
|
|
18
18
|
tell ^^.s to run
|
|
19
19
|
eval ^^.b
|
|
20
20
|
assert "expected b to be true"
|
|
21
|
+
|
|
22
|
+
# Loops whose do script breaks on its second pass.
|
|
23
|
+
passes [int] : 0
|
|
24
|
+
after [int] : 0
|
|
25
|
+
|
|
26
|
+
rep [repeat] :
|
|
27
|
+
times [int] : 4
|
|
28
|
+
index [int] :
|
|
29
|
+
do [script] :
|
|
30
|
+
put tests.verbs.break.passes + 1 into tests.verbs.break.passes
|
|
31
|
+
if tests.verbs.break.passes = 2 then break
|
|
32
|
+
put tests.verbs.break.after + 1 into tests.verbs.break.after
|
|
33
|
+
|
|
34
|
+
kids [can] :
|
|
35
|
+
a [string] : a
|
|
36
|
+
b [string] : b
|
|
37
|
+
c [string] : c
|
|
38
|
+
|
|
39
|
+
each_kid [each] :
|
|
40
|
+
child [alias] :
|
|
41
|
+
in [alias] : tests.verbs.break.kids
|
|
42
|
+
do [script] :
|
|
43
|
+
put tests.verbs.break.passes + 1 into tests.verbs.break.passes
|
|
44
|
+
if tests.verbs.break.passes = 2 then break
|
|
45
|
+
put tests.verbs.break.after + 1 into tests.verbs.break.after
|
|
46
|
+
|
|
47
|
+
inner [repeat] :
|
|
48
|
+
times [int] : 3
|
|
49
|
+
index [int] :
|
|
50
|
+
do [script] :
|
|
51
|
+
if tests.verbs.break.inner.index = 1 then break
|
|
52
|
+
put tests.verbs.break.after + 1 into tests.verbs.break.after
|
|
53
|
+
|
|
54
|
+
outer [repeat] :
|
|
55
|
+
times [int] : 2
|
|
56
|
+
index [int] :
|
|
57
|
+
do [script] :
|
|
58
|
+
tell tests.verbs.break.inner to run
|
|
59
|
+
|
|
60
|
+
break_ends_a_repeat [test] :
|
|
61
|
+
description [string] : break in a repeat's do script ends the loop.
|
|
62
|
+
on_test [script] :
|
|
63
|
+
put 0 into ^^.passes
|
|
64
|
+
put 0 into ^^.after
|
|
65
|
+
tell ^^.rep to run
|
|
66
|
+
eval ^^.passes = 2
|
|
67
|
+
assert "expected the loop to stop on the pass that broke"
|
|
68
|
+
eval ^^.after = 1
|
|
69
|
+
assert "expected only the first pass to get past the break"
|
|
70
|
+
|
|
71
|
+
break_ends_an_each [test] :
|
|
72
|
+
description [string] : break in an each's do script ends the loop.
|
|
73
|
+
on_test [script] :
|
|
74
|
+
put 0 into ^^.passes
|
|
75
|
+
put 0 into ^^.after
|
|
76
|
+
tell ^^.each_kid to run
|
|
77
|
+
eval ^^.passes = 2
|
|
78
|
+
assert "expected the loop to stop on the pass that broke"
|
|
79
|
+
|
|
80
|
+
break_ends_only_the_inner_loop [test] :
|
|
81
|
+
description [string] : break in an inner loop ends that loop, not the outer one.
|
|
82
|
+
on_test [script] :
|
|
83
|
+
put 0 into ^^.after
|
|
84
|
+
tell ^^.outer to run
|
|
85
|
+
eval ^^.after = 2
|
|
86
|
+
assert "expected each of the 2 inner loops to get past its break once"
|
|
@@ -15,3 +15,11 @@ tests.verbs.eval [can] :
|
|
|
15
15
|
|
|
16
16
|
eval false
|
|
17
17
|
refute "expected false to be false"
|
|
18
|
+
|
|
19
|
+
failed_expression_puts_false_in_it [test] :
|
|
20
|
+
description [string] : eval of an expression that fails puts false in it.
|
|
21
|
+
expect_errors [bool] : true
|
|
22
|
+
on_test [script] :
|
|
23
|
+
eval 1 + no_such_obj_xyz
|
|
24
|
+
eval it = false
|
|
25
|
+
assert "expected it to be false after a failed expression"
|
|
@@ -90,3 +90,53 @@ tests.verbs.invoke [can] :
|
|
|
90
90
|
show invoke( tests.verbs.invoke.greet "Bob Smith" )
|
|
91
91
|
eval it = "Hi, Bob Smith"
|
|
92
92
|
assert "expected it to be 'Hi, Bob Smith'"
|
|
93
|
+
|
|
94
|
+
wrong_param_count_sets_it_false [test] :
|
|
95
|
+
description [string] : invoke with the wrong number of params puts false in it.
|
|
96
|
+
expect_errors [bool] : true
|
|
97
|
+
on_test [script] :
|
|
98
|
+
eval 'Hi'
|
|
99
|
+
invoke tests.verbs.invoke.add 1
|
|
100
|
+
eval it = false
|
|
101
|
+
assert "expected it to be false after a failed invoke"
|
|
102
|
+
|
|
103
|
+
shortcut_wrong_param_count_sets_it_false [test] :
|
|
104
|
+
description [string] : ~> with the wrong number of params puts false in it.
|
|
105
|
+
expect_errors [bool] : true
|
|
106
|
+
on_test [script] :
|
|
107
|
+
eval 'Hi'
|
|
108
|
+
~> tests.verbs.invoke.add 1
|
|
109
|
+
eval it = false
|
|
110
|
+
assert "expected it to be false after a failed ~>"
|
|
111
|
+
|
|
112
|
+
not_a_function_sets_it_false [test] :
|
|
113
|
+
description [string] : invoke of something that isn't a function puts false in it.
|
|
114
|
+
expect_errors [bool] : true
|
|
115
|
+
on_test [script] :
|
|
116
|
+
eval 'Hi'
|
|
117
|
+
invoke tests.verbs.invoke.total
|
|
118
|
+
eval it = false
|
|
119
|
+
assert "expected it to be false after invoking a non-function"
|
|
120
|
+
|
|
121
|
+
missing_arg_object_is_an_error [test] :
|
|
122
|
+
description [string] : an argument naming a missing object fires on_error and puts false in it.
|
|
123
|
+
expect_errors [bool] : true
|
|
124
|
+
on_test [script] :
|
|
125
|
+
put false into ^^.err_fired
|
|
126
|
+
eval 'Hi'
|
|
127
|
+
invoke tests.verbs.invoke.add no_such_obj_xyz 4
|
|
128
|
+
eval it = false
|
|
129
|
+
assert "expected it to be false"
|
|
130
|
+
eval ^^.err_fired = true
|
|
131
|
+
assert "expected on_error to fire for the missing argument"
|
|
132
|
+
|
|
133
|
+
inline_failure_in_put_sets_it_false [test] :
|
|
134
|
+
description [string] : a failed invoke( ... ) inside put puts false in it.
|
|
135
|
+
expect_errors [bool] : true
|
|
136
|
+
on_test [script] :
|
|
137
|
+
put 5 into ^^.total
|
|
138
|
+
put invoke( tests.verbs.invoke.add 1 ) into ^^.total
|
|
139
|
+
eval it = false
|
|
140
|
+
assert "expected it to be false"
|
|
141
|
+
eval ^^.total = 5
|
|
142
|
+
assert "expected the target to keep its value"
|
|
@@ -10,3 +10,19 @@ tests.verbs.log [can] :
|
|
|
10
10
|
log 'debug' (debug)
|
|
11
11
|
eval it = 'debug'
|
|
12
12
|
assert "expected it to be 'debug'"
|
|
13
|
+
|
|
14
|
+
failed_expression_puts_false_in_it [test] :
|
|
15
|
+
description [string] : log of an expression that fails puts false in it.
|
|
16
|
+
expect_errors [bool] : true
|
|
17
|
+
on_test [script] :
|
|
18
|
+
log 1 + no_such_obj_xyz
|
|
19
|
+
eval it = false
|
|
20
|
+
assert "expected it to be false after a failed expression"
|
|
21
|
+
|
|
22
|
+
failed_level_puts_the_value_in_it [test] :
|
|
23
|
+
description [string] : log with a level that fails still puts the value in it.
|
|
24
|
+
expect_errors [bool] : true
|
|
25
|
+
on_test [script] :
|
|
26
|
+
log 'plain' (no_such_obj_xyz)
|
|
27
|
+
eval it = 'plain'
|
|
28
|
+
assert "expected the value in it when only the level fails"
|
|
@@ -33,3 +33,35 @@ tests.verbs.show [can] :
|
|
|
33
33
|
show 'a' + 'b'
|
|
34
34
|
eval it = 'ab'
|
|
35
35
|
assert "expected it to be 'ab'"
|
|
36
|
+
|
|
37
|
+
add [ƒ] :
|
|
38
|
+
on_invoke [script] :
|
|
39
|
+
put ^.params.x + ^.params.y into ^.result
|
|
40
|
+
params [can] :
|
|
41
|
+
x [int] :
|
|
42
|
+
y [int] :
|
|
43
|
+
result [int] :
|
|
44
|
+
|
|
45
|
+
failed_expression_puts_false_in_it [test] :
|
|
46
|
+
description [string] : show of an expression that fails puts false in it.
|
|
47
|
+
expect_errors [bool] : true
|
|
48
|
+
on_test [script] :
|
|
49
|
+
show 1 + no_such_obj_xyz
|
|
50
|
+
eval it = false
|
|
51
|
+
assert "expected it to be false after a failed expression"
|
|
52
|
+
|
|
53
|
+
failed_inline_invoke_puts_false_in_it [test] :
|
|
54
|
+
description [string] : show of an inline invoke that fails puts false in it.
|
|
55
|
+
expect_errors [bool] : true
|
|
56
|
+
on_test [script] :
|
|
57
|
+
show invoke( tests.verbs.show.add 1 )
|
|
58
|
+
eval it = false
|
|
59
|
+
assert "expected it to be false after a failed inline invoke"
|
|
60
|
+
|
|
61
|
+
failed_color_puts_the_value_in_it [test] :
|
|
62
|
+
description [string] : show with a color that fails still puts the value in it.
|
|
63
|
+
expect_errors [bool] : true
|
|
64
|
+
on_test [script] :
|
|
65
|
+
show 'plain' (no_such_obj_xyz)
|
|
66
|
+
eval it = 'plain'
|
|
67
|
+
assert "expected the value in it when only the color fails"
|