gloo 7.0.4 → 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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -1
  3. data/docs/application.md +16 -2
  4. data/docs/development.md +1 -1
  5. data/docs/iterators.md +24 -0
  6. data/docs/language_objects.md +34 -0
  7. data/docs/language_syntax.md +26 -1
  8. data/docs/objects.md +1 -1
  9. data/docs/plugins.md +10 -4
  10. data/docs/testing.md +206 -0
  11. data/docs/verbs.md +7 -0
  12. data/lib/VERSION +1 -1
  13. data/lib/VERSION_NOTES +18 -2
  14. data/lib/gloo/app/engine.rb +19 -9
  15. data/lib/gloo/app/log.rb +2 -2
  16. data/lib/gloo/app/settings.rb +20 -3
  17. data/lib/gloo/core/gloo_system.rb +7 -0
  18. data/lib/gloo/core/invoker.rb +20 -7
  19. data/lib/gloo/core/obj.rb +22 -1
  20. data/lib/gloo/core/tokens.rb +20 -0
  21. data/lib/gloo/core/verb.rb +31 -0
  22. data/lib/gloo/exec/exec_env.rb +6 -3
  23. data/lib/gloo/exec/script.rb +7 -0
  24. data/lib/gloo/objs/basic/script.rb +9 -0
  25. data/lib/gloo/objs/basic/string_msgs.rb +6 -7
  26. data/lib/gloo/objs/basic/untyped.rb +2 -1
  27. data/lib/gloo/objs/ctrl/each.rb +4 -2
  28. data/lib/gloo/objs/ctrl/each_child.rb +1 -1
  29. data/lib/gloo/objs/ctrl/each_dir.rb +1 -1
  30. data/lib/gloo/objs/ctrl/each_file.rb +1 -1
  31. data/lib/gloo/objs/ctrl/each_line.rb +1 -1
  32. data/lib/gloo/objs/ctrl/each_word.rb +1 -1
  33. data/lib/gloo/objs/ctrl/repeat.rb +5 -3
  34. data/lib/gloo/objs/str_utils/cipher.rb +10 -2
  35. data/lib/gloo/objs/system/file_handle.rb +5 -2
  36. data/lib/gloo/objs/web/json.rb +7 -5
  37. data/lib/gloo/persist/declaration_ledger.rb +6 -6
  38. data/lib/gloo/persist/file_loader.rb +11 -1
  39. data/lib/gloo/persist/file_storage.rb +8 -2
  40. data/lib/gloo/persist/persist_man.rb +29 -1
  41. data/lib/gloo/verbs/break.rb +7 -2
  42. data/lib/gloo/verbs/context.rb +10 -2
  43. data/lib/gloo/verbs/eval.rb +5 -4
  44. data/lib/gloo/verbs/exists.rb +14 -2
  45. data/lib/gloo/verbs/invoke.rb +7 -6
  46. data/lib/gloo/verbs/load.rb +16 -2
  47. data/lib/gloo/verbs/log.rb +8 -4
  48. data/lib/gloo/verbs/save.rb +10 -3
  49. data/lib/gloo/verbs/show.rb +15 -10
  50. data/test.gloo/ctrl/each.test.gloo +21 -7
  51. data/test.gloo/lang/declaration.test.gloo +7 -2
  52. data/test.gloo/lang/exceptions.test.gloo +22 -0
  53. data/test.gloo/lang/gloo_sys.test.gloo +12 -0
  54. data/test.gloo/lang/naming.test.gloo +7 -2
  55. data/test.gloo/objs/cipher.test.gloo +27 -0
  56. data/test.gloo/objs/file.test.gloo +18 -2
  57. data/test.gloo/objs/fixtures/source_dir_fixture.gloo +6 -0
  58. data/test.gloo/objs/int.test.gloo +6 -3
  59. data/test.gloo/objs/json.test.gloo +2 -0
  60. data/test.gloo/objs/obj.test.gloo +33 -0
  61. data/test.gloo/objs/string.test.gloo +2 -2
  62. data/test.gloo/verbs/break.test.gloo +66 -0
  63. data/test.gloo/verbs/context.test.gloo +12 -0
  64. data/test.gloo/verbs/eval.test.gloo +8 -0
  65. data/test.gloo/verbs/exists.test.gloo +19 -0
  66. data/test.gloo/verbs/invoke.test.gloo +50 -0
  67. data/test.gloo/verbs/load.test.gloo +101 -2
  68. data/test.gloo/verbs/log.test.gloo +16 -0
  69. data/test.gloo/verbs/reload.test.gloo +9 -4
  70. data/test.gloo/verbs/save.test.gloo +41 -16
  71. data/test.gloo/verbs/show.test.gloo +32 -0
  72. metadata +5 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0f84c9907a97353612993852efc46b6880b119c962b785da1c43326f1d0710b3
4
- data.tar.gz: 6582c120e0b04e9daaa3be9d70002d407f778b045fa668f87ca20fae68b22218
3
+ metadata.gz: d25bb60fb25bb1b42e44eeac62b0ec3bae86a9f3770c696caf4ab39b7f1c9784
4
+ data.tar.gz: 260731b8d5f1cd1c6f3718be0d26f17d39139795e7e5c13ff8908a4fc3f1c453
5
5
  SHA512:
6
- metadata.gz: 5ad11f0ed9bec1122fc796920029ee5b1bdea8dee0e3aecdedefd85241de70ff1f52e8fb893726d6fd135a0e86588aac44ab6598d2a3b6f071f61c2411281adc
7
- data.tar.gz: ec26f94681a480a42bf9cf21ca7668de7896eba9937a731d501c1ca2a5998fe12dc08b26e9452c9aeafbee59f2eb3e21b1599af8e3211f8e5baa593d232a3146
6
+ metadata.gz: 3729efcd7dcfa5964a408fb28750bd2ac7caea3af323b1ea8c8b75503446f51805db7f20b05bfd705a9ac020bd06fe6fba5135013137e937f7dd6f499e134f40
7
+ data.tar.gz: d69918fdaf8ab491ac5bc21cd7a838048ef4eaf2299d8a867eea017b14c0f5ca3a314e7106e6fc0bf00fad8e949550efaae897121d8ae11de4d964a1a0231218
data/README.md CHANGED
@@ -47,7 +47,8 @@ Read in order for a guided tour of the language, top to bottom, or jump straight
47
47
  8. [Operators](docs/operators.md) — math and comparison operators
48
48
  9. [Iterators](docs/iterators.md) — each, repeat
49
49
  10. [Plugins](docs/plugins.md) — core libraries and user extensions
50
- 11. [A Trivial Web App](docs/web_app.md) — a two-page "Hello World" built with the `gloo-web` core library
50
+ 11. [Testing](docs/testing.md) — writing and running gloo tests with `gloo-test`
51
+ 12. [A Trivial Web App](docs/web_app.md) — a two-page "Hello World" built with the `gloo-web` core library
51
52
 
52
53
  Working on gloo itself? See [Development](docs/development.md) — source layout, running the tests, and conventions.
53
54
 
data/docs/application.md CHANGED
@@ -17,6 +17,7 @@ Gloo keeps its configuration, projects, and logs under a per-user home directory
17
17
  - `gloo.gloo_config` — the configuration directory (see Configuration, below)
18
18
  - `gloo.gloo_projects` — the projects directory (same as `gloo.app` when an app is running)
19
19
  - `gloo.gloo_log` — the logging directory (see Logging, below)
20
+ - `gloo.gloo_tmp` — a folder for temporary files, such as scratch files a script or test writes and then deletes. It's `tmp` under the gloo home directory unless `tmp_path` in `gloo.yml` says otherwise (see Configuration, below), and it's created if it isn't there. Use it instead of `/tmp`, which doesn't exist on Windows.
20
21
 
21
22
  ```
22
23
  > show gloo.gloo_home
@@ -70,6 +71,11 @@ When specifying a file there are a couple ways to reference the gloo file to ope
70
71
  - Run the script in the file parameter and then quit.
71
72
  - If a file is provided as a parameter this option does not need to be specified.
72
73
  --test - Run in Test mode
74
+ - Runs the *.test.gloo files in the folders and files given
75
+ (the current folder if none), using the gloo-test library.
76
+ - Exits with status 1 if any test failed, or errors or warnings
77
+ were logged that no test expected; 0 otherwise.
78
+ - See Testing.
73
79
  --version - Show application version
74
80
  --help - Show the help screen
75
81
  ```
@@ -90,6 +96,12 @@ gloo:
90
96
  #
91
97
  project_path: /Users/my_user/gloo/projects
92
98
 
99
+ #
100
+ # Folder for temporary files, such as scratch files written by
101
+ # tests. Scripts get it with $.gloo_tmp.
102
+ #
103
+ tmp_path: /Users/my_user/gloo/tmp
104
+
93
105
  #
94
106
  # Run this script when starting up gloo.
95
107
  # (Only if a script file is not specified.)
@@ -146,9 +158,11 @@ Gloo writes to the `gloo.log` and `error.log` files as well as to the console.
146
158
 
147
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.
148
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
+
149
163
  Only error and warning level messages are written to the error log.
150
164
 
151
- 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`.
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.
152
166
 
153
167
  The application logs folder is in the gloo folder. When gloo is run, the log files will be created if they do not exist. To trim the logs, just delete those log files.
154
168
 
@@ -199,4 +213,4 @@ help> quit
199
213
 
200
214
  ### Narrative Docs in the Shell
201
215
 
202
- This page and its ten siblings (`docs/*.md` in the interpreter's source) are the same files shown by `help> doc {name}` — there's no separate doc server or web mode. A `gloo --doc` local web server was considered and put off in favor of this: one system, no separate build/serve step.
216
+ This page and the others in `docs/` (in the interpreter's source) are the same files shown by `help> doc {name}` — there's no separate doc server or web mode. A `gloo --doc` local web server was considered and put off in favor of this: one system, no separate build/serve step.
data/docs/development.md CHANGED
@@ -54,7 +54,7 @@ Integration tests written in gloo itself, using the `[test]` object from the `gl
54
54
  gem install gloo-test gloo-yaml gloo-md
55
55
  ```
56
56
 
57
- Each `*.test.gloo` file holds `[test]` objects whose `on_test` script uses `assert` and `refute`:
57
+ Each `*.test.gloo` file holds `[test]` objects whose `on_test` script uses `assert` and `refute`. How to write them (assertions, shuffled order, `expect_errors`, `expect_fail`, scratch files and fixtures) is in Testing; this section covers only what's particular to the interpreter's own suite. A test looks like this:
58
58
 
59
59
  ```gloo
60
60
  tests [can] :
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.
@@ -4,6 +4,8 @@
4
4
 
5
5
  - Object Naming
6
6
  - Nested Container Shorthand
7
+ - Documenting an Object
8
+ - Where an Object Came From
7
9
  - Keywords
8
10
  - Literals
9
11
  - Value Conversion
@@ -113,6 +115,38 @@ deleted, its doc comment goes with it (see Verbs, Load & Save).
113
115
  `list_docs` setting (off by default) — see `list` and `settings` in the
114
116
  in-app help.
115
117
 
118
+ ## Where an Object Came From
119
+
120
+ Every object can say which file it was loaded from: `check {obj} for
121
+ source_file` puts the file's full path in `it`, and `check {obj} for
122
+ source_dir` puts that file's folder (with no trailing slash). This lets a
123
+ script reach files stored next to it without a hard-coded path. For
124
+ example, a test can load a fixture that sits beside its own file:
125
+
126
+ ```gloo
127
+ tests.my_area [can] :
128
+ where [string] :
129
+ fixture [string] : /fixtures/sample.gloo
130
+
131
+ loads_its_fixture [test] :
132
+ on_test [script] :
133
+ check ^^ for source_dir
134
+ put it into ^^.where
135
+ load ( ^^.where ^^.fixture )
136
+ ```
137
+
138
+ The answer comes from the nearest object, going up from the one asked,
139
+ that a loaded file declares. So a nested object answers with its file,
140
+ even when its root container (like `tests` above) is declared in many
141
+ files. An object created at run time inside a loaded one answers with
142
+ that one's file. When several files declare the same object (a namespace
143
+ container), the first file loaded answers.
144
+
145
+ An object that didn't come from a file (created at run time, or
146
+ unloaded) answers blank (`''`); that isn't an error. On a `[file]`
147
+ object, `source_file` is the `.gloo` file that declared it, not the path
148
+ it holds.
149
+
116
150
  ## Keywords
117
151
 
118
152
  Gloo doesn't reserve words the way many languages do. A verb keyword like `put` or an object type name like `string` can also be used as an object name — there's no parser conflict, because verbs are only looked up as the first word of a statement, and object type names are only looked up where a type is expected (inside the `[ ]` on a declaration). Everywhere else, the word is just a pathname segment (see Object Naming, above).
@@ -3,6 +3,7 @@
3
3
  **Contents**
4
4
 
5
5
  - Color
6
+ - Computed Arguments
6
7
  - Errors
7
8
  - Events
8
9
  - Function Calls
@@ -43,6 +44,28 @@ colors [can] :
43
44
 
44
45
  See also: Show.
45
46
 
47
+ ## Computed Arguments
48
+
49
+ Some verbs take literal text rather than an object or an expression: a file name, a path, a name. Each of these can instead be an expression in parentheses. Gloo works out the expression and uses its value as the text:
50
+
51
+ ```
52
+ load ( $.gloo_tmp '/scratch.gloo' )
53
+ load lib ( lib_name )
54
+ save my_obj to ( $.gloo_tmp '/my_obj' )
55
+ context ( 'app.' section )
56
+ exists? object ( type_name )
57
+ ```
58
+
59
+ This works for `load` (a file, `ext` or `lib`), `save … to`, `context` and `exists?`. Anything not in parentheses is still taken literally, so `load common/app/*` still loads those files. A computed file name goes through the same lookup as a literal one, so wildcards, the `.gloo` extension and project-relative names work the same way.
60
+
61
+ The `+` is optional when joining text, so `( $.gloo_tmp '/scratch.gloo' )` and `( $.gloo_tmp + '/scratch.gloo' )` are the same. A `[file]` or `[string]` object holding a path works too: `load ( my.file_obj )`.
62
+
63
+ If the expression can't be worked out (for example, an object it names doesn't exist), that's reported as an error and the verb doesn't run.
64
+
65
+ For anything else, `run ~>` works out a whole command and runs it: `run ~> 'load ' $.gloo_tmp '/x.gloo'`. Prefer the parentheses where they're available; a command built this way breaks if a path has spaces in it.
66
+
67
+ See also: Load, Save, Context, Exists.
68
+
46
69
  ## Errors and Warnings
47
70
 
48
71
  Gloo makes a best guess and keeps going when it can, but it never does so silently: every problem is reported, in one of these ways.
@@ -52,7 +75,7 @@ Gloo makes a best guess and keeps going when it can, but it never does so silent
52
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.
53
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`.
54
77
 
55
- 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.`), or the script and its line (`app.on_load, line 3: Object 'app.nme' was not found.`). "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.`
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.`
56
79
 
57
80
  What happens after an error:
58
81
 
@@ -254,6 +277,7 @@ gloo.gloo_home # Get the gloo home directory
254
277
  gloo.gloo_config # Get the gloo configuration directory
255
278
  gloo.gloo_projects # Get the gloo projects directory
256
279
  gloo.gloo_log # Get the gloo logging directory
280
+ gloo.gloo_tmp # Get the gloo tmp directory, for temporary files
257
281
 
258
282
  SCREEN
259
283
  gloo.screen_lines # Get the number of lines on screen.
@@ -308,6 +332,7 @@ See also: Pathname.
308
332
 
309
333
  - **A question** (`check x for exists?`, `contains?`, `starts_with?`, …) puts its answer in `it`.
310
334
  - **A command or message that works out a value** puts that value in `it` (`eval`, `put`, `show 3 + 4`) — even if it also stores it somewhere, in the object's own value (`trim`, `sub`, `format_for_html`) or in a child (`run` on an `erb`, `http_get` or `system` object also sets its `result`).
335
+ - **A message that puts its result into a destination you name** puts its status in `it`: `true` when it worked, `false` when it didn't. With no destination, the result itself goes in `it`. So `tell f to read ( text )` puts the file's contents in `text` and `true` in `it`, while `tell f to read` puts the contents in `it`. The same goes for json `parse ( dst )` and string `split_list ( delim dst )`.
311
336
  - **A pure action** — `list`, a bare `show`, deleting, opening a file — leaves `it` alone, so a value in `it` survives it. Running a script or a loop doesn't set `it` either, though the commands inside it do.
312
337
  - **When something goes wrong** in a question or a command that would set `it`, the problem is reported and `it` is `false`. The same goes for a message that never gets to run — the object doesn't exist, or doesn't take that message — including `run` itself: `it` is `false`, so no stale value from an earlier line is left behind.
313
338
 
data/docs/objects.md CHANGED
@@ -4,7 +4,7 @@ Everything in gloo is an object. Strings, numbers, containers, scripts, function
4
4
 
5
5
  Gloo ships with a large set of built-in object types, and core libraries and extensions can add more. This page doesn't try to cover them all — it walks through three common ones to get a feel for how objects work. For the complete list of object types, and every message each one supports, use the in-app help: enter `help` (or `?`), then `objects` to list them all, or `object {name}` for detail on one (see Application, Help).
6
6
 
7
- An object's **type declares which messages it can receive** — `up` and `trim` for a `string`, `inc` for an `integer`, `run` for a `script`. An object doesn't have to have a type: with none it is `untyped` (short `any`), declared with a bare colon (`slot :`), with `[any]`, or by `create` with no `as`. An untyped object still takes the messages every object understands (`blank?`, `contains?`, `responds_to?`, `doc`, `reload`, `unload`) — just not the type-specific ones. Of those, `blank?` asks about the object's simple value only, and `contains?` asks whether it has children: a container with children but no value of its own is blank, and contains. Untyped is the right choice when you only need to hold, compare, or show a value: a generic result slot, a config value passed straight through, or a field whose kind of value changes over its life.
7
+ An object's **type declares which messages it can receive** — `up` and `trim` for a `string`, `inc` for an `integer`, `run` for a `script`. An object doesn't have to have a type: with none it is `untyped` (short `any`), declared with a bare colon (`slot :`), with `[any]`, or by `create` with no `as`. An untyped object still takes the messages every object understands (`blank?`, `contains?`, `responds_to?`, `doc`, `source_dir`, `source_file`, `reload`, `unload`) — just not the type-specific ones. Of those, `blank?` asks about the object's simple value only, and `contains?` asks whether it has children: a container with children but no value of its own is blank, and contains. Untyped is the right choice when you only need to hold, compare, or show a value: a generic result slot, a config value passed straight through, or a field whose kind of value changes over its life.
8
8
 
9
9
  **Contents**
10
10
 
data/docs/plugins.md CHANGED
@@ -29,6 +29,12 @@ settings [can] :
29
29
 
30
30
  The loader runs these top-of-file `load lib` / `load ext` lines before it builds the object tree, so declarations below them can use the library's types. Only `load lib` and `load ext` are recognized this way, and only above the first object — a `load lib` in a script still runs when that script runs, as before.
31
31
 
32
+ When working on a core library, `load lib` loads the installed gem, not the copy you're editing, so its tests quietly test the installed version. To test the edited source, run from the library's folder with `RUBYLIB` pointing at its `lib/`:
33
+
34
+ ```
35
+ RUBYLIB="$(pwd)/lib" gloo --test test/
36
+ ```
37
+
32
38
  ### Available Core Libraries
33
39
 
34
40
  - **CLI** — Use the `gloo-cli` gem when building CLI applications.
@@ -60,7 +66,7 @@ The loader runs these top-of-file `load lib` / `load ext` lines before it builds
60
66
  > load lib md
61
67
  help> object markdown
62
68
  ```
63
- - **Test** — Use the `gloo-test` gem to manually include. See Test Runner for notes about the gloo test runner.
69
+ - **Test** — Use the `gloo-test` gem to write and run gloo tests. `gloo --test` loads it for you; see Testing for how to write and run tests.
64
70
  - Library Objects: Test
65
71
  - Library Verbs: Assert, Refute
66
72
  - See also: Eval, It
@@ -348,7 +354,7 @@ class Stats < Gloo::Core::Obj
348
354
  end
349
355
  ```
350
356
 
351
- `self.messages` should call `super` and append to it — the base `Gloo::Core::Obj` already contributes messages every object receives (`reload`, `unload`, `blank?`, `contains?`, `responds_to?`).
357
+ `self.messages` should call `super` and append to it — the base `Gloo::Core::Obj` already contributes messages every object receives (`save`, `reload`, `unload`, `blank?`, `contains?`, `responds_to?`, `doc`, `source_dir`, `source_file`).
352
358
 
353
359
  Used from gloo, once loaded:
354
360
 
@@ -366,7 +372,7 @@ Register it from `stats_ext.rb` with `callback.register_obj( Stats )`.
366
372
 
367
373
  ### Custom `each` iterators
368
374
 
369
- 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`).
370
376
 
371
377
  ### Documenting the extension
372
378
 
@@ -374,7 +380,7 @@ Add `self.doc_data` to every verb/object class (see the `beep` and `stats` examp
374
380
 
375
381
  ### Testing an extension
376
382
 
377
- Add a Ruby unit test per verb/object class under `test/` (the same conventions as the interpreter's own tests, see Development, Tests — inherit from the project's `GlooTest`/`BaseEngineTest` base), and add a `*.test.gloo` integration test that loads the extension and exercises it end to end, e.g. `extensions/beep/test/beep.test.gloo`:
383
+ Add a Ruby unit test per verb/object class under `test/` (the same conventions as the interpreter's own tests, see Development, Tests — inherit from the project's `GlooTest`/`BaseEngineTest` base), and add a `*.test.gloo` integration test that loads the extension and exercises it end to end (see Testing for how to write and run gloo tests), e.g. `extensions/beep/test/beep.test.gloo`:
378
384
 
379
385
  ```gloo
380
386
  tests [can] :
data/docs/testing.md ADDED
@@ -0,0 +1,206 @@
1
+ # Testing
2
+
3
+ How to write and run tests for gloo code: an app, an extension or a core library. Tests are written in gloo itself, in `*.test.gloo` files, using the `[test]` object and the `assert` and `refute` verbs from the `gloo-test` core library. This page is about those gloo tests. Ruby unit tests for an extension's Ruby code are covered in Plugins, Testing an Extension; for the interpreter's own tests, see Development, Tests.
4
+
5
+ **Contents**
6
+
7
+ - Running Tests
8
+ - Writing a Test
9
+ - Assertions
10
+ - Test Order and Setup
11
+ - Errors and Warnings
12
+ - Tests Meant to Fail
13
+ - Files in Tests
14
+
15
+ ## Running Tests
16
+
17
+ Install the `gloo-test` gem, then run gloo in test mode with the tests to run:
18
+
19
+ ```
20
+ gem install gloo-test
21
+ gloo --test path/to/tests/
22
+ ```
23
+
24
+ `--test` loads the `gloo-test` library itself, so a test file doesn't need `load lib test`. Give any mix of folders and single files. In a folder, every `*.test.gloo` file is run, including those in subfolders. With no path, gloo looks in the current folder.
25
+
26
+ While the tests run, gloo shows a `.` for each test that passes and an `x` for each one that fails. Then it lists the failures, each with the test's path, its description and the message of the assertion that failed, and ends with a summary:
27
+
28
+ ```
29
+ x.....
30
+
31
+ *** Failures (1) ***
32
+
33
+ tests.math.wrong_on_purpose -> A failing test, to see the output.
34
+ expected 2 + 2 to be 5
35
+
36
+ Tests: 6 • Passed: 5 • Failed: 1
37
+ Assertions: 6 • Files: 2
38
+ Tests finished in 0.01 seconds
39
+ ```
40
+
41
+ In test mode, errors and warnings go to the log files, not the console, so the output is just the test results (see Application, Logging). The summary says when any were logged; see Errors and Warnings, below.
42
+
43
+ When the run is over, gloo exits with status 0 if it passed, and 1 if any test failed or the summary reports errors or warnings that no test expected. A missing test file or folder counts too. So a script or CI job can tell a good run from a bad one:
44
+
45
+ ```
46
+ gloo --test test/ && echo "all good"
47
+ ```
48
+
49
+ ## Writing a Test
50
+
51
+ A test file holds one root container with `[test]` objects inside it. By convention, the root is `tests`, and each file adds its own area under it with nested container shorthand (see Language, Objects), so many test files can share the `tests` root:
52
+
53
+ ```gloo
54
+ tests.math [can] :
55
+
56
+ total [int] : 0
57
+
58
+ adds_up [test] :
59
+ description [string] : Adding two numbers.
60
+ on_test [script] :
61
+ eval 2 + 2
62
+ put it into ^^.total
63
+ eval ^^.total = 4
64
+ assert "expected 2 + 2 to be 4"
65
+ ```
66
+
67
+ A `[test]` has two children:
68
+
69
+ - `description` — what the test checks. It's shown with the test's path when the test fails.
70
+ - `on_test` — the script that does the work and makes the assertions.
71
+
72
+ Inside `on_test`, `^` is the test and `^^` is the container that holds it, so `^^.total` is the `total` object beside the test. Objects a test works with usually sit in the container, next to the tests.
73
+
74
+ ## Assertions
75
+
76
+ `assert` and `refute` look at `it`. `assert` passes if `it` is true; `refute` passes if `it` is false. Each takes an optional message, which is shown if it fails, so write it as what was expected:
77
+
78
+ ```gloo
79
+ check ^^.name for blank?
80
+ refute "expected the name to be set"
81
+
82
+ eval ^^.count > 0
83
+ assert "expected at least one item"
84
+ ```
85
+
86
+ `expect` is another name for `assert`, and `expect_not` for `refute`.
87
+
88
+ So a test usually puts the thing it's checking into `it` first: with `eval` for a comparison, `check` for a question (`blank?`, `contains?`, `exists?`), or a message or verb that sets `it` (see Language, Syntax, It). A test can have any number of assertions, and it fails if any one of them fails.
89
+
90
+ ## Test Order and Setup
91
+
92
+ Tests run in a random order: the files are shuffled, and so are the tests within each file. A test mustn't depend on another test having run first. Each one sets up what it needs and cleans up after itself.
93
+
94
+ Between files, the heap is emptied: objects a file loads are gone before the next file runs. Within a file, the tests share the file's objects, so a test that uses one should set it at the start rather than rely on its declared value. For example, `put false into ^^.err_fired` before the step that should set it.
95
+
96
+ Setup that's the same for every test in a file, and that no test changes, can go in an `on_load` script in the file's container. It runs when the file loads, before any of its tests:
97
+
98
+ ```gloo
99
+ tests.files [can] :
100
+
101
+ scratch [file] :
102
+
103
+ # Runs when this file loads, before any test.
104
+ on_load [script] :
105
+ put $.gloo_tmp '/my_scratch.gloo' into ^.scratch
106
+ ```
107
+
108
+ ## Errors and Warnings
109
+
110
+ The run keeps track of the errors and warnings logged by each test. If any were logged, the summary gets an extra line naming the tests that logged them:
111
+
112
+ ```
113
+ Tests: 4 • Passed: 4 • Failed: 0
114
+ Assertions: 4 • Files: 1
115
+ 1 error logged during the run; see ~/gloo/logs/error.log
116
+ tests.files.missing_file_is_an_error (1 error)
117
+ Tests finished in 0.0 seconds
118
+ ```
119
+
120
+ Problems logged while a test file loads, outside any test, are counted too, as `(outside tests: …)`. A clean run has no such line. Treat one as a problem even when every test passed: something went wrong that no assertion caught.
121
+
122
+ A test that triggers an error or a warning on purpose (to check an `on_error` handler, or that bad input is reported) sets `expect_errors` to true. What it logs is then left out of the summary:
123
+
124
+ ```gloo
125
+ tests.files [can] :
126
+
127
+ err_fired [bool] : false
128
+
129
+ on_error [script] :
130
+ put true into ^.err_fired
131
+
132
+ missing_file_is_an_error [test] :
133
+ description [string] : Loading a file that isn't there fires on_error.
134
+ expect_errors [bool] : true
135
+ on_test [script] :
136
+ put false into ^^.err_fired
137
+ load no_such_file_xyz
138
+ eval ^^.err_fired = true
139
+ assert "expected on_error to fire"
140
+ ```
141
+
142
+ ## Tests Meant to Fail
143
+
144
+ A test that's meant to fail, such as one that shows an assertion really does fail when it should, sets `expect_fail` to true. It then passes if at least one of its assertions fails, and fails if none does:
145
+
146
+ ```gloo
147
+ false_fails_an_assert [test] :
148
+ description [string] : assert fails when it is false.
149
+ expect_fail [bool] : true
150
+ on_test [script] :
151
+ eval 1 = 2
152
+ assert "this one is meant to fail"
153
+ ```
154
+
155
+ `expect_fail` can't tell which assertion failed, or why, so a test can fail for the wrong reason and still pass. Keep a test like this to the one thing it checks. That's good practice for every test, but it matters most here.
156
+
157
+ ## Files in Tests
158
+
159
+ **Scratch files.** A test that needs a file it writes and then deletes should put it in the gloo tmp folder, `$.gloo_tmp`, rather than `/tmp`, which doesn't exist on Windows (see Application, The Gloo Home Directory). Set the path once in `on_load`, as in Test Order and Setup, above. To load the file, put the path in parentheses so it's worked out rather than taken literally (see Language, Syntax, Computed Arguments). Unload what was loaded and delete the file at the end of the test:
160
+
161
+ ```gloo
162
+ tell ^^.scratch to write ( ^^.source )
163
+ load ( ^^.scratch )
164
+ # ... assertions ...
165
+ tell demo to unload
166
+ tell ^^.scratch to delete
167
+ ```
168
+
169
+ **Fixtures.** A file the tests read but never change can be kept next to the test file, for example in a `fixtures` folder. Name it something other than `*.test.gloo`, so it isn't run as a test. `check ^^ for source_dir` gives the folder of the test file (see Language, Objects, Where an Object Came From):
170
+
171
+ ```gloo
172
+ tests.files [can] :
173
+
174
+ where [string] :
175
+ fixture [string] : /fixtures/sample.gloo
176
+
177
+ loads_its_fixture [test] :
178
+ description [string] : A fixture next to this file can be loaded.
179
+ on_test [script] :
180
+ check ^^ for source_dir
181
+ put it into ^^.where
182
+ load ( ^^.where ^^.fixture )
183
+ eval sample.name = 'from the fixture'
184
+ assert "expected the fixture to load"
185
+ tell sample to unload
186
+ ```
187
+
188
+ **Broken source on purpose.** To test how gloo handles a mistake in a file, such as a missing `]` or a declaration with no name, don't put the mistake in the test file itself. It would be reported while the file loads, outside any test. Instead, keep the broken source in a `[text]` object, write it to a scratch file inside the test, and load it there. The problem then belongs to that test, which sets `expect_errors`:
189
+
190
+ ```gloo
191
+ broken_src [text] : BEGIN
192
+ broken_demo [can] :
193
+ count [int : 3
194
+ END
195
+
196
+ broken_source_still_loads [test] :
197
+ description [string] : A type missing its ] is still read as that type.
198
+ expect_errors [bool] : true
199
+ on_test [script] :
200
+ tell ^^.scratch to write ( ^^.broken_src )
201
+ load ( ^^.scratch )
202
+ eval broken_demo.count = 3
203
+ assert "expected the value 3"
204
+ tell broken_demo to unload
205
+ tell ^^.scratch to delete
206
+ ```
data/docs/verbs.md CHANGED
@@ -58,6 +58,13 @@ tell {path.to.object} to {message}
58
58
  > show it
59
59
  ```
60
60
 
61
+ `source_dir` and `source_file` are two more: they put the folder, or the full path, of the file the object was loaded from in `it` — blank if it didn't come from a file (see Language, Objects — Where an Object Came From).
62
+
63
+ ```gloo
64
+ > check my.obj for source_dir
65
+ > show it
66
+ ```
67
+
61
68
  ## Put
62
69
 
63
70
  `put` evaluates an expression and stores the result in an object.
data/lib/VERSION CHANGED
@@ -1 +1 @@
1
- 7.0.4
1
+ 7.1.1
data/lib/VERSION_NOTES CHANGED
@@ -1,3 +1,20 @@
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
+
8
+ 7.1 - 2026.10.23
9
+ - Loading an empty gloo file is now allowed and only gives a warning.
10
+ - Adds configuration setting for tmp directory.
11
+ - load (file, lib, ext), save … to, context and exists? accept ( expression ) in place of a literal name or path. For example, load ( $.gloo_tmp '/x.gloo' ).
12
+ - save … to with a blank path reports the missing-path error.
13
+ - Updates for more consistent results in `it`.
14
+ - Get source file path and name for object.
15
+ - Adds doc for gloo tests.
16
+
17
+
1
18
  7.0.4 - 2026.10.19
2
19
  - Documentation update.
3
20
 
@@ -169,7 +186,7 @@
169
186
 
170
187
 
171
188
  5.1.0 - 2026.02.15
172
- - Fixes issue with int * dec
189
+ - Fixes issue with int * dec
173
190
  - Some code cleanup
174
191
 
175
192
 
@@ -396,4 +413,3 @@ adds tag helper for apple touch icon
396
413
 
397
414
  2.0 - 2023.05.22
398
415
  - Version 2 merges gloo-lang back into a single gloo gem.
399
-
@@ -154,21 +154,31 @@ module Gloo
154
154
  @args.files.each { |f| @persist_man.load( f ) }
155
155
  end
156
156
 
157
- #
157
+ #
158
158
  # Run in gloo unit test mode.
159
+ # Then quit, with exit status 1 if the run failed (see run_tests),
160
+ # so a calling script or CI job can tell.
159
161
  #
160
162
  def run_test
161
163
  @log.debug 'Running in Test mode…'
162
-
163
- begin
164
- @lib_manager.load_lib TEST_LIB_NAME
165
- TestRunner.new( self, @args.files ).run
166
- rescue StandardError, ScriptError => ex
167
- handle_exception ex
168
- end
169
-
164
+ passed = run_tests
170
165
  @log.debug 'Tests complete.'
171
166
  quit
167
+ exit( 1 ) unless passed
168
+ end
169
+
170
+ #
171
+ # Load gloo-test and run the tests given on the command line.
172
+ # True unless the run reports that it failed, or something went
173
+ # wrong running it. An older gloo-test whose run doesn't report
174
+ # counts as passed, as it always has.
175
+ #
176
+ def run_tests
177
+ @lib_manager.load_lib TEST_LIB_NAME
178
+ return TestRunner.new( self, @args.files ).run != false
179
+ rescue StandardError, ScriptError => ex
180
+ handle_exception ex
181
+ return false
172
182
  end
173
183
 
174
184
  #
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
  #