gloo 7.1.3 → 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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +23 -0
  3. data/docs/application.md +47 -2
  4. data/docs/development.md +10 -0
  5. data/docs/language_objects.md +20 -15
  6. data/docs/language_scripting.md +6 -2
  7. data/docs/testing.md +4 -7
  8. data/docs/verbs.md +23 -3
  9. data/lib/VERSION +1 -1
  10. data/lib/VERSION_NOTES +8 -0
  11. data/lib/dependencies.rb +4 -1
  12. data/lib/gloo/app/engine.rb +18 -6
  13. data/lib/gloo/core/obj.rb +0 -23
  14. data/lib/gloo/objs/basic/script.rb +0 -23
  15. data/lib/gloo/persist/declaration_ledger.rb +9 -9
  16. data/lib/gloo/persist/declaration_writer.rb +208 -0
  17. data/lib/gloo/persist/disc_mech.rb +70 -39
  18. data/lib/gloo/persist/file_loader.rb +125 -66
  19. data/lib/gloo/persist/file_saver.rb +130 -198
  20. data/lib/gloo/persist/file_storage.rb +15 -4
  21. data/lib/gloo/persist/file_style.rb +89 -0
  22. data/lib/gloo/persist/line_splitter.rb +16 -1
  23. data/lib/gloo/persist/persist_man.rb +112 -30
  24. data/lib/gloo/persist/save_index.rb +211 -0
  25. data/lib/gloo/persist/script_body_collector.rb +16 -21
  26. data/lib/gloo/persist/source/blank_node.rb +10 -3
  27. data/lib/gloo/persist/source/comment_node.rb +10 -2
  28. data/lib/gloo/persist/source/directive_node.rb +10 -2
  29. data/lib/gloo/persist/source/obj_node.rb +174 -9
  30. data/lib/gloo/persist/source/source_doc.rb +54 -0
  31. data/lib/gloo/persist/subtree_extractor.rb +9 -37
  32. data/lib/gloo/persist/trivia_buffer.rb +7 -7
  33. data/lib/gloo/verbs/load.rb +27 -21
  34. data/lib/gloo/verbs/move.rb +42 -3
  35. data/lib/gloo/verbs/save.rb +26 -4
  36. data/test.gloo/app/exit_status.test.gloo +34 -0
  37. data/test.gloo/app/fixtures/exit_after_error.gloo +9 -0
  38. data/test.gloo/lang/declaration.test.gloo +36 -0
  39. data/test.gloo/lang/fixtures/mixed_indent.gloo +5 -0
  40. data/test.gloo/lang/naming.test.gloo +44 -6
  41. data/test.gloo/verbs/fixtures/load_beside.gloo +6 -0
  42. data/test.gloo/verbs/fixtures/lossless_text.gloo +7 -0
  43. data/test.gloo/verbs/fixtures/lossless_text_saved.gloo +9 -0
  44. data/test.gloo/verbs/load.test.gloo +18 -0
  45. data/test.gloo/verbs/move.test.gloo +39 -0
  46. data/test.gloo/verbs/save.test.gloo +239 -0
  47. metadata +10 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 153d674b150e06c999a0756c90c41f872a410205f516eaa8b560335f93ec0e2b
4
- data.tar.gz: 1b11115e161f139b44dc6750713e0e883f638ae58048c8b3007441cbf681f319
3
+ metadata.gz: b5cc04330edd79c2663408d7afbedc9195dbd994c31ce0a7cd6a591ce9276c56
4
+ data.tar.gz: d4f004bccdc1dbae382b2d3106154557b90e6b236447b04c8c5b5fcf05ebeffc
5
5
  SHA512:
6
- metadata.gz: 3e92b508048653b4e1e4e283a7e90de32605b4a239f56b7b24123c600e5b38a6af582a50a6d2a7130da8ed73d8ded9f0e6aab2a75798a9709939749cf72119dd
7
- data.tar.gz: 19cd40e0d35cfed856aa7eb6ffe4fe648adde3c706656d81211d01f0c940a4c38dd63b0bffe2a4b50b3b57d9e76afba997050c7fca99066f2fe6529825d05591
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
- - Do not include the `.gloo` or other file extension.
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.gloo
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:
@@ -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
- - Names should be unique within their context.
19
- - Note that there is no technical restriction. Multiple objects with the same name in the same context can exist, but there's no way to reference objects past the first one with the given name.
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 treated as unique.
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' objects are not reachable.
29
- # 'naming.a' will always reach the 'First A' string.
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 is not reachable
39
- A [string] : Capital A is not reachable
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 test can load a fixture that sits beside its own file:
125
+ example, a script can read a data file that sits beside its own file:
125
126
 
126
127
  ```gloo
127
- tests.my_area [can] :
128
+ report [can] :
128
129
  where [string] :
129
- fixture [string] : /fixtures/sample.gloo
130
+ data [file] :
131
+ rows [string] :
130
132
 
131
- loads_its_fixture [test] :
132
- on_test [script] :
133
- check ^^ for source_dir
134
- put it into ^^.where
135
- load ( ^^.where ^^.fixture )
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
@@ -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/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. `check ^^ for source_dir` gives the folder of the test file (see Language, Objects, Where an Object Came From):
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
- check ^^ for source_dir
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 path relative to the project folder, a full path, or one starting with `~` for your home folder; the `.gloo` extension is optional. `*` in place of a file name loads every `.gloo` file in a folder, wherever the folder is. Anything before the `*` is the start of the file names, so `my/app/a*` loads just the files whose names start with `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 my/project/config
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.3
1
+ 7.2
data/lib/VERSION_NOTES CHANGED
@@ -1,3 +1,11 @@
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
+
1
9
  7.1.3 - 2026.10.29
2
10
  - break in a loop's do skips to the next item again, as it did before 7.1.1.
3
11
  - New: break loop ends the whole loop. It's only valid directly in a loop's do script.
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
- require ruby_file
31
+ require_relative ruby_file
29
32
  end
@@ -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 a non-zero exit code if any file failed to
140
- # load (missing, unresolvable, or errored on load) so that a
141
- # calling shell script can tell a bad run from a good one.
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 failed
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
  #
@@ -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
  #
@@ -3,8 +3,8 @@
3
3
  #
4
4
  # Tracks what a single file declares as it loads: its top-level
5
5
  # objects (for save resolution), and every object it created -- so a
6
- # re-declaration of a name an earlier file already used can be
7
- # flagged, since in that case the second value is silently dropped.
6
+ # re-declaration of a name already used, in an earlier file or earlier
7
+ # in this one, can be flagged when its value is ignored.
8
8
  #
9
9
 
10
10
  module Gloo
@@ -39,19 +39,19 @@ module Gloo
39
39
 
40
40
  #
41
41
  # `prior` already existed when this file declared `name` with
42
- # `value`. If another file created it and the values differ, the
43
- # new value is being ignored -- warn so it isn't a surprise. A
44
- # duplicate name inside one file is left alone (a documented
45
- # "first one wins"). The warning's location (this file and line)
46
- # comes from the load.
42
+ # `value`: the line names the same object, and its value is
43
+ # ignored (the first declaration wins). When the ignored value is
44
+ # different, warn so it isn't a surprise -- whether the first
45
+ # declaration was in another file or earlier in this one. The
46
+ # warning's location (this file and line) comes from the load.
47
47
  #
48
48
  def clash( prior, name, value )
49
- return if @mine.key?( prior )
50
49
  return if value.nil? || value.to_s.strip.empty?
51
50
  return if prior.value.to_s == value.to_s
52
51
 
52
+ where = @mine.key?( prior ) ? 'above' : 'in another loaded file'
53
53
  @engine.warn(
54
- "'#{name}' is already declared in another loaded file with a " \
54
+ "'#{name}' is already declared #{where} with a " \
55
55
  "different value (#{prior.value.inspect} kept, #{value.inspect} ignored)."
56
56
  )
57
57
  end