gloo 6.3.1 → 6.5.0
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/CLAUDE.md +13 -0
- data/docs/application.md +9 -0
- data/docs/language_objects.md +78 -0
- data/docs/language_scripting.md +41 -0
- data/docs/objects.md +1 -1
- data/docs/verbs.md +36 -2
- data/lib/VERSION +1 -1
- data/lib/VERSION_NOTES +13 -0
- data/lib/gloo/app/engine.rb +5 -1
- data/lib/gloo/app/log.rb +5 -5
- data/lib/gloo/app/settings.rb +4 -1
- data/lib/gloo/core/obj.rb +60 -1
- data/lib/gloo/core/parser.rb +30 -1
- data/lib/gloo/core/tokens.rb +44 -18
- data/lib/gloo/expr/l_string.rb +8 -4
- data/lib/gloo/objs/basic/script.rb +28 -2
- data/lib/gloo/objs/web/json.rb +4 -1
- data/lib/gloo/persist/comment_buffer.rb +58 -0
- data/lib/gloo/persist/declaration_ledger.rb +61 -0
- data/lib/gloo/persist/disc_mech.rb +15 -2
- data/lib/gloo/persist/file_loader.rb +208 -102
- data/lib/gloo/persist/file_saver.rb +251 -6
- data/lib/gloo/persist/file_storage.rb +10 -4
- data/lib/gloo/persist/indent_stack.rb +90 -0
- data/lib/gloo/persist/line_splitter.rb +18 -3
- data/lib/gloo/persist/persist_man.rb +193 -26
- data/lib/gloo/persist/script_body_collector.rb +102 -0
- data/lib/gloo/persist/shorthand_expander.rb +51 -0
- data/lib/gloo/persist/source/blank_node.rb +27 -0
- data/lib/gloo/persist/source/comment_node.rb +27 -0
- data/lib/gloo/persist/source/directive_node.rb +26 -0
- data/lib/gloo/persist/source/obj_node.rb +91 -0
- data/lib/gloo/persist/source/source_doc.rb +41 -0
- data/lib/gloo/verbs/list.rb +24 -2
- data/lib/gloo/verbs/load.rb +9 -1
- data/lib/gloo/verbs/reload.rb +3 -0
- data/lib/gloo/verbs/save.rb +57 -10
- data/test.gloo/ctrl/each.test.gloo +28 -30
- data/test.gloo/dt/date.test.gloo +69 -71
- data/test.gloo/dt/datetime.test.gloo +55 -57
- data/test.gloo/dt/time.test.gloo +70 -72
- data/test.gloo/lang/continuation.test.gloo +25 -27
- data/test.gloo/lang/convert.test.gloo +50 -52
- data/test.gloo/lang/dt_comparisons.test.gloo +87 -89
- data/test.gloo/lang/exceptions.test.gloo +42 -44
- data/test.gloo/lang/gloo_sys.test.gloo +100 -102
- data/test.gloo/lang/here.test.gloo +32 -34
- data/test.gloo/lang/it.test.gloo +27 -29
- data/test.gloo/lang/literal.test.gloo +55 -57
- data/test.gloo/lang/load_lib_directive.test.gloo +18 -20
- data/test.gloo/lang/naming.test.gloo +28 -30
- data/test.gloo/lang/ops.test.gloo +53 -55
- data/test.gloo/lang/shorthand.test.gloo +27 -0
- data/test.gloo/math/add.test.gloo +40 -42
- data/test.gloo/math/div.test.gloo +23 -25
- data/test.gloo/math/mult.test.gloo +23 -25
- data/test.gloo/math/sub.test.gloo +23 -25
- data/test.gloo/objs/alias.test.gloo +15 -17
- data/test.gloo/objs/bool.test.gloo +54 -56
- data/test.gloo/objs/can.test.gloo +33 -35
- data/test.gloo/objs/cipher.test.gloo +39 -41
- data/test.gloo/objs/decimal.test.gloo +27 -29
- data/test.gloo/objs/erb.test.gloo +47 -49
- data/test.gloo/objs/file.test.gloo +68 -70
- data/test.gloo/objs/function.test.gloo +37 -39
- data/test.gloo/objs/int.test.gloo +54 -56
- data/test.gloo/objs/json.test.gloo +57 -50
- data/test.gloo/objs/obj.test.gloo +68 -48
- data/test.gloo/objs/outline.test.gloo +48 -50
- data/test.gloo/objs/password.test.gloo +34 -36
- data/test.gloo/objs/repeat.test.gloo +42 -44
- data/test.gloo/objs/script.test.gloo +13 -15
- data/test.gloo/objs/string.test.gloo +82 -84
- data/test.gloo/objs/text.test.gloo +90 -92
- data/test.gloo/objs/untyped.test.gloo +36 -38
- data/test.gloo/objs/uri.test.gloo +37 -39
- data/test.gloo/string/str.test.gloo +15 -0
- data/test.gloo/verbs/break.test.gloo +12 -14
- data/test.gloo/verbs/check.test.gloo +22 -24
- data/test.gloo/verbs/context.test.gloo +11 -13
- data/test.gloo/verbs/create.test.gloo +7 -9
- data/test.gloo/verbs/eval.test.gloo +10 -12
- data/test.gloo/verbs/exists.test.gloo +43 -45
- data/test.gloo/verbs/if.test.gloo +8 -10
- data/test.gloo/verbs/invoke.test.gloo +73 -75
- data/test.gloo/verbs/list.test.gloo +21 -23
- data/test.gloo/verbs/load.test.gloo +27 -21
- data/test.gloo/verbs/log.test.gloo +7 -9
- data/test.gloo/verbs/move.test.gloo +12 -14
- data/test.gloo/verbs/put.test.gloo +10 -12
- data/test.gloo/verbs/reload.test.gloo +55 -0
- data/test.gloo/verbs/run.test.gloo +8 -10
- data/test.gloo/verbs/save.test.gloo +111 -0
- data/test.gloo/verbs/show.test.gloo +26 -28
- data/test.gloo/verbs/tell.test.gloo +8 -10
- data/test.gloo/verbs/throw.test.gloo +31 -33
- data/test.gloo/verbs/unless.test.gloo +13 -15
- data/test.gloo/verbs/unload.test.gloo +10 -12
- metadata +14 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c586639c9bf946ff078f68d28ac3c8fd676ae3e040ee181d7a65eb63a648feda
|
|
4
|
+
data.tar.gz: defbe6d74c65dfd5df9abbf3e917cdd3acb950c8d890dfab53cc37b158118772
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 578cac3fcd5cf809fd00db651563b3a0ff8a2eef70796f9ccd4742b1086f6d4e9045ffe77db73a307be5972a46d9262f9b43a59c2fe215ecacae900fd450f478
|
|
7
|
+
data.tar.gz: 5248581d7000865535b0419cd6772e72de8c5ac1a09fe8668345e2ca2703ffcf28756fdd70b8b7e99f2fe04a88c64fbe5a9bb4ad7e204827e689eef05011ac4f
|
data/CLAUDE.md
CHANGED
|
@@ -58,6 +58,19 @@ Base classes: inherit from `GlooTest` (defined in `test/base_test.rb`).
|
|
|
58
58
|
### Gloo integration tests — `test.gloo/`
|
|
59
59
|
Written in gloo itself. Each file contains `[test]` objects with `on_test` scripts using `assert` and `refute`.
|
|
60
60
|
|
|
61
|
+
Run with `ruby lib/run.rb --test --quiet <paths...>` — **not** `bundle exec` (the Gemfile doesn't carry `gloo-test` / `gloo-md` / the other core-lib gems, so `[test]` and `load lib` fail under bundler; plain `ruby` uses the system gem env where they're installed).
|
|
62
|
+
|
|
63
|
+
- Interpreter suite only:
|
|
64
|
+
`ruby ~/dev/gloo/lib/run.rb --test --quiet ~/dev/gloo/test.gloo/`
|
|
65
|
+
- Interpreter **plus** every extension and core library:
|
|
66
|
+
```
|
|
67
|
+
ruby ~/dev/gloo/lib/run.rb --test --quiet \
|
|
68
|
+
~/dev/gloo/test.gloo/ \
|
|
69
|
+
~/gloo/extensions/{alert,beep,git,ruby,slack,stats,teams}/test/ \
|
|
70
|
+
~/dev/gloo_core_libraries/gloo-{beep,cli,db,email,md,mysql,pg,sqlite,test,web,yaml}/test/
|
|
71
|
+
```
|
|
72
|
+
(brace expansion is shell sugar for the full explicit path list)
|
|
73
|
+
|
|
61
74
|
```
|
|
62
75
|
test.gloo/
|
|
63
76
|
├── basic.test.gloo
|
data/docs/application.md
CHANGED
|
@@ -30,6 +30,8 @@ gloo [global option] [file]
|
|
|
30
30
|
|
|
31
31
|
Running gloo with a file specified will run that file. Once that file is done, gloo will quit. However, by specifying `--cli`, once the file has finished, gloo will remain open in CLI mode.
|
|
32
32
|
|
|
33
|
+
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 not found: {name}` to stderr and exits with a non-zero status. Any other load or run error also exits non-zero, so a shell script calling gloo can tell a failed run from a clean one.
|
|
34
|
+
|
|
33
35
|
When specifying a file there are a couple ways to reference the gloo file to open:
|
|
34
36
|
|
|
35
37
|
- By Path
|
|
@@ -101,6 +103,13 @@ gloo:
|
|
|
101
103
|
#
|
|
102
104
|
list_levels: 3
|
|
103
105
|
|
|
106
|
+
#
|
|
107
|
+
# When listing the object tree, also show each object's doc (the
|
|
108
|
+
# comment declared immediately above it in its source file), if it
|
|
109
|
+
# has one.
|
|
110
|
+
#
|
|
111
|
+
list_docs: false
|
|
112
|
+
|
|
104
113
|
#
|
|
105
114
|
# Show debug statements in the log?
|
|
106
115
|
#
|
data/docs/language_objects.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
**Contents**
|
|
4
4
|
|
|
5
5
|
- Object Naming
|
|
6
|
+
- Nested Container Shorthand
|
|
6
7
|
- Keywords
|
|
7
8
|
- Literals
|
|
8
9
|
- Value Conversion
|
|
@@ -39,6 +40,76 @@ naming [container] :
|
|
|
39
40
|
|
|
40
41
|
See also: Pathname.
|
|
41
42
|
|
|
43
|
+
## Nested Container Shorthand
|
|
44
|
+
|
|
45
|
+
A declaration whose name is a dotted path creates a container for each
|
|
46
|
+
prefix segment, then declares the real object under the last one. These
|
|
47
|
+
two files are equivalent:
|
|
48
|
+
|
|
49
|
+
```gloo
|
|
50
|
+
page [container] :
|
|
51
|
+
core [container] :
|
|
52
|
+
users [container] :
|
|
53
|
+
list [container] :
|
|
54
|
+
title [string] : Users
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```gloo
|
|
58
|
+
page.core.users.list [container] :
|
|
59
|
+
title [string] : Users
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
- Each prefix segment (`page`, `core`, `users`) becomes a container,
|
|
63
|
+
unless a sibling object of that name already exists — in which case
|
|
64
|
+
that object is used as-is.
|
|
65
|
+
- The shorthand works at any indent level; the prefix is resolved
|
|
66
|
+
relative to the current parent.
|
|
67
|
+
- Several shorthand lines can share a prefix — `page.core.users [...]`
|
|
68
|
+
and `page.core.settings [...]` both reuse the same `page` and
|
|
69
|
+
`page.core` containers.
|
|
70
|
+
- Indented lines below a shorthand declaration are children of the last
|
|
71
|
+
segment (`list` above), not of any prefix container.
|
|
72
|
+
|
|
73
|
+
## Documenting an Object
|
|
74
|
+
|
|
75
|
+
A contiguous run of whole-line comments immediately above a declaration,
|
|
76
|
+
at the same indent and with no blank line in between, is that object's
|
|
77
|
+
**doc** — read it back at run time with `tell {obj} to doc` or
|
|
78
|
+
`check {obj} for doc` (the cleaned text lands in `it`, like any other
|
|
79
|
+
value-returning message):
|
|
80
|
+
|
|
81
|
+
```gloo
|
|
82
|
+
#
|
|
83
|
+
# The user's display name. Empty until they set it in preferences.
|
|
84
|
+
#
|
|
85
|
+
name [string] :
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
```gloo
|
|
89
|
+
> check name for doc
|
|
90
|
+
> show it
|
|
91
|
+
|
|
92
|
+
The user's display name. Empty until they set it in preferences.
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Each line's own leading whitespace and `#` marker (plus one space after
|
|
97
|
+
it, if there is one) are stripped, and the result is dedented to its
|
|
98
|
+
shallowest line — but otherwise the full block is kept exactly as
|
|
99
|
+
written, blank `#` lines above and below the text included. An object
|
|
100
|
+
with no leading comment — including anything created at run time rather
|
|
101
|
+
than loaded from a file — has a blank doc (`''`).
|
|
102
|
+
|
|
103
|
+
A comment separated from the declaration by a blank line, or at a
|
|
104
|
+
different indent, is not associated with it — it's kept as its own
|
|
105
|
+
floating comment in the file instead. When a name is declared in more
|
|
106
|
+
than one loaded file, the first non-empty doc wins (same rule as the
|
|
107
|
+
first value).
|
|
108
|
+
|
|
109
|
+
`list` can show every documented object in a listed tree: turn on the
|
|
110
|
+
`list_docs` setting (off by default) — see `list` and `settings` in the
|
|
111
|
+
in-app help.
|
|
112
|
+
|
|
42
113
|
## Keywords
|
|
43
114
|
|
|
44
115
|
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).
|
|
@@ -60,6 +131,11 @@ The following rules apply to literal values:
|
|
|
60
131
|
|
|
61
132
|
- Strings
|
|
62
133
|
- Can be delimited by single or double quotes. (`"` or `'`)
|
|
134
|
+
- A quote of the other kind inside needs no escaping —
|
|
135
|
+
`'{"x":1}'` is the string `{"x":1}`, which is the usual way to
|
|
136
|
+
write a JSON literal.
|
|
137
|
+
- A quote of the same kind inside is escaped with a backslash —
|
|
138
|
+
`"say \"hi\""` is the string `say "hi"`.
|
|
63
139
|
- Numbers
|
|
64
140
|
- Integer and decimal numbers need no delimiters.
|
|
65
141
|
- To refer to a decimal with no fractional value, include `.0` to indicate a decimal value.
|
|
@@ -89,6 +165,8 @@ The following rules apply to literal values:
|
|
|
89
165
|
show ^.s
|
|
90
166
|
put "You're Awesome!" into ^.s
|
|
91
167
|
show ^.s
|
|
168
|
+
put '{"lang":"gloo"}' into ^.s
|
|
169
|
+
show ^.s
|
|
92
170
|
|
|
93
171
|
# Number literals
|
|
94
172
|
put 1 into ^.i
|
data/docs/language_scripting.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
**Contents**
|
|
4
4
|
|
|
5
5
|
- Gloo Script Files
|
|
6
|
+
- Comments
|
|
6
7
|
- Gloo Constants
|
|
7
8
|
- Line Continuation
|
|
8
9
|
|
|
@@ -26,6 +27,46 @@ hello [can] :
|
|
|
26
27
|
on_load [script] : show 'hello world'
|
|
27
28
|
```
|
|
28
29
|
|
|
30
|
+
## Comments
|
|
31
|
+
|
|
32
|
+
A line whose first non-blank character is `#` is a comment. Whole-line comments
|
|
33
|
+
can appear anywhere — between objects, inside a container, or inside a script
|
|
34
|
+
body — and are ignored when the file runs.
|
|
35
|
+
|
|
36
|
+
```gloo
|
|
37
|
+
#
|
|
38
|
+
# A whole-line comment.
|
|
39
|
+
#
|
|
40
|
+
demo [can] :
|
|
41
|
+
# a comment inside the container
|
|
42
|
+
msg [string] : hello
|
|
43
|
+
on_load [script] :
|
|
44
|
+
# a comment inside the script body
|
|
45
|
+
show demo.msg
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
A statement line may also end with an inline `# ...` comment. It is stripped
|
|
49
|
+
before the statement runs, so it has no effect on execution:
|
|
50
|
+
|
|
51
|
+
```gloo
|
|
52
|
+
on_load [script] :
|
|
53
|
+
show 3 + 4 # prints 7
|
|
54
|
+
check demo.msg for blank?
|
|
55
|
+
show it # prints false
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Two things are *not* treated as inline comments, and are left alone:
|
|
59
|
+
|
|
60
|
+
- a `#` inside a quoted string — `show 'a # b'`
|
|
61
|
+
- a `#` with no space before it — a URL fragment such as
|
|
62
|
+
`http://example.com/page#section`
|
|
63
|
+
|
|
64
|
+
An inline comment on an object declaration is also left alone — everything after
|
|
65
|
+
the `:` is the object's value, so `note [string] : see item # 5` stores the
|
|
66
|
+
string `see item # 5`.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
29
70
|
## Gloo Constants
|
|
30
71
|
|
|
31
72
|
There is no gloo language construct for constants. They are simply objects. But by convention, constants are named in all caps. They might be in a container or at the root object level.
|
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?`, `reload`, `unload`) — just not the type-specific ones. 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`, `reload`, `unload`) — just not the type-specific ones. 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/verbs.md
CHANGED
|
@@ -9,8 +9,9 @@ Verbs aren't just for scripts, though. They're also the interactive language of
|
|
|
9
9
|
- Run
|
|
10
10
|
- Tell
|
|
11
11
|
- Put
|
|
12
|
+
- Load & Save
|
|
12
13
|
|
|
13
|
-
This page walks through
|
|
14
|
+
This page walks through the most commonly used verbs to get a feel for how they work together. For the complete list of verbs, their full syntax, and every error they can raise, use the in-app help: enter `help` (or `?`), then `verbs` to list them all, or `verb {name}` for detail on one (see Application, Help).
|
|
14
15
|
|
|
15
16
|
## Run
|
|
16
17
|
|
|
@@ -50,6 +51,13 @@ tell {path.to.object} to {message}
|
|
|
50
51
|
> if it then show 'it does'
|
|
51
52
|
```
|
|
52
53
|
|
|
54
|
+
`doc` is one such message every object responds to: it returns the comment block declared immediately above it in its source file, cleaned up (see Language, Objects — Documenting an Object).
|
|
55
|
+
|
|
56
|
+
```gloo
|
|
57
|
+
> tell my.obj to doc
|
|
58
|
+
> show it
|
|
59
|
+
```
|
|
60
|
+
|
|
53
61
|
## Put
|
|
54
62
|
|
|
55
63
|
`put` evaluates an expression and stores the result in an object.
|
|
@@ -67,6 +75,32 @@ put {expression} into {dst.path}
|
|
|
67
75
|
|
|
68
76
|
`it` also picks up the result of the evaluation, same as with other verbs — see It.
|
|
69
77
|
|
|
78
|
+
## Load & Save
|
|
79
|
+
|
|
80
|
+
`load` reads a `.gloo` file into the heap and runs its `on_load` script. Give a path relative to the project folder (no extension needed) or a full path (extension required); `*` in place of a file name loads every `.gloo` file in a folder.
|
|
81
|
+
|
|
82
|
+
```gloo
|
|
83
|
+
> load my/project/config
|
|
84
|
+
> load my/app/*
|
|
85
|
+
> load ~/.my_app/settings.gloo
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
If the name can't be resolved to a file, `load` reports `File not found: {name}` — it fires `on_error` and, when the file was named on the `gloo` command line, prints the message to stderr and exits non-zero. A bad or missing file never fails silently.
|
|
89
|
+
|
|
90
|
+
`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 saves to a new file and remembers the mapping.
|
|
91
|
+
|
|
92
|
+
```gloo
|
|
93
|
+
> save # every open file
|
|
94
|
+
> save config # just config's file
|
|
95
|
+
> save config to backups/config
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
A save is a **rewrite, not a regeneration**: comments, blank lines, and the original spacing are kept, and only the values you actually changed are re-written. A declaration you never touched comes back byte-for-byte.
|
|
99
|
+
|
|
100
|
+
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.
|
|
101
|
+
|
|
102
|
+
An object can also save itself: `tell config to save`.
|
|
103
|
+
|
|
70
104
|
---
|
|
71
105
|
|
|
72
|
-
`run`, `tell`, and `
|
|
106
|
+
`run`, `tell`, `put`, and `load` / `save` cover a lot of ground, but there are more than two dozen other verbs — `show`, `if`, `create`, `each`, `check`, `reload`, `unload`, and so on — all documented in-app. Enter `help` (or `?`), then `verbs` to browse them. (This page itself is also viewable in-app: `help> doc verbs`.)
|
data/lib/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
6.
|
|
1
|
+
6.5.0
|
data/lib/VERSION_NOTES
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
6.5.0 - 2026.09.11
|
|
2
|
+
- Adds option to show comments from the list verb.
|
|
3
|
+
- Adds message to get doc for an object.
|
|
4
|
+
- Report error if the file to load can't be found.
|
|
5
|
+
- Fixes issue with multisegment root level object.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
6.4.0 - 2026.09.06
|
|
9
|
+
- Persistance refresh with updates for loading and saving
|
|
10
|
+
- Nested container shortcut syntax
|
|
11
|
+
- Fixes a number of comment and quoting issues
|
|
12
|
+
|
|
13
|
+
|
|
1
14
|
6.3.1 - 2026.09.01
|
|
2
15
|
- Fixes issue with MD in a text block (# ignored as comments)
|
|
3
16
|
|
data/lib/gloo/app/engine.rb
CHANGED
|
@@ -134,11 +134,15 @@ module Gloo
|
|
|
134
134
|
|
|
135
135
|
#
|
|
136
136
|
# Run files specified on the CLI.
|
|
137
|
-
# Then quit
|
|
137
|
+
# Then quit, with a non-zero exit code if any file failed to
|
|
138
|
+
# load (missing, unresolvable, or errored on load) so that a
|
|
139
|
+
# calling shell script can tell a bad run from a good one.
|
|
138
140
|
#
|
|
139
141
|
def run_files
|
|
140
142
|
load_files
|
|
143
|
+
failed = error?
|
|
141
144
|
quit
|
|
145
|
+
exit( 1 ) if failed
|
|
142
146
|
end
|
|
143
147
|
|
|
144
148
|
#
|
data/lib/gloo/app/log.rb
CHANGED
|
@@ -167,7 +167,7 @@ module Gloo
|
|
|
167
167
|
#
|
|
168
168
|
# Write an error message to the log and set the error
|
|
169
169
|
# in the engine's data heap.
|
|
170
|
-
# Also write to the console unless quiet.
|
|
170
|
+
# Also write to the console (on stderr) unless quiet.
|
|
171
171
|
#
|
|
172
172
|
def error( msg, ex = nil, engine = nil )
|
|
173
173
|
engine&.heap&.error&.set_to( msg ) if engine
|
|
@@ -176,11 +176,11 @@ module Gloo
|
|
|
176
176
|
if ex
|
|
177
177
|
@error.error ex.message
|
|
178
178
|
@error.error ex.backtrace
|
|
179
|
-
puts @theme.error( msg ) unless @quiet
|
|
180
|
-
puts @theme.error( ex.message ) unless @quiet
|
|
181
|
-
puts ex.backtrace unless @quiet
|
|
179
|
+
$stderr.puts @theme.error( msg ) unless @quiet
|
|
180
|
+
$stderr.puts @theme.error( ex.message ) unless @quiet
|
|
181
|
+
$stderr.puts ex.backtrace unless @quiet
|
|
182
182
|
else
|
|
183
|
-
puts @theme.error( msg ) unless @quiet
|
|
183
|
+
$stderr.puts @theme.error( msg ) unless @quiet
|
|
184
184
|
end
|
|
185
185
|
end
|
|
186
186
|
|
data/lib/gloo/app/settings.rb
CHANGED
|
@@ -15,7 +15,7 @@ module Gloo
|
|
|
15
15
|
|
|
16
16
|
attr_reader :user_root, :log_path,
|
|
17
17
|
:config_path, :project_path, :ext_path,
|
|
18
|
-
:start_with, :list_indent, :list_levels, :tmp_path,
|
|
18
|
+
:start_with, :list_indent, :list_levels, :list_docs, :tmp_path,
|
|
19
19
|
:debug_path, :debug, :theme
|
|
20
20
|
|
|
21
21
|
#
|
|
@@ -41,6 +41,7 @@ module Gloo
|
|
|
41
41
|
puts theme.accent( ' Startup with: ' ) + theme.emphasis( @start_with.to_s )
|
|
42
42
|
puts theme.accent( ' Indent in Listing: ' ) + theme.emphasis( @list_indent.to_s )
|
|
43
43
|
puts theme.accent( ' List Levels: ' ) + theme.emphasis( @list_levels.to_s )
|
|
44
|
+
puts theme.accent( ' List Docs: ' ) + theme.emphasis( @list_docs.to_s )
|
|
44
45
|
puts theme.accent( ' Debug? ' ) + theme.emphasis( @debug.to_s )
|
|
45
46
|
puts theme.accent( ' Theme: ' ) + theme.emphasis( @theme )
|
|
46
47
|
puts theme.accent( ' Screen Lines: ' ) + theme.emphasis( Gloo::App::Settings.lines( @engine ).to_s )
|
|
@@ -158,6 +159,7 @@ module Gloo
|
|
|
158
159
|
@start_with = settings[ 'gloo' ][ 'start_with' ]
|
|
159
160
|
@list_indent = settings[ 'gloo' ][ 'list_indent' ]
|
|
160
161
|
@list_levels = settings[ 'gloo' ][ 'list_levels' ]
|
|
162
|
+
@list_docs = settings[ 'gloo' ][ 'list_docs' ] == true
|
|
161
163
|
|
|
162
164
|
@debug = settings[ 'gloo' ][ 'debug' ]
|
|
163
165
|
|
|
@@ -192,6 +194,7 @@ module Gloo
|
|
|
192
194
|
start_with:
|
|
193
195
|
list_indent: 2
|
|
194
196
|
list_levels: 3
|
|
197
|
+
list_docs: false
|
|
195
198
|
debug: false
|
|
196
199
|
theme: #{DEFAULT_THEME}
|
|
197
200
|
TEXT
|
data/lib/gloo/core/obj.rb
CHANGED
|
@@ -12,12 +12,21 @@ module Gloo
|
|
|
12
12
|
attr_accessor :value
|
|
13
13
|
attr_reader :children, :parent
|
|
14
14
|
|
|
15
|
+
# The object's documentation: the cleaned comment block declared
|
|
16
|
+
# immediately above it in its source file (Source::ObjNode#doc),
|
|
17
|
+
# copied on at load time by FileLoader. '' for an object with no
|
|
18
|
+
# such comment, or one created at run time. Loader-set only --
|
|
19
|
+
# there's no verb/message path to change it, so it can't drift
|
|
20
|
+
# from what the file actually says.
|
|
21
|
+
attr_accessor :doc
|
|
22
|
+
|
|
15
23
|
#
|
|
16
24
|
# Set up the object.
|
|
17
25
|
#
|
|
18
26
|
def initialize( engine )
|
|
19
27
|
@engine = engine
|
|
20
28
|
@value = ''
|
|
29
|
+
@doc = ''
|
|
21
30
|
@children = []
|
|
22
31
|
@parent = nil
|
|
23
32
|
end
|
|
@@ -118,6 +127,29 @@ module Gloo
|
|
|
118
127
|
return false
|
|
119
128
|
end
|
|
120
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
|
+
|
|
121
153
|
#
|
|
122
154
|
# Is the value a String?
|
|
123
155
|
#
|
|
@@ -306,7 +338,7 @@ module Gloo
|
|
|
306
338
|
# Get a list of message names that this object receives.
|
|
307
339
|
#
|
|
308
340
|
def self.messages
|
|
309
|
-
return %w[reload unload blank? contains? responds_to?]
|
|
341
|
+
return %w[save reload unload blank? contains? responds_to? doc]
|
|
310
342
|
end
|
|
311
343
|
|
|
312
344
|
#
|
|
@@ -356,6 +388,22 @@ module Gloo
|
|
|
356
388
|
@engine.persist_man.unload self
|
|
357
389
|
end
|
|
358
390
|
|
|
391
|
+
#
|
|
392
|
+
# Send the object the save message: tell it to save itself,
|
|
393
|
+
# rather than going through the save verb. Same rules as
|
|
394
|
+
# `save {path.to.object}` -- saves every file that owns a
|
|
395
|
+
# declaration in this object's root, or saves fresh to a default
|
|
396
|
+
# path if it isn't mapped to a file yet.
|
|
397
|
+
#
|
|
398
|
+
def msg_save
|
|
399
|
+
if self.root?
|
|
400
|
+
@engine.err 'Cannot save the root object.'
|
|
401
|
+
return
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
@engine.persist_man.save self.pn
|
|
405
|
+
end
|
|
406
|
+
|
|
359
407
|
#
|
|
360
408
|
# Send the object the reload message.
|
|
361
409
|
# Note that this will only work for objects with file assoications.
|
|
@@ -406,6 +454,17 @@ module Gloo
|
|
|
406
454
|
end
|
|
407
455
|
end
|
|
408
456
|
|
|
457
|
+
#
|
|
458
|
+
# Get this object's documentation -- the comment block declared
|
|
459
|
+
# immediately above it in its source file, cleaned up. Puts the
|
|
460
|
+
# result into 'it' rather than printing, same as other
|
|
461
|
+
# value-returning messages (count, down, blank?, ...).
|
|
462
|
+
#
|
|
463
|
+
def msg_doc
|
|
464
|
+
@engine.heap.it.set_to self.doc
|
|
465
|
+
return self.doc
|
|
466
|
+
end
|
|
467
|
+
|
|
409
468
|
|
|
410
469
|
# ---------------------------------------------------------------------
|
|
411
470
|
# Render
|
data/lib/gloo/core/parser.rb
CHANGED
|
@@ -21,7 +21,11 @@ module Gloo
|
|
|
21
21
|
# Parse a command from the immediate execution context.
|
|
22
22
|
#
|
|
23
23
|
def parse_immediate( full_cmd )
|
|
24
|
-
#
|
|
24
|
+
# Drop an inline trailing comment first, then break the
|
|
25
|
+
# command into verb and params.
|
|
26
|
+
full_cmd = strip_comment( full_cmd )
|
|
27
|
+
return nil if full_cmd.strip.empty?
|
|
28
|
+
|
|
25
29
|
cmd, params = split_params full_cmd
|
|
26
30
|
|
|
27
31
|
# Params are the parenthetical part of the command at the end
|
|
@@ -35,6 +39,31 @@ module Gloo
|
|
|
35
39
|
return nil
|
|
36
40
|
end
|
|
37
41
|
|
|
42
|
+
#
|
|
43
|
+
# Remove an inline trailing comment: the first '#' that starts a
|
|
44
|
+
# word (at the start of the command, or right after whitespace)
|
|
45
|
+
# and is not inside a quoted string -- and everything after it.
|
|
46
|
+
#
|
|
47
|
+
# Left alone: a '#' inside single or double quotes (show 'a # b'),
|
|
48
|
+
# a '#' glued to preceding text (a URL fragment like
|
|
49
|
+
# http://x?id=1#frag), and a command with no such '#'. Object
|
|
50
|
+
# declaration values in a .gloo file never reach here -- they're
|
|
51
|
+
# split by LineSplitter, not the parser.
|
|
52
|
+
#
|
|
53
|
+
def strip_comment( full_cmd )
|
|
54
|
+
quote = nil
|
|
55
|
+
full_cmd.each_char.with_index do |ch, i|
|
|
56
|
+
if quote
|
|
57
|
+
quote = nil if ch == quote
|
|
58
|
+
elsif Gloo::Core::Tokens::QUOTE_CHARS.include?( ch )
|
|
59
|
+
quote = ch
|
|
60
|
+
elsif ch == '#' && ( i.zero? || full_cmd[ i - 1 ] =~ /\s/ )
|
|
61
|
+
return full_cmd[ 0...i ].rstrip
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
return full_cmd
|
|
65
|
+
end
|
|
66
|
+
|
|
38
67
|
#
|
|
39
68
|
# If additional params were provided, split them out
|
|
40
69
|
# from the token list.
|
data/lib/gloo/core/tokens.rb
CHANGED
|
@@ -154,8 +154,9 @@ module Gloo
|
|
|
154
154
|
# An inline call (invoke( ... ) / ~>( ... )) is checked for
|
|
155
155
|
# first since it needs to be quote-aware in its own right (a
|
|
156
156
|
# call's args can include a quoted string) - see
|
|
157
|
-
# find_call_range.
|
|
158
|
-
#
|
|
157
|
+
# find_call_range. Otherwise the first quoted run (whichever
|
|
158
|
+
# quote char opens first) becomes one token, and the rest is
|
|
159
|
+
# split on spaces.
|
|
159
160
|
#
|
|
160
161
|
def tokenize( str )
|
|
161
162
|
range = find_call_range( str )
|
|
@@ -163,27 +164,52 @@ module Gloo
|
|
|
163
164
|
tokenize( str[ 0...range.first ] ) if range.first.positive?
|
|
164
165
|
@tokens << str[ range ]
|
|
165
166
|
tokenize( str[ range.last + 1..-1 ] ) if range.last + 1 < str.length
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
i = str.index( "'" )
|
|
176
|
-
j = str.index( "'", i + 1 )
|
|
177
|
-
j ||= str.length
|
|
178
|
-
|
|
179
|
-
tokenize( str[ 0..i - 1 ] ) if i > 1
|
|
180
|
-
@tokens << str[ i..j ]
|
|
181
|
-
tokenize( str[ j + 1..-1 ] ) if j + 1 < str.length
|
|
167
|
+
return
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
qi, qc = first_quote( str )
|
|
171
|
+
if qi
|
|
172
|
+
close = closing_quote( str, qi, qc )
|
|
173
|
+
tokenize( str[ 0...qi ] ) if qi.positive?
|
|
174
|
+
@tokens << str[ qi..close ]
|
|
175
|
+
tokenize( str[ close + 1..-1 ] ) if close + 1 < str.length
|
|
182
176
|
else
|
|
183
177
|
str.strip.split( ' ' ).each { |t| @tokens << t }
|
|
184
178
|
end
|
|
185
179
|
end
|
|
186
180
|
|
|
181
|
+
#
|
|
182
|
+
# Find the first quote character in the string, of either kind
|
|
183
|
+
# -- returns [index, char], or [nil, nil] if there is none. The
|
|
184
|
+
# kind that opens first wins, so a " inside a '...' literal (and
|
|
185
|
+
# vice versa) is treated as ordinary content.
|
|
186
|
+
#
|
|
187
|
+
def first_quote( str )
|
|
188
|
+
found = nil
|
|
189
|
+
QUOTE_CHARS.each do |q|
|
|
190
|
+
i = str.index( q )
|
|
191
|
+
found = [ i, q ] if i && ( found.nil? || i < found[ 0 ] )
|
|
192
|
+
end
|
|
193
|
+
return found || [ nil, nil ]
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
#
|
|
197
|
+
# Index of the quote that closes the one opened at open_index.
|
|
198
|
+
# A backslash-escaped quote (\" or \') does not close the
|
|
199
|
+
# string. Returns str.length if it is never closed.
|
|
200
|
+
#
|
|
201
|
+
def closing_quote( str, open_index, quote_char )
|
|
202
|
+
i = open_index + 1
|
|
203
|
+
while i < str.length
|
|
204
|
+
ch = str[ i ]
|
|
205
|
+
return i if ch == quote_char
|
|
206
|
+
|
|
207
|
+
i += 1
|
|
208
|
+
i += 1 if ch == '\\'
|
|
209
|
+
end
|
|
210
|
+
return str.length
|
|
211
|
+
end
|
|
212
|
+
|
|
187
213
|
#
|
|
188
214
|
# Find the char range of the first top-level (not inside a
|
|
189
215
|
# quoted string) invoke(...)/~>(...) call in str. Returns nil
|
data/lib/gloo/expr/l_string.rb
CHANGED
|
@@ -31,19 +31,23 @@ module Gloo
|
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
#
|
|
34
|
-
# Given a string with leading and trailing quotes,
|
|
35
|
-
#
|
|
34
|
+
# Given a string with leading and trailing quotes, strip them
|
|
35
|
+
# out. A quote of the other kind inside needs nothing done to it
|
|
36
|
+
# ('{"x":1}' -> {"x":1}); an escaped quote of the same kind is
|
|
37
|
+
# unescaped ("say \"hi\"" -> say "hi").
|
|
36
38
|
#
|
|
37
39
|
def self.strip_quotes( str )
|
|
38
40
|
if str.start_with?( '"' )
|
|
39
41
|
str = str[ 1..-1 ]
|
|
40
42
|
str = str[ 0..-2 ] if str.end_with?( '"' )
|
|
41
|
-
return str
|
|
43
|
+
return str.gsub( '\\"', '"' )
|
|
42
44
|
elsif str.start_with?( "'" )
|
|
43
45
|
str = str[ 1..-1 ]
|
|
44
46
|
str = str[ 0..-2 ] if str.end_with?( "'" )
|
|
45
|
-
return str
|
|
47
|
+
return str.gsub( "\\'", "'" )
|
|
46
48
|
end
|
|
49
|
+
|
|
50
|
+
return str
|
|
47
51
|
end
|
|
48
52
|
|
|
49
53
|
#
|