gloo 6.4.0 → 6.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/docs/application.md +9 -0
  3. data/docs/language_objects.md +40 -0
  4. data/docs/objects.md +1 -1
  5. data/docs/verbs.md +9 -0
  6. data/lib/VERSION +1 -1
  7. data/lib/VERSION_NOTES +11 -0
  8. data/lib/gloo/app/engine.rb +5 -1
  9. data/lib/gloo/app/log.rb +5 -5
  10. data/lib/gloo/app/settings.rb +4 -1
  11. data/lib/gloo/core/obj.rb +21 -1
  12. data/lib/gloo/persist/file_loader.rb +7 -0
  13. data/lib/gloo/persist/persist_man.rb +35 -3
  14. data/lib/gloo/persist/source/obj_node.rb +41 -0
  15. data/lib/gloo/verbs/list.rb +24 -2
  16. data/lib/gloo/verbs/load.rb +13 -2
  17. data/test.gloo/ctrl/each.test.gloo +28 -30
  18. data/test.gloo/dt/date.test.gloo +69 -71
  19. data/test.gloo/dt/datetime.test.gloo +55 -57
  20. data/test.gloo/dt/time.test.gloo +70 -72
  21. data/test.gloo/lang/continuation.test.gloo +25 -27
  22. data/test.gloo/lang/convert.test.gloo +50 -52
  23. data/test.gloo/lang/dt_comparisons.test.gloo +87 -89
  24. data/test.gloo/lang/exceptions.test.gloo +42 -44
  25. data/test.gloo/lang/gloo_sys.test.gloo +100 -102
  26. data/test.gloo/lang/here.test.gloo +32 -34
  27. data/test.gloo/lang/it.test.gloo +27 -29
  28. data/test.gloo/lang/literal.test.gloo +55 -57
  29. data/test.gloo/lang/load_lib_directive.test.gloo +18 -20
  30. data/test.gloo/lang/naming.test.gloo +28 -30
  31. data/test.gloo/lang/ops.test.gloo +53 -55
  32. data/test.gloo/lang/shorthand.test.gloo +15 -17
  33. data/test.gloo/math/add.test.gloo +40 -42
  34. data/test.gloo/math/div.test.gloo +23 -25
  35. data/test.gloo/math/mult.test.gloo +23 -25
  36. data/test.gloo/math/sub.test.gloo +23 -25
  37. data/test.gloo/objs/alias.test.gloo +15 -17
  38. data/test.gloo/objs/bool.test.gloo +54 -56
  39. data/test.gloo/objs/can.test.gloo +33 -35
  40. data/test.gloo/objs/cipher.test.gloo +39 -41
  41. data/test.gloo/objs/decimal.test.gloo +27 -29
  42. data/test.gloo/objs/erb.test.gloo +47 -49
  43. data/test.gloo/objs/file.test.gloo +68 -70
  44. data/test.gloo/objs/function.test.gloo +37 -39
  45. data/test.gloo/objs/int.test.gloo +54 -56
  46. data/test.gloo/objs/json.test.gloo +56 -58
  47. data/test.gloo/objs/obj.test.gloo +68 -48
  48. data/test.gloo/objs/outline.test.gloo +48 -50
  49. data/test.gloo/objs/password.test.gloo +34 -36
  50. data/test.gloo/objs/repeat.test.gloo +42 -44
  51. data/test.gloo/objs/script.test.gloo +13 -15
  52. data/test.gloo/objs/string.test.gloo +82 -84
  53. data/test.gloo/objs/text.test.gloo +90 -92
  54. data/test.gloo/objs/untyped.test.gloo +36 -38
  55. data/test.gloo/objs/uri.test.gloo +37 -39
  56. data/test.gloo/verbs/break.test.gloo +12 -14
  57. data/test.gloo/verbs/check.test.gloo +22 -24
  58. data/test.gloo/verbs/context.test.gloo +11 -13
  59. data/test.gloo/verbs/create.test.gloo +7 -9
  60. data/test.gloo/verbs/eval.test.gloo +10 -12
  61. data/test.gloo/verbs/exists.test.gloo +43 -45
  62. data/test.gloo/verbs/if.test.gloo +8 -10
  63. data/test.gloo/verbs/invoke.test.gloo +73 -75
  64. data/test.gloo/verbs/list.test.gloo +21 -23
  65. data/test.gloo/verbs/load.test.gloo +27 -21
  66. data/test.gloo/verbs/log.test.gloo +7 -9
  67. data/test.gloo/verbs/move.test.gloo +12 -14
  68. data/test.gloo/verbs/put.test.gloo +10 -12
  69. data/test.gloo/verbs/reload.test.gloo +30 -32
  70. data/test.gloo/verbs/run.test.gloo +8 -10
  71. data/test.gloo/verbs/save.test.gloo +90 -94
  72. data/test.gloo/verbs/show.test.gloo +26 -28
  73. data/test.gloo/verbs/tell.test.gloo +8 -10
  74. data/test.gloo/verbs/throw.test.gloo +31 -33
  75. data/test.gloo/verbs/unless.test.gloo +13 -15
  76. data/test.gloo/verbs/unload.test.gloo +10 -12
  77. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 345f1e1145a1c3e32a2d94f39eda0246c0b36c8a6aa36857cb8a459042b97b0a
4
- data.tar.gz: a8cc80d6cfe82bd0f938cf9993e3948c7ae0ca1434fc430bb79a357239f8ac70
3
+ metadata.gz: 19bd707a24eb9bbd3e2759cdb2ae244d2dafc28f40c0f4046c856152e343261c
4
+ data.tar.gz: fd3fd34b664abb32a464d3745de0ede24e136f2b2a7f375809170312bbec51a1
5
5
  SHA512:
6
- metadata.gz: 1c6f947e4282de5e585543771f30d42ec3ab85197237e805d1c560c06fc286ecd5053b5627b86f7f017368dcf3ae957c1784f74b498e885feacc247a43b17ea1
7
- data.tar.gz: cd6e0ad73493fd5dd7e56dff790ed22dafa31a32d2f2347fb263cb4d6177aef4e83aaab7127ad28899d30802ffdddd6b83415f57ae27f2fc8dc2001494cd86cf
6
+ metadata.gz: f21efa7866b7eeb455fb4e7a8c64465bcd41a498d6129f04220c69eb814178f92775aeb96ef0e2a972f6df481d4603acf6e3de7c728e6508262347012cfb20cc
7
+ data.tar.gz: 956306465a135039e185104bb81cf504af379265814b5a02ba4637c3a6412463ce655aa48bae66e1f3315fd8c90925899c2ca0afda9fbf6a795c66b35e339967
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
  #
@@ -70,6 +70,46 @@ page.core.users.list [container] :
70
70
  - Indented lines below a shorthand declaration are children of the last
71
71
  segment (`list` above), not of any prefix container.
72
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
+
73
113
  ## Keywords
74
114
 
75
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).
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
@@ -51,6 +51,13 @@ tell {path.to.object} to {message}
51
51
  > if it then show 'it does'
52
52
  ```
53
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
+
54
61
  ## Put
55
62
 
56
63
  `put` evaluates an expression and stores the result in an object.
@@ -78,6 +85,8 @@ put {expression} into {dst.path}
78
85
  > load ~/.my_app/settings.gloo
79
86
  ```
80
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
+
81
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.
82
91
 
83
92
  ```gloo
data/lib/VERSION CHANGED
@@ -1 +1 @@
1
- 6.4.0
1
+ 6.5.1
data/lib/VERSION_NOTES CHANGED
@@ -1,3 +1,14 @@
1
+ 6.5.1 - 2026.09.11
2
+ - Adds doc to the load verb for recent changes.
3
+
4
+
5
+ 6.5.0 - 2026.09.11
6
+ - Adds option to show comments from the list verb.
7
+ - Adds message to get doc for an object.
8
+ - Report error if the file to load can't be found.
9
+ - Fixes issue with multisegment root level object.
10
+
11
+
1
12
  6.4.0 - 2026.09.06
2
13
  - Persistance refresh with updates for loading and saving
3
14
  - Nested container shortcut syntax
@@ -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
 
@@ -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
@@ -329,7 +338,7 @@ module Gloo
329
338
  # Get a list of message names that this object receives.
330
339
  #
331
340
  def self.messages
332
- return %w[save reload unload blank? contains? responds_to?]
341
+ return %w[save reload unload blank? contains? responds_to? doc]
333
342
  end
334
343
 
335
344
  #
@@ -445,6 +454,17 @@ module Gloo
445
454
  end
446
455
  end
447
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
+
448
468
 
449
469
  # ---------------------------------------------------------------------
450
470
  # Render
@@ -226,6 +226,13 @@ module Gloo
226
226
 
227
227
  node = build_obj_node( leading_ws( line ), name, type, value, style )
228
228
  node.leading_doc = @comments.take_leading_doc( line_tabs, @indent_stack.node.children )
229
+ # First non-empty doc wins, same as "first value wins" for a
230
+ # name re-declared across files -- @last is the same object
231
+ # across re-declarations (the factory returns the existing
232
+ # one), so a later, comment-less re-declaration doesn't blank
233
+ # out an earlier file's doc. @last can be nil for an unknown
234
+ # type (factory.create logs a warning and returns nil).
235
+ @last.doc = node.doc if @last && @last.doc.to_s.strip.empty?
229
236
  @indent_stack.node.children << node
230
237
  @last_node = node
231
238
  @obj ||= @ledger.roots.last || @last
@@ -15,6 +15,7 @@ module Gloo
15
15
  OBJ_NOT_FOUND_ERR = 'Could not resolve object to save: '.freeze
16
16
  PATH_EXISTS_ERR = 'Will not overwrite a file not already saved there: '.freeze
17
17
  RELOAD_DIRTY_WARNING = 'Reloading will discard unsaved changes in: '.freeze
18
+ FILE_NOT_FOUND_ERR = 'File not found: '.freeze
18
19
 
19
20
  #
20
21
  # Constructor for the persistence manager.
@@ -75,12 +76,20 @@ module Gloo
75
76
  end
76
77
 
77
78
  #
78
- # Load the object from the file.
79
+ # Load the object(s) from the file (or files, for a wildcard).
80
+ # Returns true if at least one object was loaded, false if the
81
+ # name couldn't be resolved to any file or nothing loaded --
82
+ # a missing/unresolvable file is reported via engine.err rather
83
+ # than failing silently.
79
84
  #
80
85
  def load( name )
81
86
  pns = get_full_path_names name
82
- return unless pns
87
+ if pns.nil? || pns.empty?
88
+ @engine.err( "#{FILE_NOT_FOUND_ERR}#{name}" ) unless name.to_s.strip.empty?
89
+ return false
90
+ end
83
91
 
92
+ loaded = false
84
93
  pns.each do |pn|
85
94
  @engine.log.debug "Load file(s) at: #{pn}"
86
95
  begin
@@ -90,10 +99,12 @@ module Gloo
90
99
 
91
100
  @maps << fs
92
101
  @engine.event_manager.on_load fs.obj
102
+ loaded = true
93
103
  rescue => ex
94
104
  @engine.handle_exception( ex )
95
105
  end
96
106
  end
107
+ return loaded
97
108
  end
98
109
 
99
110
  #
@@ -273,11 +284,32 @@ module Gloo
273
284
  nodes.each do |node|
274
285
  next unless node.is_a?( Gloo::Persist::Source::ObjNode )
275
286
 
276
- owned[ node.obj ] = true if node.obj
287
+ if node.obj
288
+ owned[ node.obj ] = true
289
+ mark_shorthand_ancestors( node, owned )
290
+ end
277
291
  collect_owned( node.children, owned )
278
292
  end
279
293
  end
280
294
 
295
+ #
296
+ # A nested-container shorthand declaration (a.b.c [type] :) only
297
+ # records a source node for the leaf; the intermediate containers
298
+ # (a, a.b) are real heap objects with no node of their own. Mark
299
+ # them owned too, walking up from the leaf, so another file's save
300
+ # doesn't see an intermediate as an orphan and adopt its whole
301
+ # subtree.
302
+ #
303
+ def mark_shorthand_ancestors( node, owned )
304
+ return unless node.name.include?( '.' )
305
+
306
+ o = node.obj.parent
307
+ while o && !o.root?
308
+ owned[ o ] = true
309
+ o = o.parent
310
+ end
311
+ end
312
+
281
313
  #
282
314
  # Save to a path that's already mapped to some file. If that
283
315
  # mapping is for a different root, it's a real collision -- the
@@ -44,6 +44,47 @@ module Gloo
44
44
  @children = []
45
45
  end
46
46
 
47
+ #
48
+ # The cleaned-up leading_doc: each line's leading whitespace and
49
+ # its '#' marker (plus one following space, if any) stripped,
50
+ # then the result dedented to its shallowest line. The full
51
+ # block is kept as written, blank '#' lines at the top/bottom
52
+ # included -- it's a faithful reproduction of the comment, not
53
+ # a trimmed summary. '' when there's no leading_doc at all.
54
+ #
55
+ def doc
56
+ return '' unless @leading_doc
57
+
58
+ lines = @leading_doc.split( "\n" ).map { |l| strip_marker( l ) }
59
+ return dedent( lines ).join( "\n" )
60
+ end
61
+
62
+ private
63
+
64
+ #
65
+ # One raw comment line -> its text past the '#': leading
66
+ # whitespace dropped, then the '#' and at most one space after
67
+ # it, then trailing whitespace dropped.
68
+ #
69
+ def strip_marker( raw )
70
+ return raw.lstrip.sub( /\A#\x20?/, '' ).rstrip
71
+ end
72
+
73
+ #
74
+ # Remove the common leading whitespace shared by every non-blank
75
+ # line, so a comment block indented for readability (eg. a
76
+ # bullet list within it) keeps its relative indentation.
77
+ #
78
+ def dedent( lines )
79
+ non_blank = lines.reject( &:empty? )
80
+ return lines if non_blank.empty?
81
+
82
+ n = non_blank.map { |l| l[ /\A */ ].length }.min
83
+ return lines if n.zero?
84
+
85
+ return lines.map { |l| l.empty? ? l : l[ n.. ] }
86
+ end
87
+
47
88
  end
48
89
  end
49
90
  end
@@ -74,6 +74,7 @@ module Gloo
74
74
  #
75
75
  def show_obj( obj, indent = ' ' )
76
76
  theme = @engine.theme
77
+ show_doc( obj, indent ) if @engine.settings.list_docs
77
78
  if obj.multiline_value? && obj.value_is_array?
78
79
  str = theme.emphasis( "#{indent}#{obj.name}" )
79
80
  str << theme.accent( " [#{obj.type_display}] : " )
@@ -89,6 +90,23 @@ module Gloo
89
90
  end
90
91
  end
91
92
 
93
+ #
94
+ # Show the object's doc (its leading comment, cleaned up) above
95
+ # its listing line, one '#'-prefixed line per line of doc.
96
+ # Silent when the object has none.
97
+ #
98
+ def show_doc( obj, indent )
99
+ return if obj.doc.to_s.strip.empty?
100
+
101
+ theme = @engine.theme
102
+ # split( -1 ), not each_line -- a doc ending in a blank '#' line
103
+ # ends with "\n", and each_line silently drops that trailing
104
+ # empty line rather than yielding it.
105
+ obj.doc.split( "\n", -1 ).each do |line|
106
+ @engine.log.show theme.muted( "#{indent}# #{line}".rstrip )
107
+ end
108
+ end
109
+
92
110
  #
93
111
  # Determine how many levels to show.
94
112
  #
@@ -130,12 +148,16 @@ module Gloo
130
148
  'current context. When a path is provided, it will be ' \
131
149
  'listed instead of the current context. When using context, ' \
132
150
  'the current context will be shown, but when context has ' \
133
- 'not been set, the root will be shown.',
151
+ 'not been set, the root will be shown. When the list_docs ' \
152
+ 'setting is on, any listed object that has a doc (the ' \
153
+ 'comment block declared immediately above it in its source ' \
154
+ 'file) shows it too.',
134
155
  :syntax => [ 'list {path.to.object}' ],
135
156
  :parameters => [
136
157
  '{path.to.object} — Optional path to object that will be listed. When no path is provided, the current context is used.'
137
158
  ],
138
- :result => 'Object and children are listed out in the CLI.',
159
+ :result => 'Object and children are listed out in the CLI. ' \
160
+ 'Doc lines are included when the list_docs setting is on.',
139
161
  :errors => [
140
162
  "#{TARGET_MISSING_ERR}{path.to.object} — The object specified that is to be listed could not be found."
141
163
  ],
@@ -122,13 +122,24 @@ module Gloo
122
122
  :errors => [
123
123
  "#{MISSING_EXPR_ERR} — No expression is provided as parameter to the verb.",
124
124
  "#{UNKNOWN_OPT_ERR} — The reference type given isn't file, ext, or lib.",
125
- "#{WRONG_NUM_ARGS_ERR} — load expects 2 or 3 arguments (the verb, an optional reference type, and the file/ext/lib name)."
125
+ "#{WRONG_NUM_ARGS_ERR} — load expects 2 or 3 arguments (the verb, an optional reference type, and the file/ext/lib name).",
126
+ 'File not found: {name} — The file name given could not be ' \
127
+ 'resolved to a gloo file (checked as a full path, and ' \
128
+ 'relative to the project and gloo root folders).'
126
129
  ],
127
130
  :notes => 'Several files can contribute to one container: ' \
128
131
  'declare it in each file and add different children, and ' \
129
132
  'they merge in the heap. If two loaded files declare the ' \
130
133
  'same object with different values, the first one loaded ' \
131
- 'wins and a warning is logged.',
134
+ "wins and a warning is logged.\n\n" \
135
+ 'A contiguous run of whole-line comments declared ' \
136
+ 'immediately above an object (same indent, no blank line ' \
137
+ "in between) becomes that object's doc, captured as the " \
138
+ 'file loads. Read it back with tell {obj} to doc or ' \
139
+ 'check {obj} for doc, or see it inline in the object tree ' \
140
+ 'with list when the list_docs setting is on. When a name ' \
141
+ 'is declared in more than one loaded file, the first ' \
142
+ 'non-empty doc wins, same as the first value.',
132
143
  :examples => <<~EXAMPLES.strip
133
144
  > load my/project/file
134
145
  > load my/app/*
@@ -2,38 +2,36 @@
2
2
  # Each and repeat control object tests
3
3
  #
4
4
 
5
- tests [can] :
6
- ctrl [can] :
7
- each [can] :
5
+ tests.ctrl.each [can] :
8
6
 
9
- count [int] : 0
7
+ count [int] : 0
10
8
 
11
- wrd [each] :
12
- word [string] :
13
- IN [string] : one two three
14
- do [script] :
15
- eval ^^.count + 1
16
- put it into ^^.count
9
+ wrd [each] :
10
+ word [string] :
11
+ IN [string] : one two three
12
+ do [script] :
13
+ eval ^^.count + 1
14
+ put it into ^^.count
17
15
 
18
- rpt [repeat] :
19
- times [int] : 3
20
- index [int] : 0
21
- do [script] :
22
- eval ^^.count + 1
23
- put it into ^^.count
16
+ rpt [repeat] :
17
+ times [int] : 3
18
+ index [int] : 0
19
+ do [script] :
20
+ eval ^^.count + 1
21
+ put it into ^^.count
24
22
 
25
- each_word [test] :
26
- description [string] : Count words with each word
27
- on_test [script] :
28
- put 0 into ^^.count
29
- tell ^^.wrd to run
30
- eval ^^.count = 3
31
- assert "expected 3 words counted"
23
+ each_word [test] :
24
+ description [string] : Count words with each word
25
+ on_test [script] :
26
+ put 0 into ^^.count
27
+ tell ^^.wrd to run
28
+ eval ^^.count = 3
29
+ assert "expected 3 words counted"
32
30
 
33
- repeat_n_times [test] :
34
- description [string] : Repeat a script n times
35
- on_test [script] :
36
- put 0 into ^^.count
37
- tell ^^.rpt to run
38
- eval ^^.count = 3
39
- assert "expected repeat to run 3 times"
31
+ repeat_n_times [test] :
32
+ description [string] : Repeat a script n times
33
+ on_test [script] :
34
+ put 0 into ^^.count
35
+ tell ^^.rpt to run
36
+ eval ^^.count = 3
37
+ assert "expected repeat to run 3 times"