gloo 7.1.2 → 7.2
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/Rakefile +23 -0
- data/docs/application.md +47 -2
- data/docs/development.md +10 -0
- data/docs/iterators.md +22 -5
- data/docs/language_objects.md +20 -15
- data/docs/language_scripting.md +6 -2
- data/docs/plugins.md +1 -1
- data/docs/testing.md +4 -7
- data/docs/verbs.md +23 -3
- data/lib/VERSION +1 -1
- data/lib/VERSION_NOTES +13 -0
- data/lib/dependencies.rb +4 -1
- data/lib/gloo/app/engine.rb +18 -6
- data/lib/gloo/core/obj.rb +0 -23
- data/lib/gloo/exec/script.rb +12 -2
- data/lib/gloo/objs/basic/script.rb +5 -28
- data/lib/gloo/objs/ctrl/each.rb +4 -3
- data/lib/gloo/objs/ctrl/repeat.rb +4 -3
- data/lib/gloo/persist/declaration_ledger.rb +9 -9
- data/lib/gloo/persist/declaration_writer.rb +208 -0
- data/lib/gloo/persist/disc_mech.rb +70 -39
- data/lib/gloo/persist/file_loader.rb +125 -66
- data/lib/gloo/persist/file_saver.rb +130 -198
- data/lib/gloo/persist/file_storage.rb +15 -4
- data/lib/gloo/persist/file_style.rb +89 -0
- data/lib/gloo/persist/line_splitter.rb +16 -1
- data/lib/gloo/persist/persist_man.rb +112 -30
- data/lib/gloo/persist/save_index.rb +211 -0
- data/lib/gloo/persist/script_body_collector.rb +16 -21
- data/lib/gloo/persist/source/blank_node.rb +10 -3
- data/lib/gloo/persist/source/comment_node.rb +10 -2
- data/lib/gloo/persist/source/directive_node.rb +10 -2
- data/lib/gloo/persist/source/obj_node.rb +174 -9
- data/lib/gloo/persist/source/source_doc.rb +54 -0
- data/lib/gloo/persist/subtree_extractor.rb +9 -37
- data/lib/gloo/persist/trivia_buffer.rb +7 -7
- data/lib/gloo/verbs/break.rb +99 -10
- data/lib/gloo/verbs/load.rb +27 -21
- data/lib/gloo/verbs/move.rb +42 -3
- data/lib/gloo/verbs/save.rb +26 -4
- data/test.gloo/app/exit_status.test.gloo +34 -0
- data/test.gloo/app/fixtures/exit_after_error.gloo +9 -0
- data/test.gloo/lang/declaration.test.gloo +36 -0
- data/test.gloo/lang/fixtures/mixed_indent.gloo +5 -0
- data/test.gloo/lang/naming.test.gloo +44 -6
- data/test.gloo/verbs/break.test.gloo +104 -11
- data/test.gloo/verbs/fixtures/load_beside.gloo +6 -0
- data/test.gloo/verbs/fixtures/lossless_text.gloo +7 -0
- data/test.gloo/verbs/fixtures/lossless_text_saved.gloo +9 -0
- data/test.gloo/verbs/load.test.gloo +18 -0
- data/test.gloo/verbs/move.test.gloo +39 -0
- data/test.gloo/verbs/save.test.gloo +239 -0
- metadata +10 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b5cc04330edd79c2663408d7afbedc9195dbd994c31ce0a7cd6a591ce9276c56
|
|
4
|
+
data.tar.gz: d4f004bccdc1dbae382b2d3106154557b90e6b236447b04c8c5b5fcf05ebeffc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8a0ecc8a6e085ce93a55ec93de2a629c3fa023cfd4982fd0cc1005cc954c737ae81c4dd2d2299924623692546360df79a15b856d138201671a5c1181cccfb7c0
|
|
7
|
+
data.tar.gz: 603536ee8defb9805546bd785b51afcea41809e67a76118a0d750ea8862e5929c5aca2e68cadd22c4488c12f52618864f1727bc2feb2e6f8e504e95035c63b19
|
data/Rakefile
CHANGED
|
@@ -9,3 +9,26 @@ Rake::TestTask.new(:test) do |t|
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
task :default => :test
|
|
12
|
+
|
|
13
|
+
#
|
|
14
|
+
# Load and save a copy of every .gloo file under the given folders, and
|
|
15
|
+
# list the files that come back different. The originals are never
|
|
16
|
+
# written. For example:
|
|
17
|
+
# rake "round_trip[~/gloo/app,~/dev/gloo_apps]"
|
|
18
|
+
#
|
|
19
|
+
desc 'Round-trip copies of .gloo files and list the ones that change'
|
|
20
|
+
task :round_trip do |_task, args|
|
|
21
|
+
$LOAD_PATH.unshift File.expand_path( 'lib', __dir__ ), File.expand_path( 'test', __dir__ )
|
|
22
|
+
require 'gloo'
|
|
23
|
+
require 'round_trip_check'
|
|
24
|
+
|
|
25
|
+
check = RoundTripCheck.new do
|
|
26
|
+
engine = Gloo::App::Engine.new( Gloo::App::EngineContext.new( [ '--quiet' ] ) )
|
|
27
|
+
engine.log.quiet = true
|
|
28
|
+
engine.start
|
|
29
|
+
engine
|
|
30
|
+
end
|
|
31
|
+
changed, count = check.check_folders( args.extras )
|
|
32
|
+
changed.each { |path, diff| puts "#{path}\n #{diff}" }
|
|
33
|
+
puts "#{changed.size} of #{count} files changed."
|
|
34
|
+
end
|
data/docs/application.md
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
- The Gloo Home Directory
|
|
6
6
|
- Running Gloo
|
|
7
7
|
- Modes
|
|
8
|
+
- Script Mode and App Mode
|
|
8
9
|
- Configuration
|
|
9
10
|
- Logging
|
|
10
11
|
- Help
|
|
@@ -35,6 +36,8 @@ Gloo can also be started from another Ruby program; see Development, Running Glo
|
|
|
35
36
|
|
|
36
37
|
If a file named on the command line can't be found (a typo, the wrong folder, a missing `.gloo` extension on a full path), gloo prints `File '{name}' was not found.` to stderr and exits with a non-zero status. Any other load or run error also exits non-zero — including syntax errors in a file that otherwise loaded (gloo keeps loading past them, reporting each with its file and line) — so a shell script calling gloo can tell a failed run from a clean one.
|
|
37
38
|
|
|
39
|
+
The exit status covers the whole run, not just its last line: gloo exits with status 1 if any error, or any Ruby exception, happened at any point while the files were loading or running, and 0 otherwise. An error still counts when an `on_error` or `on_exception` handler ran for it, because a handler reports the problem, it doesn't undo it. Warnings don't count. This applies when gloo runs files (Script mode); App and CLI modes don't set an exit status, and Test mode has its own (see the `--test` option below).
|
|
40
|
+
|
|
38
41
|
When specifying a file there are a couple ways to reference the gloo file to open:
|
|
39
42
|
|
|
40
43
|
- By Path
|
|
@@ -46,11 +49,11 @@ When specifying a file there are a couple ways to reference the gloo file to ope
|
|
|
46
49
|
```
|
|
47
50
|
- By Project
|
|
48
51
|
- Use the path within the project folder.
|
|
49
|
-
-
|
|
52
|
+
- The `.gloo` extension can be left off.
|
|
50
53
|
- The location of the project folder is part of the configuration.
|
|
51
54
|
- For example:
|
|
52
55
|
```
|
|
53
|
-
gloo my_project/file
|
|
56
|
+
gloo my_project/file
|
|
54
57
|
```
|
|
55
58
|
|
|
56
59
|
## Modes
|
|
@@ -87,6 +90,48 @@ When specifying a file there are a couple ways to reference the gloo file to ope
|
|
|
87
90
|
--help - Show the help screen
|
|
88
91
|
```
|
|
89
92
|
|
|
93
|
+
## Script Mode and App Mode
|
|
94
|
+
|
|
95
|
+
Gloo runs a file in one of two ways, and they're meant for different things.
|
|
96
|
+
|
|
97
|
+
**Script mode** (`gloo path/to/file.gloo`) is a transaction: gloo loads the file, runs its `on_load`, and quits. Use it for a job that does something and finishes.
|
|
98
|
+
|
|
99
|
+
**App mode** (`gloo --app path/to/folder`) is a process: gloo loads the folder's `start.gloo`, and the app stays up — a shell taking commands, a web server, a connection — until it's told to stop. The folder is the app's root: `gloo.app` is that folder, and words after it on the command line are a command for the app, not files to load.
|
|
100
|
+
|
|
101
|
+
An app only runs in App mode. If an app's `start.gloo` is run as a script by mistake, starting a shell (gloo-cli) or a web server (gloo-web) is an error that says how to run it, for example `Could not start the shell: an app runs in App mode. Run it with 'gloo --app ~/gloo/app/notes'.`, and gloo exits with status 1.
|
|
102
|
+
|
|
103
|
+
The two also differ in what the **project folder** is. In App mode it's the app's folder. Otherwise it's where you keep your projects: the `project_path` setting (see Configuration), `~/gloo/projects` unless you change it, and `gloo.gloo_projects`.
|
|
104
|
+
|
|
105
|
+
### Where a Relative Load Looks
|
|
106
|
+
|
|
107
|
+
A `load` with a full path, or one starting with `~`, loads that file. A relative name is looked for in a few places, in order, and the first place that has it wins:
|
|
108
|
+
|
|
109
|
+
| Mode | Looks in, in order |
|
|
110
|
+
|---|---|
|
|
111
|
+
| App mode | the app folder → next to the loading file → the gloo home |
|
|
112
|
+
| Script and Test modes | next to the loading file → the projects folder → the gloo home |
|
|
113
|
+
| A command typed at the prompt | the working folder → the projects folder → the gloo home (in App mode: the app folder → the working folder → the gloo home) |
|
|
114
|
+
|
|
115
|
+
"Next to the loading file" is the folder of the file the running script was declared in — the folder `check {obj} for source_dir` gives for it. A script declared in one file and run from another still looks next to its own file. A script that didn't come from a file (one created while gloo runs) skips this step. A command typed at the prompt has no file, so the working folder stands in for it; a load inside a file never depends on where gloo was started.
|
|
116
|
+
|
|
117
|
+
A folder load (`load parts/*`) is looked for the same way, and the first place with matching `.gloo` files is used. The gloo home comes last in every mode, so files kept there (such as a `common` folder shared by several apps) can be loaded from anywhere.
|
|
118
|
+
|
|
119
|
+
For example, a small project:
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
report/
|
|
123
|
+
start.gloo # on_load: load parts/*
|
|
124
|
+
parts/
|
|
125
|
+
totals.gloo
|
|
126
|
+
format.gloo
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Run as a script (`gloo report/start.gloo`), `load parts/*` finds `report/parts` next to `start.gloo`, whatever folder gloo was started from. Run as an app (`gloo --app report`), it finds the same folder as part of the app. Inside an app, a name means the same thing in every file: `load parts/format` gets `report/parts/format.gloo` whichever of the app's files says it.
|
|
130
|
+
|
|
131
|
+
Files named on the command line are found differently: as given (from the working folder), then in the projects folder, then in the gloo home (see Running Gloo, above).
|
|
132
|
+
|
|
133
|
+
A new file saved with a relative name (`save notes to lists/notes`, or `save notes` for a top-level object that has no file yet) goes in the first place a load would look: the app folder in App mode, otherwise next to the saving script's file, or the working folder for a command typed at the prompt. A script that didn't come from a file saves into the projects folder. So a script that saves `lists/notes` can load it back with `load lists/notes`.
|
|
134
|
+
|
|
90
135
|
## Configuration
|
|
91
136
|
|
|
92
137
|
When gloo runs, it looks for a configuration file which it expects to find in `~/gloo/config/`. If there is no `gloo.yml` file in that directory, one will be created and default values added.
|
data/docs/development.md
CHANGED
|
@@ -78,6 +78,16 @@ Use plain `ruby`, not `bundle exec`. The Gemfile doesn't include `gloo-test` or
|
|
|
78
78
|
|
|
79
79
|
The same command runs the gloo tests of core libraries and extensions; add their `test/` folders to the list of paths.
|
|
80
80
|
|
|
81
|
+
### Round-trip check
|
|
82
|
+
|
|
83
|
+
Saving a file that was loaded and not changed must give back the same bytes. `test/persist/round_trip_test.rb` checks that for every `.gloo` file in `test.gloo/` and `test/gloo/projects/` (add a fixture to `test/gloo/projects/round_trip/` for a new construct). To check other folders, such as apps:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
rake "round_trip[~/gloo/app,~/dev/gloo_apps]"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
It copies each file to a scratch folder, loads it without running any scripts or `load lib` lines, saves it, and lists the files that came back different with their first changed line. The originals are never written.
|
|
90
|
+
|
|
81
91
|
## Documentation
|
|
82
92
|
|
|
83
93
|
The narrative documentation is the set of flat pages in `docs/`, linked from the README. Two conventions keep them readable both on GitHub and in a terminal:
|
data/docs/iterators.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
- Each Line
|
|
10
10
|
- Each Word
|
|
11
11
|
- Repeat
|
|
12
|
-
- Ending a Loop
|
|
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
|
|
333
|
+
## Skipping an Item or Ending a Loop
|
|
334
334
|
|
|
335
|
-
`break` in a loop's `do` script
|
|
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`
|
|
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/language_objects.md
CHANGED
|
@@ -15,18 +15,19 @@
|
|
|
15
15
|
Object names are single words (no spaces) and conform to the following:
|
|
16
16
|
|
|
17
17
|
- Spaces in names are not allowed.
|
|
18
|
-
-
|
|
19
|
-
-
|
|
18
|
+
- A name is unique within its container.
|
|
19
|
+
- Declaring a name again, in the same file or another one, names the same object: its children are added to the first one's, and its value is ignored (with a warning when it's different). `create` with a name that's already there leaves the object as it is, and `move` won't move an object into a container that already has its name.
|
|
20
20
|
- Names can be capitalized or lower case.
|
|
21
|
-
- But names with different cases are not
|
|
21
|
+
- But names with different cases are not different names: `a` and `A` are the same object.
|
|
22
22
|
- Names can contain numbers and special characters.
|
|
23
23
|
|
|
24
24
|
```gloo
|
|
25
25
|
#
|
|
26
26
|
# Example of object naming.
|
|
27
27
|
#
|
|
28
|
-
# NOTE that the second 'a' and the 'A'
|
|
29
|
-
# 'naming.a'
|
|
28
|
+
# NOTE that the second 'a' and the 'A' lines name the same object as
|
|
29
|
+
# the first: 'naming.a' is 'First A', and loading warns that the other
|
|
30
|
+
# two values are ignored.
|
|
30
31
|
#
|
|
31
32
|
naming [container] :
|
|
32
33
|
on_load [script] :
|
|
@@ -35,8 +36,8 @@ naming [container] :
|
|
|
35
36
|
show naming.1
|
|
36
37
|
msg!$% [string] : Naming stuff
|
|
37
38
|
a [string] : First A
|
|
38
|
-
a [string] : Second A
|
|
39
|
-
A [string] : Capital A
|
|
39
|
+
a [string] : Second A
|
|
40
|
+
A [string] : Capital A
|
|
40
41
|
1 [string] : One
|
|
41
42
|
```
|
|
42
43
|
|
|
@@ -121,20 +122,24 @@ Every object can say which file it was loaded from: `check {obj} for
|
|
|
121
122
|
source_file` puts the file's full path in `it`, and `check {obj} for
|
|
122
123
|
source_dir` puts that file's folder (with no trailing slash). This lets a
|
|
123
124
|
script reach files stored next to it without a hard-coded path. For
|
|
124
|
-
example, a
|
|
125
|
+
example, a script can read a data file that sits beside its own file:
|
|
125
126
|
|
|
126
127
|
```gloo
|
|
127
|
-
|
|
128
|
+
report [can] :
|
|
128
129
|
where [string] :
|
|
129
|
-
|
|
130
|
+
data [file] :
|
|
131
|
+
rows [string] :
|
|
130
132
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
133
|
+
on_load [script] :
|
|
134
|
+
check ^ for source_dir
|
|
135
|
+
put it into ^.where
|
|
136
|
+
put ^.where '/data/rows.txt' into ^.data
|
|
137
|
+
tell ^.data to read ( ^.rows )
|
|
136
138
|
```
|
|
137
139
|
|
|
140
|
+
(A `load` doesn't need this: a relative name already looks next to the
|
|
141
|
+
loading file first. See Application, Script Mode and App Mode.)
|
|
142
|
+
|
|
138
143
|
The answer comes from the nearest object, going up from the one asked,
|
|
139
144
|
that a loaded file declares. So a nested object answers with its file,
|
|
140
145
|
even when its root container (like `tests` above) is declared in many
|
data/docs/language_scripting.md
CHANGED
|
@@ -9,7 +9,11 @@
|
|
|
9
9
|
|
|
10
10
|
## Gloo Script Files
|
|
11
11
|
|
|
12
|
-
Gloo scripts are stored in regular text files with the `.gloo` extension.
|
|
12
|
+
Gloo scripts are stored in regular text files with the `.gloo` extension. A file is UTF-8, whatever the locale gloo runs in (cron and launchd, for example, give a process a plain ASCII locale). A line with bytes that aren't UTF-8 still loads, with a warning: those bytes read as `�` (U+FFFD), and the file keeps them, so saving it doesn't change them.
|
|
13
|
+
|
|
14
|
+
Indentation is what puts an object under another: one level deeper is a tab or two spaces. They can be mixed, from line to line or on one line (a tab and two spaces is two levels).
|
|
15
|
+
|
|
16
|
+
An object is declared as `name [type] : value`. Spaces just inside the brackets don't matter: `[ int ]` and `[int ]` are `[int]`.
|
|
13
17
|
|
|
14
18
|
Conventions:
|
|
15
19
|
|
|
@@ -85,7 +89,7 @@ constants [can] :
|
|
|
85
89
|
|
|
86
90
|
## Line Continuation
|
|
87
91
|
|
|
88
|
-
In gloo scripting, a line continuation is done by ending a line with a backslash (`\`). Logical lines can be split across multiple physical lines of text in scripts. A line break is otherwise an indication of a new statement.
|
|
92
|
+
In gloo scripting, a line continuation is done by ending a line with a backslash (`\`). Logical lines can be split across multiple physical lines of text in scripts. A line break is otherwise an indication of a new statement. Inside a `BEGIN` … `END` block a backslash is just a character: the block's lines are its value, exactly as written.
|
|
89
93
|
|
|
90
94
|
```gloo
|
|
91
95
|
#
|
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/docs/testing.md
CHANGED
|
@@ -166,25 +166,22 @@ A test that's meant to fail, such as one that shows an assertion really does fai
|
|
|
166
166
|
tell ^^.scratch to delete
|
|
167
167
|
```
|
|
168
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.
|
|
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. A relative `load` looks next to the test file first (see Application, Script Mode and App Mode), so the fixture loads by its plain name:
|
|
170
170
|
|
|
171
171
|
```gloo
|
|
172
172
|
tests.files [can] :
|
|
173
173
|
|
|
174
|
-
where [string] :
|
|
175
|
-
fixture [string] : /fixtures/sample.gloo
|
|
176
|
-
|
|
177
174
|
loads_its_fixture [test] :
|
|
178
175
|
description [string] : A fixture next to this file can be loaded.
|
|
179
176
|
on_test [script] :
|
|
180
|
-
|
|
181
|
-
put it into ^^.where
|
|
182
|
-
load ( ^^.where ^^.fixture )
|
|
177
|
+
load fixtures/sample
|
|
183
178
|
eval sample.name = 'from the fixture'
|
|
184
179
|
assert "expected the fixture to load"
|
|
185
180
|
tell sample to unload
|
|
186
181
|
```
|
|
187
182
|
|
|
183
|
+
For anything else that needs the test file's folder, such as the path of a data file to read or a program to run, `check ^^ for source_dir` gives it (see Language, Objects, Where an Object Came From).
|
|
184
|
+
|
|
188
185
|
**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
186
|
|
|
190
187
|
```gloo
|
data/docs/verbs.md
CHANGED
|
@@ -86,10 +86,10 @@ If the destination doesn't exist, or the expression can't be worked out (eg. it
|
|
|
86
86
|
|
|
87
87
|
## Load & Save
|
|
88
88
|
|
|
89
|
-
`load` reads a `.gloo` file into the heap and runs its `on_load` script. Give a
|
|
89
|
+
`load` reads a `.gloo` file into the heap and runs its `on_load` script. Give a relative name, a full path, or one starting with `~` for your home folder; the `.gloo` extension is optional. A relative name is looked for next to the file the loading script came from, in the projects folder, and in the gloo home; in App mode the app folder comes first (see Application, Script Mode and App Mode, for the order in each mode). `*` in place of a file name loads every `.gloo` file in a folder, found the same way. Anything before the `*` is the start of the file names, so `my/app/a*` loads just the files whose names start with `a`.
|
|
90
90
|
|
|
91
91
|
```gloo
|
|
92
|
-
> load
|
|
92
|
+
> load parts/config
|
|
93
93
|
> load my/app/*
|
|
94
94
|
> load ~/.my_app/settings.gloo
|
|
95
95
|
> load ~/.my_app/plugins/*
|
|
@@ -99,6 +99,8 @@ If the name can't be resolved to a file, `load` reports `File '{name}' was not f
|
|
|
99
99
|
|
|
100
100
|
`save` writes loaded objects back to their files. With no argument it saves every open file; with an object it saves the file (or files) that object's tree came from; with `to {path}` it **extracts** — moves the object (and its descendants) out of whichever file currently owns them, into a new file, under their full dotted path.
|
|
101
101
|
|
|
102
|
+
A new file — `save {obj} to {path}`, or `save {obj}` for a top-level object with no file yet (named after the object) — goes where a relative `load` would look first: in App mode the app folder; otherwise next to the file the saving script came from, or the working folder for a command typed at the prompt (see Application, Script Mode and App Mode). A script that didn't come from a file saves into the projects folder. A full path, or one starting with `~`, is used as given. An object that's already loaded always saves back to its own file.
|
|
103
|
+
|
|
102
104
|
```gloo
|
|
103
105
|
> save # every open file
|
|
104
106
|
> save config # just config's file
|
|
@@ -113,11 +115,29 @@ An object's doc comment goes wherever the object goes: it moves with a moved or
|
|
|
113
115
|
|
|
114
116
|
A save never loses or alters data: loading the saved files again gives back the same objects, with the same values and docs, under the same parents. The interpreter decides how a changed or new declaration is laid out; only what it says has to come back the same.
|
|
115
117
|
|
|
118
|
+
How a change is written:
|
|
119
|
+
|
|
120
|
+
- A changed declaration keeps its name, type and spacing as written (an untyped `plain : old` stays untyped); only its value is written again.
|
|
121
|
+
- A new object goes after the last child of its parent's first declaration, indented like its neighbours, with its type spelled the way the file spells it (`[int]` in a file of `[int]`s), so it looks like the rest of the file. A new child of a container that only exists through a dotted name (`page.core.users [can] :` makes `page` and `core`) is written as a dotted line too: `page.core.x [string] : v`.
|
|
122
|
+
- A moved object takes its first declaration and its doc comment with it, at its new depth. Moving it into another file's container saves that file too.
|
|
123
|
+
- An object deleted and then created again is a new object: it's written where new objects go, without the old doc comment.
|
|
124
|
+
- A new top-level object isn't added to an existing file, so a bare `save` leaves it out and warns, naming it (`Not saved (no file yet): notes. Use save notes.`). `save {obj}` writes it to a file of its own, which later saves include.
|
|
125
|
+
- A file that doesn't end with a line break still doesn't after a save.
|
|
126
|
+
|
|
127
|
+
An object can be declared on more than one line — the same name twice in one container, or in more than one file (see Language, Objects, Object Naming). It's one object: its first declaration holds its value, so a change rewrites only that line, and the later lines stay exactly as written. Deleting or moving the object takes all of its lines with it, in every file, so a reload doesn't bring it back.
|
|
128
|
+
|
|
129
|
+
A few things can't be written back exactly; save writes the closest thing and, for a value, warns `Could not save '{path}' exactly: {why}.`:
|
|
130
|
+
|
|
131
|
+
- A multi-line value is written as a `BEGIN` … `END` block, its lines exactly as they are (a block's lines are its value, so they're not indented to match). A block value always ends with a line break, so a value without one gains it; a line that is just `END` would end the block early; and bytes that aren't UTF-8 are written as they are.
|
|
132
|
+
- A value ending in `\` or `BEGIN` is written as a block, since on the line it would be read as a continuation or a block start.
|
|
133
|
+
- A changed script body is written from its commands, at its original indentation; the comments and blank lines inside it are lost. (An untouched one is kept exactly.)
|
|
134
|
+
- A declaration gloo had to guess at when loading — no name (`[int] : 3` loads as `unnamed_1`) or a missing `]` (`b [int : 3`) — is written as gloo understood it.
|
|
135
|
+
|
|
116
136
|
`save {obj} to {path}` doesn't just write a copy — `obj` stops being declared in its old file, wherever that was. Given `app.core.settings` declared inside `app.gloo`, `save app.core.settings to config/settings` leaves `app.gloo` without a `settings` subtree and writes `config/settings.gloo` with `app.core.settings [can] : ...` at the top level (the `app`/`core` prefix is implied via nested-container shorthand, not repeated as nested declarations) plus everything `settings` owns underneath, comments included. An object with no file of its own yet (brand new, or nested under an object that's never been saved) is simply written fresh — nothing to move.
|
|
117
137
|
|
|
118
138
|
If `obj`'s own subtree already has declarations spread across more than one file (the namespace-merge pattern below, applied *inside* the subtree being extracted, not just above it), extraction refuses rather than guessing which file's slice is authoritative — save each contributing file on its own first.
|
|
119
139
|
|
|
120
|
-
Several files can contribute to one container — declare `app [container] :` in each and add different children. They merge in the heap, and each file's save only rewrites its own declarations. If two files declare the *same* object with different values, the first one loaded wins and `load` logs a warning.
|
|
140
|
+
Several files can contribute to one container — declare `app [container] :` in each and add different children. They merge in the heap, and each file's save only rewrites its own declarations; a new child of `app` goes into the file with `app`'s first declaration (the first loaded). If two files declare the *same* object with different values, the first one loaded wins and `load` logs a warning — the same as two declarations in one file.
|
|
121
141
|
|
|
122
142
|
An object can also save itself: `tell config to save`.
|
|
123
143
|
|
data/lib/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
7.
|
|
1
|
+
7.2
|
data/lib/VERSION_NOTES
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
7.2 - 2026.11.04
|
|
2
|
+
- Update for lossless load and save: fixes a number of bugs.
|
|
3
|
+
- Allow spaces inside type brackets.
|
|
4
|
+
- Improves gloo startup time.
|
|
5
|
+
- A script run now exits 1 if an error or Ruby exception happened anywhere in the run.
|
|
6
|
+
- Improves how load and save verbs use relatives paths.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
7.1.3 - 2026.10.29
|
|
10
|
+
- break in a loop's do skips to the next item again, as it did before 7.1.1.
|
|
11
|
+
- New: break loop ends the whole loop. It's only valid directly in a loop's do script.
|
|
12
|
+
|
|
13
|
+
|
|
1
14
|
7.1.2 - 2026.10.28
|
|
2
15
|
- Fixes bug with create verb and ^.here reference.
|
|
3
16
|
- Behaviour change: an each over lines gives each line without its line ending.
|
data/lib/dependencies.rb
CHANGED
|
@@ -23,7 +23,10 @@ end
|
|
|
23
23
|
#
|
|
24
24
|
# Now that we have the first with some key dependencies
|
|
25
25
|
# at the top, we'll require them.
|
|
26
|
+
# require_relative skips RubyGems' require hook, which
|
|
27
|
+
# would otherwise search the installed gems for each file
|
|
28
|
+
# (about 0.4 s of startup).
|
|
26
29
|
#
|
|
27
30
|
files.each do |ruby_file|
|
|
28
|
-
|
|
31
|
+
require_relative ruby_file
|
|
29
32
|
end
|
data/lib/gloo/app/engine.rb
CHANGED
|
@@ -17,7 +17,7 @@ module Gloo
|
|
|
17
17
|
#
|
|
18
18
|
TEST_LIB_NAME = 'test'
|
|
19
19
|
|
|
20
|
-
attr_reader :settings, :log, :running_app
|
|
20
|
+
attr_reader :settings, :log, :running_app, :run_error_count
|
|
21
21
|
attr_reader :args, :mode, :running, :platform,
|
|
22
22
|
:dictionary, :parser, :heap, :factory,
|
|
23
23
|
:ext_manager, :lib_manager
|
|
@@ -43,6 +43,7 @@ module Gloo
|
|
|
43
43
|
|
|
44
44
|
@handling_exception = false
|
|
45
45
|
@handling_error = false
|
|
46
|
+
@run_error_count = 0
|
|
46
47
|
@log.debug 'engine intialized...'
|
|
47
48
|
end
|
|
48
49
|
|
|
@@ -136,15 +137,14 @@ module Gloo
|
|
|
136
137
|
|
|
137
138
|
#
|
|
138
139
|
# Run files specified on the CLI.
|
|
139
|
-
# Then quit, with
|
|
140
|
-
#
|
|
141
|
-
#
|
|
140
|
+
# Then quit, with exit status 1 if anything went wrong at any
|
|
141
|
+
# point in the run (see run_failed?), so that a calling shell
|
|
142
|
+
# script can tell a bad run from a good one.
|
|
142
143
|
#
|
|
143
144
|
def run_files
|
|
144
145
|
load_files
|
|
145
|
-
failed = error?
|
|
146
146
|
quit
|
|
147
|
-
exit( 1 ) if
|
|
147
|
+
exit( 1 ) if run_failed?
|
|
148
148
|
end
|
|
149
149
|
|
|
150
150
|
#
|
|
@@ -337,6 +337,16 @@ module Gloo
|
|
|
337
337
|
return !@heap.error.value.nil?
|
|
338
338
|
end
|
|
339
339
|
|
|
340
|
+
#
|
|
341
|
+
# Did anything go wrong since the engine started: an error or
|
|
342
|
+
# a Ruby exception, whether or not on_error or on_exception
|
|
343
|
+
# handled it? A handler reports, it doesn't undo the failure.
|
|
344
|
+
# Warnings don't count.
|
|
345
|
+
#
|
|
346
|
+
def run_failed?
|
|
347
|
+
return @run_error_count.positive?
|
|
348
|
+
end
|
|
349
|
+
|
|
340
350
|
#
|
|
341
351
|
# Report an error.
|
|
342
352
|
# Write it to the log and set the heap error value. Always logged;
|
|
@@ -356,6 +366,7 @@ module Gloo
|
|
|
356
366
|
@log.error( location ? "#{location}: #{msg}" : msg )
|
|
357
367
|
return unless @heap
|
|
358
368
|
|
|
369
|
+
@run_error_count += 1
|
|
359
370
|
@heap.error.set_to msg, kind, location
|
|
360
371
|
|
|
361
372
|
return if @handling_error
|
|
@@ -433,6 +444,7 @@ module Gloo
|
|
|
433
444
|
backtrace = format_backtrace( ex )
|
|
434
445
|
@log.error ex.message
|
|
435
446
|
@log.backtrace backtrace
|
|
447
|
+
@run_error_count += 1
|
|
436
448
|
|
|
437
449
|
return if @handling_exception
|
|
438
450
|
|
data/lib/gloo/core/obj.rb
CHANGED
|
@@ -127,29 +127,6 @@ module Gloo
|
|
|
127
127
|
return false
|
|
128
128
|
end
|
|
129
129
|
|
|
130
|
-
#
|
|
131
|
-
# Serialize this object's value for saving to a file.
|
|
132
|
-
# Returns the text that follows "name [type]" on the declaration
|
|
133
|
-
# line -- including the leading ' :' and, for a multi-line string
|
|
134
|
-
# value, the additional indented lines and the closing END marker.
|
|
135
|
-
# indent is the indentation level of the declaration line itself.
|
|
136
|
-
#
|
|
137
|
-
def serialize_value( indent )
|
|
138
|
-
return " : #{value_display}" unless value_string? && value.include?( "\n" )
|
|
139
|
-
|
|
140
|
-
return serialize_block_value( indent )
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
#
|
|
144
|
-
# Serialize a multi-line string value as a BEGIN/END block.
|
|
145
|
-
#
|
|
146
|
-
def serialize_block_value( indent )
|
|
147
|
-
t = "\t" * indent
|
|
148
|
-
inner = "\t" * ( indent + 1 )
|
|
149
|
-
body = value.split( "\n" ).map { |line| "#{inner}#{line}" }.join( "\n" )
|
|
150
|
-
return " : BEGIN\n#{body}\n#{t}END"
|
|
151
|
-
end
|
|
152
|
-
|
|
153
130
|
#
|
|
154
131
|
# Is the value a String?
|
|
155
132
|
#
|
data/lib/gloo/exec/script.rb
CHANGED
|
@@ -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
|
|
@@ -62,29 +62,6 @@ module Gloo
|
|
|
62
62
|
return true
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
-
#
|
|
66
|
-
# Serialize this object's value for saving to a file.
|
|
67
|
-
# A script's value is an Array of command lines (once add_line
|
|
68
|
-
# has been called) or a single-line String. Render as indented
|
|
69
|
-
# body lines below the declaration, gloo's convention for
|
|
70
|
-
# scripts -- not a BEGIN/END block, which is reserved for a
|
|
71
|
-
# literal multi-line string value.
|
|
72
|
-
#
|
|
73
|
-
def serialize_value( indent )
|
|
74
|
-
lines = if value_is_array?
|
|
75
|
-
value
|
|
76
|
-
elsif value_string? && !value.strip.empty?
|
|
77
|
-
[ value ]
|
|
78
|
-
else
|
|
79
|
-
[]
|
|
80
|
-
end
|
|
81
|
-
return ' :' if lines.empty?
|
|
82
|
-
|
|
83
|
-
t = "\t" * ( indent + 1 )
|
|
84
|
-
body = lines.map { |line| "#{t}#{line}" }.join( "\n" )
|
|
85
|
-
return " :\n#{body}"
|
|
86
|
-
end
|
|
87
|
-
|
|
88
65
|
#
|
|
89
66
|
# Get the number of lines in this script.
|
|
90
67
|
#
|
|
@@ -115,15 +92,15 @@ module Gloo
|
|
|
115
92
|
def msg_run
|
|
116
93
|
s = Gloo::Exec::Script.new( @engine, self )
|
|
117
94
|
s.run
|
|
118
|
-
@
|
|
95
|
+
@broke_loop = s.broke_loop?
|
|
119
96
|
end
|
|
120
97
|
|
|
121
98
|
#
|
|
122
|
-
# Did a break end this script's last run? A loop uses this
|
|
123
|
-
# stop when its do script
|
|
99
|
+
# Did a break loop end this script's last run? A loop uses this
|
|
100
|
+
# to stop when its do script says break loop.
|
|
124
101
|
#
|
|
125
|
-
def
|
|
126
|
-
return @
|
|
102
|
+
def broke_loop?
|
|
103
|
+
return @broke_loop == true
|
|
127
104
|
end
|
|
128
105
|
|
|
129
106
|
# ---------------------------------------------------------------------
|
data/lib/gloo/objs/ctrl/each.rb
CHANGED
|
@@ -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
|
|
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?( :
|
|
55
|
+
return o.respond_to?( :broke_loop? ) && o.broke_loop?
|
|
55
56
|
end
|
|
56
57
|
|
|
57
58
|
|