gloo 7.1.2 → 7.1.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e3e9f9ec23fd38faee18f95a521fe1d21a8e572b2306c814233c3fc67ae6e5f3
4
- data.tar.gz: a1f2f961d1860473c14faefb6c5f9ffe0556bae36479cf3755b8333a1b0db68c
3
+ metadata.gz: 153d674b150e06c999a0756c90c41f872a410205f516eaa8b560335f93ec0e2b
4
+ data.tar.gz: 1b11115e161f139b44dc6750713e0e883f638ae58048c8b3007441cbf681f319
5
5
  SHA512:
6
- metadata.gz: d1665486b345b607c64f98519ac7219ac720ccad75d88d135a88225e3ea527871e6305acc95db9b31a89b7ba5373a6016b7fbcb0a354dfc82ecd04a3f5733bc9
7
- data.tar.gz: 35f9a3c3013d394ed4be04106f682ef92b14328279b12524d345f5a5a04f4ce31d7c8645d38a48484ac699a0d9d8ccc231340004a90cb0b5acdb98f890809b58
6
+ metadata.gz: 3e92b508048653b4e1e4e283a7e90de32605b4a239f56b7b24123c600e5b38a6af582a50a6d2a7130da8ed73d8ded9f0e6aab2a75798a9709939749cf72119dd
7
+ data.tar.gz: 19cd40e0d35cfed856aa7eb6ffe4fe648adde3c706656d81211d01f0c940a4c38dd63b0bffe2a4b50b3b57d9e76afba997050c7fca99066f2fe6529825d05591
data/docs/iterators.md CHANGED
@@ -9,7 +9,7 @@
9
9
  - Each Line
10
10
  - Each Word
11
11
  - Repeat
12
- - Ending a Loop Early
12
+ - Skipping an Item or Ending a Loop
13
13
 
14
14
  ## Each
15
15
 
@@ -330,9 +330,26 @@ repeat [can] :
330
330
 
331
331
  See also: Object Base.
332
332
 
333
- ## Ending a Loop Early
333
+ ## Skipping an Item or Ending a Loop
334
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:
335
+ `break` ends the script it's written in. In a loop's `do` script, that skips the rest of this pass, and the loop goes on with the next item. This works for `repeat` and every kind of `each`:
336
+
337
+ ```gloo
338
+ skip [can] :
339
+ on_load [script] :
340
+ tell skip.r to run
341
+ show 'after the loop'
342
+ r [repeat] :
343
+ times [int] : 5
344
+ index [int] :
345
+ do [script] :
346
+ if skip.r.index = 2 then break
347
+ show skip.r.index
348
+ ```
349
+
350
+ This shows `0`, `1`, `3`, `4`, then `after the loop`.
351
+
352
+ `break loop` ends the whole loop. The script that ran the loop carries on with its next line:
336
353
 
337
354
  ```gloo
338
355
  early [can] :
@@ -343,12 +360,12 @@ early [can] :
343
360
  times [int] : 5
344
361
  index [int] :
345
362
  do [script] :
346
- if early.r.index = 2 then break
363
+ if early.r.index = 2 then break loop
347
364
  show early.r.index
348
365
  ```
349
366
 
350
367
  This shows `0`, `1`, then `after the loop`.
351
368
 
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.
369
+ `break loop` is only valid directly in a loop's `do` script. In nested loops, a `break loop` in the inner loop's `do` ends only the inner loop. Anywhere else — a script the `do` script runs (with `run`, or a function it invokes), a loop's `on_group_start` or `on_group_end`, or a script that isn't in a loop — it's an error (`Could not break loop: not in a loop.`) and nothing stops. To end a loop from a helper, have the helper set a flag and check it in the `do` script. An `each` with `group_by` still runs `on_group_end` for the group that was open when the loop ended.
353
370
 
354
371
  See also: Break.
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. `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`).
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 loop` in the `do` script ended it, so stop the walk then: `break if @iterator_obj.run_do`. (A plain `break` ends only that pass, and `run_do` returns false.) 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.2
1
+ 7.1.3
data/lib/VERSION_NOTES CHANGED
@@ -1,3 +1,8 @@
1
+ 7.1.3 - 2026.10.29
2
+ - break in a loop's do skips to the next item again, as it did before 7.1.1.
3
+ - New: break loop ends the whole loop. It's only valid directly in a loop's do script.
4
+
5
+
1
6
  7.1.2 - 2026.10.28
2
7
  - Fixes bug with create verb and ^.here reference.
3
8
  - Behaviour change: an each over lines gives each line without its line ending.
@@ -16,6 +16,7 @@ module Gloo
16
16
  @engine = engine
17
17
  @obj = obj
18
18
  @break_out = false
19
+ @break_loop = false
19
20
  @line = 0
20
21
  end
21
22
 
@@ -59,10 +60,12 @@ module Gloo
59
60
  end
60
61
 
61
62
  #
62
- # Stop running this script.
63
+ # Stop running this script. With loop, also end the loop whose
64
+ # do script this is (break loop).
63
65
  #
64
- def break_out
66
+ def break_out( loop: false )
65
67
  @break_out = true
68
+ @break_loop = loop
66
69
  end
67
70
 
68
71
  #
@@ -72,6 +75,13 @@ module Gloo
72
75
  return @break_out
73
76
  end
74
77
 
78
+ #
79
+ # Did a break loop end this script's run, so its loop should end?
80
+ #
81
+ def broke_loop?
82
+ return @break_loop
83
+ end
84
+
75
85
  end
76
86
  end
77
87
  end
@@ -115,15 +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?
118
+ @broke_loop = s.broke_loop?
119
119
  end
120
120
 
121
121
  #
122
- # Did a break end this script's last run? A loop uses this to
123
- # stop when its do script breaks.
122
+ # Did a break loop end this script's last run? A loop uses this
123
+ # to stop when its do script says break loop.
124
124
  #
125
- def broke_out?
126
- return @broke_out == true
125
+ def broke_loop?
126
+ return @broke_loop == true
127
127
  end
128
128
 
129
129
  # ---------------------------------------------------------------------
@@ -43,15 +43,16 @@ module Gloo
43
43
  end
44
44
 
45
45
  #
46
- # Run the do script once. Returns true if a break in it ended the
47
- # run, so the loop should stop.
46
+ # Run the do script once. Returns true if a break loop in it
47
+ # ended the run, so the loop should stop. A plain break ends only
48
+ # the do script, and the loop goes on.
48
49
  #
49
50
  def run_do
50
51
  o = find_child DO
51
52
  return false unless o
52
53
 
53
54
  Gloo::Exec::Dispatch.message( @engine, 'run', o )
54
- return o.respond_to?( :broke_out? ) && o.broke_out?
55
+ return o.respond_to?( :broke_loop? ) && o.broke_loop?
55
56
  end
56
57
 
57
58
 
@@ -37,15 +37,16 @@ module Gloo
37
37
  end
38
38
 
39
39
  #
40
- # Run the do script once. Returns true if a break in it ended the
41
- # run, so the loop should stop.
40
+ # Run the do script once. Returns true if a break loop in it
41
+ # ended the run, so the loop should stop. A plain break ends only
42
+ # the do script, and the loop goes on.
42
43
  #
43
44
  def run_do
44
45
  o = find_child DO
45
46
  return false unless o
46
47
 
47
48
  Gloo::Exec::Dispatch.message( @engine, 'run', o )
48
- return o.respond_to?( :broke_out? ) && o.broke_out?
49
+ return o.respond_to?( :broke_loop? ) && o.broke_loop?
49
50
  end
50
51
 
51
52
  #
@@ -10,14 +10,23 @@ module Gloo
10
10
 
11
11
  KEYWORD = 'break'.freeze
12
12
  KEYWORD_SHORT = 'stop'.freeze
13
+ LOOP = 'loop'.freeze
14
+ NOT_IN_A_LOOP = 'Could not break loop: not in a loop.'.freeze
13
15
 
14
16
  #
15
17
  # Run the verb.
16
- # Stop the execution of the current script.
18
+ # Stop the execution of the current script. With loop (break
19
+ # loop), also end the loop whose do script this is.
17
20
  #
18
21
  def run
19
- warn_extra_words
20
- @engine.exec_env.running_script.break_out
22
+ words = extra_words.split( ' ' )
23
+ if words.first&.downcase == LOOP
24
+ warn_words_after_loop( words.drop( 1 ) )
25
+ break_loop
26
+ else
27
+ warn_unknown_words
28
+ @engine.exec_env.running_script.break_out
29
+ end
21
30
  end
22
31
 
23
32
  #
@@ -40,6 +49,54 @@ module Gloo
40
49
 
41
50
  private
42
51
 
52
+ #
53
+ # End the running script and its loop. Only valid directly in a
54
+ # loop's do script; anywhere else (a script the do runs, a group
55
+ # script, outside any loop) it's an error and nothing stops.
56
+ #
57
+ def break_loop
58
+ script = @engine.exec_env.running_script
59
+ unless loop_do?( script&.obj )
60
+ @engine.err NOT_IN_A_LOOP
61
+ return
62
+ end
63
+
64
+ script.break_out( loop: true )
65
+ end
66
+
67
+ #
68
+ # Is the object the do script of a loop (a repeat or an each)?
69
+ #
70
+ def loop_do?( obj )
71
+ parent = obj&.parent
72
+ return false unless parent.is_a?( Gloo::Objs::Repeat ) ||
73
+ parent.is_a?( Gloo::Objs::Each )
74
+
75
+ return parent.find_child( Gloo::Objs::Each::DO ).equal?( obj )
76
+ end
77
+
78
+ #
79
+ # Warn about anything written after break other than loop.
80
+ # The break still happens.
81
+ #
82
+ def warn_unknown_words
83
+ extra = extra_words
84
+ return if extra.empty?
85
+
86
+ @engine.warn "#{written_verb} takes only '#{LOOP}'; ignoring '#{extra}'."
87
+ end
88
+
89
+ #
90
+ # Warn about anything written after break loop. The loop still
91
+ # ends.
92
+ #
93
+ def warn_words_after_loop( words )
94
+ return if words.empty?
95
+
96
+ @engine.warn "#{written_verb} #{LOOP} takes no object; " \
97
+ "ignoring '#{words.join( ' ' )}'."
98
+ end
99
+
43
100
  # ---------------------------------------------------------------------
44
101
  # Verb Documentation
45
102
  # ---------------------------------------------------------------------
@@ -52,15 +109,26 @@ module Gloo
52
109
  :name => KEYWORD,
53
110
  :shortcut => KEYWORD_SHORT,
54
111
  :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.',
57
- :syntax => [ 'break' ],
112
+ 'In a loop\'s do script, break skips the rest of that pass ' \
113
+ 'and the loop goes on; break loop ends the whole loop.',
114
+ :syntax => [ 'break', 'break loop' ],
115
+ :parameters => [
116
+ 'loop — Optional. Also end the loop whose do script this is. ' \
117
+ 'Only valid directly in a loop\'s do script (repeat, or any ' \
118
+ 'kind of each).'
119
+ ],
58
120
  :result => 'No more commands are executed in the currently ' \
59
121
  'running script. If there are other scripts in the stack, ' \
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.',
122
+ 'they will resume normally. In a loop\'s do script, a plain ' \
123
+ 'break ends that pass and the loop goes on with the next ' \
124
+ 'item. With break loop, the loop ends too and the script ' \
125
+ 'that ran it carries on.',
126
+ :errors => [
127
+ "#{NOT_IN_A_LOOP} — break loop in a script that isn't a " \
128
+ 'loop\'s do script: one the do script runs (with run, or a ' \
129
+ 'function), a loop\'s on_group_start or on_group_end, or a ' \
130
+ 'script outside any loop. Nothing stops.'
131
+ ],
64
132
  :examples => <<~EXAMPLES.strip
65
133
  #
66
134
  # Break out of a script.
@@ -73,6 +141,27 @@ module Gloo
73
141
  put true into ^.x
74
142
  if ^.x then break
75
143
  show 'should not see this'
144
+
145
+ #
146
+ # In a loop: break skips an item, break loop ends the loop.
147
+ # Shows 0, 1, 3, then 0, 1.
148
+ #
149
+ loops [can] :
150
+ on_load [script] :
151
+ tell loops.skip to run
152
+ tell loops.early to run
153
+ skip [repeat] :
154
+ times [int] : 4
155
+ index [int] :
156
+ do [script] :
157
+ if loops.skip.index = 2 then break
158
+ show loops.skip.index
159
+ early [repeat] :
160
+ times [int] : 4
161
+ index [int] :
162
+ do [script] :
163
+ if loops.early.index = 2 then break loop
164
+ show loops.early.index
76
165
  EXAMPLES
77
166
  }
78
167
  end
@@ -8,7 +8,7 @@ tests.verbs.break [can] :
8
8
 
9
9
  s [script] :
10
10
  if ^.b then break
11
-
11
+
12
12
  # Should not get here
13
13
  put false into ^.b
14
14
 
@@ -19,16 +19,27 @@ tests.verbs.break [can] :
19
19
  eval ^^.b
20
20
  assert "expected b to be true"
21
21
 
22
- # Loops whose do script breaks on its second pass.
22
+ # The last error, for the break loop errors.
23
+ on_error [script] :
24
+ error_data [can] :
25
+ message [string] :
26
+
27
+ not_in_a_loop [string] : Could not break loop: not in a loop.
28
+
29
+ # Loops whose do script breaks on one pass: skip_on for a plain
30
+ # break, end_on for break loop (0 never breaks).
23
31
  passes [int] : 0
24
32
  after [int] : 0
33
+ skip_on [int] : 0
34
+ end_on [int] : 0
25
35
 
26
36
  rep [repeat] :
27
37
  times [int] : 4
28
38
  index [int] :
29
39
  do [script] :
30
40
  put tests.verbs.break.passes + 1 into tests.verbs.break.passes
31
- if tests.verbs.break.passes = 2 then break
41
+ if tests.verbs.break.passes = tests.verbs.break.skip_on then break
42
+ if tests.verbs.break.passes = tests.verbs.break.end_on then break loop
32
43
  put tests.verbs.break.after + 1 into tests.verbs.break.after
33
44
 
34
45
  kids [can] :
@@ -41,14 +52,15 @@ tests.verbs.break [can] :
41
52
  in [alias] : tests.verbs.break.kids
42
53
  do [script] :
43
54
  put tests.verbs.break.passes + 1 into tests.verbs.break.passes
44
- if tests.verbs.break.passes = 2 then break
55
+ if tests.verbs.break.passes = tests.verbs.break.skip_on then break
56
+ if tests.verbs.break.passes = tests.verbs.break.end_on then break loop
45
57
  put tests.verbs.break.after + 1 into tests.verbs.break.after
46
58
 
47
59
  inner [repeat] :
48
60
  times [int] : 3
49
61
  index [int] :
50
62
  do [script] :
51
- if tests.verbs.break.inner.index = 1 then break
63
+ if tests.verbs.break.inner.index = 1 then break loop
52
64
  put tests.verbs.break.after + 1 into tests.verbs.break.after
53
65
 
54
66
  outer [repeat] :
@@ -57,30 +69,111 @@ tests.verbs.break [can] :
57
69
  do [script] :
58
70
  tell tests.verbs.break.inner to run
59
71
 
60
- break_ends_a_repeat [test] :
61
- description [string] : break in a repeat's do script ends the loop.
72
+ # The form the apps use to skip an item: unless ... do break.
73
+ skipped [string] :
74
+ skip_b [each] :
75
+ child [alias] :
76
+ in [alias] : tests.verbs.break.kids
77
+ do [script] :
78
+ check tests.verbs.break.skip_b.child for starts_with?( 'b' )
79
+ unless it = false do break
80
+ put tests.verbs.break.skipped + tests.verbs.break.skip_b.child into tests.verbs.break.skipped
81
+
82
+ # break loop is only valid directly in a loop's do script.
83
+ lone [script] :
84
+ break loop
85
+
86
+ loop_helper [script] :
87
+ break loop
88
+
89
+ via_loop [repeat] :
90
+ times [int] : 2
91
+ index [int] :
92
+ do [script] :
93
+ put tests.verbs.break.passes + 1 into tests.verbs.break.passes
94
+ run tests.verbs.break.loop_helper
95
+
96
+ break_skips_the_rest_of_a_repeat_pass [test] :
97
+ description [string] : break in a repeat's do script skips to the next pass.
62
98
  on_test [script] :
63
99
  put 0 into ^^.passes
64
100
  put 0 into ^^.after
101
+ put 0 into ^^.end_on
102
+ put 2 into ^^.skip_on
103
+ tell ^^.rep to run
104
+ eval ^^.passes = 4
105
+ assert "expected every pass to run"
106
+ eval ^^.after = 3
107
+ assert "expected only the pass that broke to stop early"
108
+
109
+ break_skips_the_rest_of_an_each_pass [test] :
110
+ description [string] : break in an each's do script skips to the next child.
111
+ on_test [script] :
112
+ put 0 into ^^.passes
113
+ put 0 into ^^.after
114
+ put 0 into ^^.end_on
115
+ put 2 into ^^.skip_on
116
+ tell ^^.each_kid to run
117
+ eval ^^.after = 2
118
+ assert "expected every child but the one that broke to get past it"
119
+
120
+ unless_do_break_skips_an_item [test] :
121
+ description [string] : unless ... do break in an each skips that item, as the apps use it.
122
+ on_test [script] :
123
+ put '' into ^^.skipped
124
+ tell ^^.skip_b to run
125
+ eval ^^.skipped = 'ac'
126
+ assert "expected b to be skipped and c still reached"
127
+
128
+ break_loop_ends_a_repeat [test] :
129
+ description [string] : break loop in a repeat's do script ends the loop.
130
+ on_test [script] :
131
+ put 0 into ^^.passes
132
+ put 0 into ^^.after
133
+ put 0 into ^^.skip_on
134
+ put 2 into ^^.end_on
65
135
  tell ^^.rep to run
66
136
  eval ^^.passes = 2
67
137
  assert "expected the loop to stop on the pass that broke"
68
138
  eval ^^.after = 1
69
139
  assert "expected only the first pass to get past the break"
70
140
 
71
- break_ends_an_each [test] :
72
- description [string] : break in an each's do script ends the loop.
141
+ break_loop_ends_an_each [test] :
142
+ description [string] : break loop in an each's do script ends the loop.
73
143
  on_test [script] :
74
144
  put 0 into ^^.passes
75
145
  put 0 into ^^.after
146
+ put 0 into ^^.skip_on
147
+ put 2 into ^^.end_on
76
148
  tell ^^.each_kid to run
77
149
  eval ^^.passes = 2
78
150
  assert "expected the loop to stop on the pass that broke"
79
151
 
80
- break_ends_only_the_inner_loop [test] :
81
- description [string] : break in an inner loop ends that loop, not the outer one.
152
+ break_loop_ends_only_the_inner_loop [test] :
153
+ description [string] : break loop in an inner loop ends that loop, not the outer one.
82
154
  on_test [script] :
83
155
  put 0 into ^^.after
84
156
  tell ^^.outer to run
85
157
  eval ^^.after = 2
86
158
  assert "expected each of the 2 inner loops to get past its break once"
159
+
160
+ break_loop_outside_a_loop_is_an_error [test] :
161
+ description [string] : break loop in a script that isn't a loop's do is an error.
162
+ expect_errors [bool] : true
163
+ on_test [script] :
164
+ put '' into ^^.error_data.message
165
+ run ^^.lone
166
+ eval ^^.error_data.message = ^^.not_in_a_loop
167
+ assert "expected the not-in-a-loop error"
168
+
169
+ break_loop_in_a_helper_is_an_error [test] :
170
+ description [string] : break loop in a script the do runs is an error, and the loop goes on.
171
+ expect_errors [bool] : true
172
+ on_test [script] :
173
+ put '' into ^^.error_data.message
174
+ put 0 into ^^.passes
175
+ tell ^^.via_loop to run
176
+ eval ^^.error_data.message = ^^.not_in_a_loop
177
+ assert "expected the not-in-a-loop error"
178
+ eval ^^.passes = 2
179
+ assert "expected the loop to go on"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gloo
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.1.2
4
+ version: 7.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Crane