gloo 6.0.1 → 6.2.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.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/CLAUDE.md +9 -3
  3. data/README.md +39 -7
  4. data/docs/application.md +184 -0
  5. data/docs/getting_started.md +110 -0
  6. data/docs/iterators.md +294 -0
  7. data/docs/language_objects.md +190 -0
  8. data/docs/language_scripting.md +62 -0
  9. data/docs/language_syntax.md +334 -0
  10. data/docs/objects.md +77 -0
  11. data/docs/operators.md +62 -0
  12. data/docs/plugins.md +376 -0
  13. data/docs/verbs.md +64 -0
  14. data/docs/web_app.md +211 -0
  15. data/gloo.gemspec +3 -1
  16. data/lib/VERSION +1 -1
  17. data/lib/VERSION_NOTES +16 -0
  18. data/lib/gloo/app/engine.rb +17 -2
  19. data/lib/gloo/app/log.rb +5 -4
  20. data/lib/gloo/app/platform.rb +34 -2
  21. data/lib/gloo/app/prompt.rb +2 -1
  22. data/lib/gloo/app/settings.rb +25 -15
  23. data/lib/gloo/app/table.rb +7 -3
  24. data/lib/gloo/app/theme.rb +144 -0
  25. data/lib/gloo/core/gloo_system.rb +52 -1
  26. data/lib/gloo/core/invoker.rb +102 -0
  27. data/lib/gloo/core/parser.rb +48 -7
  28. data/lib/gloo/core/tokens.rb +81 -1
  29. data/lib/gloo/docs/doc_data.rb +160 -0
  30. data/lib/gloo/docs/help_shell.rb +332 -0
  31. data/lib/gloo/docs/markdown_renderer.rb +39 -0
  32. data/lib/gloo/expr/call.rb +54 -0
  33. data/lib/gloo/expr/expression.rb +3 -0
  34. data/lib/gloo/objs/basic/alias.rb +53 -0
  35. data/lib/gloo/objs/basic/boolean.rb +30 -0
  36. data/lib/gloo/objs/basic/container.rb +38 -0
  37. data/lib/gloo/objs/basic/decimal.rb +30 -0
  38. data/lib/gloo/objs/basic/integer.rb +58 -0
  39. data/lib/gloo/objs/basic/script.rb +31 -0
  40. data/lib/gloo/objs/basic/string.rb +31 -4
  41. data/lib/gloo/objs/basic/string_msgs.rb +212 -2
  42. data/lib/gloo/objs/basic/text.rb +30 -4
  43. data/lib/gloo/objs/basic/untyped.rb +21 -0
  44. data/lib/gloo/objs/ctrl/each.rb +63 -1
  45. data/lib/gloo/objs/ctrl/function.rb +86 -4
  46. data/lib/gloo/objs/ctrl/repeat.rb +41 -0
  47. data/lib/gloo/objs/dt/date.rb +43 -0
  48. data/lib/gloo/objs/dt/datetime.rb +50 -0
  49. data/lib/gloo/objs/dt/time.rb +43 -0
  50. data/lib/gloo/objs/str_utils/cipher.rb +59 -0
  51. data/lib/gloo/objs/str_utils/outline.rb +65 -1
  52. data/lib/gloo/objs/str_utils/password.rb +56 -0
  53. data/lib/gloo/objs/system/erb.rb +39 -0
  54. data/lib/gloo/objs/system/file_handle.rb +56 -0
  55. data/lib/gloo/objs/system/system.rb +29 -0
  56. data/lib/gloo/objs/web/http_get.rb +35 -0
  57. data/lib/gloo/objs/web/http_post.rb +33 -0
  58. data/lib/gloo/objs/web/json.rb +40 -0
  59. data/lib/gloo/objs/web/uri.rb +40 -0
  60. data/lib/gloo/shell/command_node.rb +39 -0
  61. data/lib/gloo/shell/context.rb +93 -0
  62. data/lib/gloo/shell/runner.rb +315 -0
  63. data/lib/gloo/verbs/break.rb +32 -0
  64. data/lib/gloo/verbs/check.rb +49 -0
  65. data/lib/gloo/verbs/cls.rb +18 -0
  66. data/lib/gloo/verbs/context.rb +47 -0
  67. data/lib/gloo/verbs/create.rb +42 -0
  68. data/lib/gloo/verbs/eval.rb +27 -0
  69. data/lib/gloo/verbs/execute.rb +27 -0
  70. data/lib/gloo/verbs/exists.rb +54 -2
  71. data/lib/gloo/verbs/files.rb +22 -0
  72. data/lib/gloo/verbs/help.rb +46 -178
  73. data/lib/gloo/verbs/if.rb +52 -0
  74. data/lib/gloo/verbs/invoke.rb +88 -33
  75. data/lib/gloo/verbs/list.rb +37 -4
  76. data/lib/gloo/verbs/load.rb +55 -2
  77. data/lib/gloo/verbs/log.rb +44 -0
  78. data/lib/gloo/verbs/move.rb +36 -1
  79. data/lib/gloo/verbs/put.rb +35 -0
  80. data/lib/gloo/verbs/quit.rb +19 -0
  81. data/lib/gloo/verbs/redirect.rb +52 -2
  82. data/lib/gloo/verbs/reload.rb +27 -0
  83. data/lib/gloo/verbs/run.rb +39 -0
  84. data/lib/gloo/verbs/save.rb +26 -0
  85. data/lib/gloo/verbs/show.rb +54 -0
  86. data/lib/gloo/verbs/tell.rb +32 -0
  87. data/lib/gloo/verbs/throw.rb +29 -0
  88. data/lib/gloo/verbs/unless.rb +50 -0
  89. data/lib/gloo/verbs/unload.rb +23 -0
  90. data/lib/gloo/verbs/version.rb +37 -3
  91. data/lib/gloo/verbs/wait.rb +26 -0
  92. data/test.gloo/objs/string.test.gloo +54 -0
  93. data/test.gloo/verbs/invoke.test.gloo +73 -0
  94. metadata +26 -10
  95. data/.DS_Store +0 -0
  96. data/.travis.yml +0 -5
@@ -47,6 +47,7 @@ module Gloo
47
47
  end
48
48
 
49
49
  return @left.value if @left.is_a? Gloo::Core::Literal
50
+ return @left.value if @left.is_a? Gloo::Expr::Call
50
51
  return resolve_ref @left if @left.is_a? Gloo::Core::Pn
51
52
 
52
53
  return @left
@@ -75,6 +76,7 @@ module Gloo
75
76
  #
76
77
  def evaluate_sym( sym )
77
78
  return sym.value if sym.is_a? Gloo::Core::Literal
79
+ return sym.value if sym.is_a? Gloo::Expr::Call
78
80
  return resolve_ref sym if sym.is_a? Gloo::Core::Pn
79
81
 
80
82
  return sym
@@ -109,6 +111,7 @@ module Gloo
109
111
  return LInteger.new( token ) if LInteger.integer?( token )
110
112
  return LString.new( token ) if LString.string?( token )
111
113
  return LDecimal.new( token ) if LDecimal.decimal?( token )
114
+ return Gloo::Expr::Call.new( @engine, token ) if Gloo::Expr::Call.call?( token )
112
115
 
113
116
  # last chance: an Object reference
114
117
  return Gloo::Core::Pn.new( @engine, token )
@@ -73,6 +73,59 @@ module Gloo
73
73
  return ln.resolve
74
74
  end
75
75
 
76
+ # ---------------------------------------------------------------------
77
+ # Object Documentation
78
+ # ---------------------------------------------------------------------
79
+
80
+ #
81
+ # Get the object's documentation data.
82
+ #
83
+ def self.doc_data
84
+ {
85
+ :name => KEYWORD,
86
+ :shortcut => KEYWORD_SHORT,
87
+ :description => 'A pointer to another object. Normal ' \
88
+ 'path-name references will refer to the aliased object. To ' \
89
+ 'refer to the alias itself, add an * at the end of the ' \
90
+ 'path-name — needed, for example, to set the value of the ' \
91
+ 'alias. The value of the alias is merely the path-name of ' \
92
+ 'the referenced object. Well constructed aliases will ' \
93
+ 'redirect to the referenced object through any number of ' \
94
+ 'steps, and relative references will also work in an alias.',
95
+ :messages => [
96
+ 'resolve — Check to see if the object referenced exists. Sets it to true or false.'
97
+ ],
98
+ :notes => 'The alias also reflects (forwards) the messages of the object it points to.',
99
+ :examples => <<~EXAMPLES.strip
100
+ #
101
+ # Alias object.
102
+ #
103
+
104
+ a [can] :
105
+ s [string] : a string
106
+ i [integer] : 13
107
+ ln [alias] : a.s
108
+
109
+ on_load [script] :
110
+ show a.ln
111
+ show a.ln*
112
+ put 'a.i' into a.ln*
113
+ put 7 into a.ln
114
+ show a.ln
115
+ run a.add
116
+
117
+ #
118
+ # Example of creating an object using an alias.
119
+ #
120
+ add [script] :
121
+ put 'x' into a.ln*
122
+ create a.ln* as string
123
+ put 'test' into x
124
+ show a.ln
125
+ EXAMPLES
126
+ }
127
+ end
128
+
76
129
  end
77
130
  end
78
131
  end
@@ -115,6 +115,36 @@ module Gloo
115
115
  return false
116
116
  end
117
117
 
118
+ # ---------------------------------------------------------------------
119
+ # Object Documentation
120
+ # ---------------------------------------------------------------------
121
+
122
+ #
123
+ # Get the object's documentation data.
124
+ #
125
+ def self.doc_data
126
+ {
127
+ :name => KEYWORD,
128
+ :shortcut => KEYWORD_SHORT,
129
+ :description => 'A boolean value. Value will be either true or false.',
130
+ :messages => [
131
+ 'not — Set the boolean to the opposite of what it is now.',
132
+ 'true — Set the boolean to true.',
133
+ 'false — Set the boolean to false.'
134
+ ],
135
+ :examples => <<~EXAMPLES.strip
136
+ b [can] :
137
+ flag [boolean] : true
138
+ on_load [script] :
139
+ show b.flag
140
+ put false into b.flag
141
+ show b.flag
142
+ tell b.flag to not
143
+ show b.flag
144
+ EXAMPLES
145
+ }
146
+ end
147
+
118
148
  end
119
149
  end
120
150
  end
@@ -75,6 +75,44 @@ module Gloo
75
75
  @engine.heap.it.set_to val
76
76
  return val
77
77
  end
78
+
79
+ # ---------------------------------------------------------------------
80
+ # Object Documentation
81
+ # ---------------------------------------------------------------------
82
+
83
+ #
84
+ # Get the object's documentation data.
85
+ #
86
+ def self.doc_data
87
+ {
88
+ :name => KEYWORD,
89
+ :shortcut => KEYWORD_SHORT,
90
+ :description => 'A container of other objects. A container is ' \
91
+ 'similar to a folder in a file system. It can contain any ' \
92
+ 'number of objects including other containers. The ' \
93
+ 'container structure provides direct access to any object ' \
94
+ 'within it through the object.object.object path-name structure.',
95
+ :children => [
96
+ 'None by default — but any container can have any number of objects added to it, at runtime.'
97
+ ],
98
+ :messages => [
99
+ 'count — Count the number of children objects in the container. The result is put in it.',
100
+ 'delete_children — Delete all children objects from the container.',
101
+ 'show_key_value_table — Show a table with key (name) and values for all children in the container.',
102
+ 'child_exists ({name}) — Check to see if there is a child with the given name. A parameter is required. It will have a boolean.'
103
+ ],
104
+ :examples => <<~EXAMPLES.strip
105
+ can [can] :
106
+ data [can] :
107
+ 1 : one
108
+ 2 : two
109
+ 3 : three
110
+ on_load [script] :
111
+ tell can.data to show_key_value_table
112
+ EXAMPLES
113
+ }
114
+ end
115
+
78
116
  end
79
117
  end
80
118
  end
@@ -92,6 +92,36 @@ module Gloo
92
92
  @engine.heap.it.set_to formatted
93
93
  end
94
94
 
95
+ # ---------------------------------------------------------------------
96
+ # Object Documentation
97
+ # ---------------------------------------------------------------------
98
+
99
+ #
100
+ # Get the object's documentation data.
101
+ #
102
+ def self.doc_data
103
+ {
104
+ :name => KEYWORD,
105
+ :shortcut => KEYWORD_SHORT,
106
+ :description => 'A decimal (numeric) value.',
107
+ :messages => [
108
+ 'round ({precision}) — Round to the nearest whole value. If ' \
109
+ 'an optional parameter is included, round to the precision specified.',
110
+ 'format ({fmt}) — With no parameter, adds comma separators to the whole part (e.g. 1000.5 -> 1,000.5). With a parameter, uses it as a sprintf-style format string (e.g. \'%.2f\'). It will have the formatted string.'
111
+ ],
112
+ :examples => <<~EXAMPLES.strip
113
+ d [can] :
114
+ x [decimal] : 100
115
+ on_load [script] :
116
+ show d.x
117
+ put d.x / 3 into d.x
118
+ show d.x
119
+ tell d.x to round (1)
120
+ show d.x
121
+ EXAMPLES
122
+ }
123
+ end
124
+
95
125
  end
96
126
  end
97
127
  end
@@ -118,6 +118,64 @@ module Gloo
118
118
  @engine.heap.it.set_to formatted
119
119
  end
120
120
 
121
+ # ---------------------------------------------------------------------
122
+ # Object Documentation
123
+ # ---------------------------------------------------------------------
124
+
125
+ #
126
+ # Get the object's documentation data.
127
+ #
128
+ def self.doc_data
129
+ {
130
+ :name => KEYWORD,
131
+ :shortcut => KEYWORD_SHORT,
132
+ :description => 'An integer (numeric) value.',
133
+ :messages => [
134
+ 'inc — Increment the integer value by 1.',
135
+ 'dec — Decrement the integer value by 1.',
136
+ 'randomize ({max}) — Set the value of the integer to a random ' \
137
+ 'number. By default the range is 0..99 inclusive. Use an ' \
138
+ 'optional parameter to set the maximum of the range; the ' \
139
+ 'range always starts at 0. To model a 6-sided die, set the ' \
140
+ 'range to 6 and add 1 to the result.',
141
+ 'format ({fmt}) — With no parameter, adds comma separators (e.g. 1000 -> 1,000). With a parameter, uses it as a sprintf-style format string (e.g. \'%05d\'). It will have the formatted string.'
142
+ ],
143
+ :examples => <<~EXAMPLES.strip
144
+ #
145
+ # Integer object.
146
+ #
147
+
148
+ i [can] :
149
+
150
+ #
151
+ # The integer value.
152
+ #
153
+ x [integer] : 0
154
+
155
+ #
156
+ # Do some basic tests with it
157
+ #
158
+ on_load [script] :
159
+ show i.x
160
+ tell i.x to inc
161
+ show i.x
162
+ put i.x * 10 into i.x
163
+ show i.x
164
+
165
+ # Show a random number
166
+ tell ^.x to randomize
167
+ show 'Random number (up to 100 by default): ' + ^.x
168
+
169
+ tell ^.x to randomize(6)
170
+ tell ^.x to inc
171
+ show '6-sided dice: ' + ^.x
172
+
173
+ # An uninitialized integer.
174
+ y [integer] :
175
+ EXAMPLES
176
+ }
177
+ end
178
+
121
179
  end
122
180
  end
123
181
  end
@@ -94,6 +94,37 @@ module Gloo
94
94
  s.run
95
95
  end
96
96
 
97
+ # ---------------------------------------------------------------------
98
+ # Object Documentation
99
+ # ---------------------------------------------------------------------
100
+
101
+ #
102
+ # Get the object's documentation data.
103
+ #
104
+ def self.doc_data
105
+ {
106
+ :name => KEYWORD,
107
+ :shortcut => KEYWORD_SHORT,
108
+ :description => 'An executable script — a set of commands to be run.',
109
+ :messages => [
110
+ 'run — Run the script. The script can be run by telling the ' \
111
+ 'object to run, or via the run verb.'
112
+ ],
113
+ :examples => <<~EXAMPLES.strip
114
+ script [can] :
115
+ on_load [script] :
116
+ show "Showing multiple lines..."
117
+ show script.msg1
118
+ show script.msg2
119
+ show script.msg3
120
+ show "Done."
121
+ msg1 [string] : one
122
+ msg2 [string] : two
123
+ msg3 [string] : three
124
+ EXAMPLES
125
+ }
126
+ end
127
+
97
128
  end
98
129
  end
99
130
  end
@@ -44,10 +44,37 @@ module Gloo
44
44
  # Get a list of message names that this object receives.
45
45
  #
46
46
  def self.messages
47
- return super + %w[up down size starts_with? ends_with? substring? sub gsub
48
- count_lines count_words count_chars trim
49
- format_for_html encode64 decode64 escape unescape
50
- gen_alphanumeric gen_uuid gen_hex gen_base64]
47
+ return super + StringMsgs.messages
48
+ end
49
+
50
+ # ---------------------------------------------------------------------
51
+ # Object Documentation
52
+ # ---------------------------------------------------------------------
53
+
54
+ #
55
+ # Get the object's documentation data.
56
+ #
57
+ def self.doc_data
58
+ {
59
+ :name => KEYWORD,
60
+ :shortcut => KEYWORD_SHORT,
61
+ :description => 'A string value. For string interpolation, ' \
62
+ 'see the erb object type. Shares the same messages as the ' \
63
+ 'text object type; the two differ mainly by convention — ' \
64
+ 'string for a single word or line, text for longer, ' \
65
+ 'multi-line blocks.',
66
+ :messages => StringMsgs.message_docs,
67
+ :examples => <<~EXAMPLES.strip
68
+ s [can] :
69
+ msg [string] : Hello World!
70
+ on_load [script] :
71
+ show s.msg
72
+ tell s.msg to up
73
+ show s.msg
74
+ tell s.msg to size
75
+ show it
76
+ EXAMPLES
77
+ }
51
78
  end
52
79
 
53
80
  end
@@ -10,8 +10,57 @@ module Gloo
10
10
  module Objs
11
11
  module StringMsgs
12
12
 
13
+ #
14
+ # Get the list of message names this mixin implements, derived
15
+ # from its own msg_* methods so String/Text don't have to
16
+ # hand-duplicate the list (and can't drift out of sync with it).
17
+ #
18
+ def self.messages
19
+ return instance_methods( false )
20
+ .select { |m| m.to_s.start_with?( 'msg_' ) }
21
+ .map { |m| m.to_s.sub( /\Amsg_/, '' ) }
22
+ end
13
23
 
14
- #
24
+ #
25
+ # Documentation for each message this mixin implements, for use
26
+ # in String and Text's doc_data (see help_shell). Kept here as
27
+ # the single source, since String and Text share the exact
28
+ # same message set and would otherwise have to keep two
29
+ # hand-written copies of this list in sync.
30
+ #
31
+ def self.message_docs
32
+ return [
33
+ 'up — Convert the string to uppercase. This message changes the value of the string.',
34
+ 'down — Convert the string to lowercase. This message changes the value of the string.',
35
+ 'size — Get the size of the string. It will have the string size.',
36
+ 'count_chars — Count the number of characters in the string. It will have the character count.',
37
+ 'count_words — Count the number of words in the string. It will have the word count.',
38
+ 'count_lines — Count the number of lines in the string. It will have the line count.',
39
+ 'starts_with? ({str}) — Check if the string starts with the given string. A parameter is required: the string to look for at the beginning of this string. It will have a boolean.',
40
+ 'ends_with? ({str}) — Check if the string ends with the given string. A parameter is required: the string to look for at the end of this string. It will have a boolean.',
41
+ 'substring? ({str}) — Check if the string includes the given sub-string. A parameter is required: the string to look for in this string. It will have a boolean.',
42
+ 'format_for_html — Format this string for HTML output. Tabs, spaces and returns are converted to HTML elements. The value of the string is changed.',
43
+ 'encode64 — Base64 encode the string. This message changes the value of the string. It will have the encoded string.',
44
+ 'decode64 — Decode the string from Base64. This message changes the value of the string. It will have the decoded string.',
45
+ 'escape — Escape the string to make it URL safe. This message changes the value of the string. It will have the escaped string.',
46
+ 'unescape — Unescape the string (from URL safe format). This message changes the value of the string. It will have the unescaped string.',
47
+ 'gen_uuid — Set the value of the string to a newly generated, random UUID. This message changes the value of the string.',
48
+ 'gen_alphanumeric ({len}) — Set the value of the string to a newly generated, random alphanumeric string. The {len} parameter is optional; the length is 10 if not specified. This message changes the value of the string.',
49
+ 'gen_hex ({len}) — Set the value of the string to a newly generated, random hex string. The {len} parameter is optional; the length is 10 if not specified. This message changes the value of the string.',
50
+ 'gen_base64 ({len}) — Set the value of the string to a newly generated, random base64 string. The {len} parameter is optional; the length is 12 if not specified. This message changes the value of the string.',
51
+ 'trim — Strip whitespace from the beginning and end of the string. This message changes the value of the string. It will have the trimmed string.',
52
+ 'sub ({from} {to}) — Substitute the first occurrence of {from} with {to}. Both parameters are required. This message changes the value of the string. It will have the result.',
53
+ 'gsub ({from} {to}) — Substitute all occurrences of {from} with {to}. Both parameters are required. This message changes the value of the string. It will have the result.',
54
+ 'split ({from} {to}) — Get the substring from index {from} up to (not including) index {to}. Indexes are 0-based; out-of-range indexes are clamped to the start or end of the string. Both parameters are required. Does not change the value of the string. It will have the substring.',
55
+ 'splitl ({index}) — Get the substring to the left of index {index} (same as split (0, {index})). A parameter is required. Does not change the value of the string. It will have the substring.',
56
+ 'splitr ({index}) — Get the substring from index {index} to the end of the string (same as split ({index}, size)). A parameter is required. Does not change the value of the string. It will have the substring.',
57
+ 'split_list ({delim} {dst.path}) — Split the string by {delim} and put the parts into children of the container at {dst.path} (or an alias that points to one), one part per child, in order. Existing children are matched by position and have their values set; extra parts get new (untyped) children, numbered from 1; extra existing children are left alone. Both parameters are required. Does not change the value of the string. It will have the number of parts.',
58
+ 'page — Show the value in a pager (less), for viewing long content a screen at a time.'
59
+ ]
60
+ end
61
+
62
+
63
+ #
15
64
  # Strip whitespace from the beginning and end of the string.
16
65
  #
17
66
  def msg_trim
@@ -109,7 +158,115 @@ module Gloo
109
158
  end
110
159
  end
111
160
 
112
- #
161
+ #
162
+ # Get the substring from index {from} up to (not including) index {to}.
163
+ # Indexes are 0-based. Out-of-range indexes are clamped to the
164
+ # beginning or end of the string. Does not change the string's value.
165
+ #
166
+ def msg_split
167
+ return '' unless value
168
+
169
+ if @params&.token_count&.positive?
170
+ expr = Gloo::Expr::Expression.new( @engine, [ @params.tokens.first ] )
171
+ from = expr.evaluate.to_i
172
+ expr = Gloo::Expr::Expression.new( @engine, [ @params.tokens.last ] )
173
+ to = expr.evaluate.to_i
174
+
175
+ result = clamped_substring( from, to )
176
+ @engine.heap.it.set_to result
177
+ return result
178
+ else
179
+ # Error
180
+ @engine.log.error MISSING_PARAM_MSG
181
+ @engine.heap.it.set_to false
182
+ return false
183
+ end
184
+ end
185
+
186
+ #
187
+ # Get the substring to the left of (not including) index {index}.
188
+ # Same as split( 0, index ). Does not change the string's value.
189
+ #
190
+ def msg_splitl
191
+ return '' unless value
192
+
193
+ if @params&.token_count&.positive?
194
+ expr = Gloo::Expr::Expression.new( @engine, @params.tokens )
195
+ index = expr.evaluate.to_i
196
+
197
+ result = clamped_substring( 0, index )
198
+ @engine.heap.it.set_to result
199
+ return result
200
+ else
201
+ # Error
202
+ @engine.log.error MISSING_PARAM_MSG
203
+ @engine.heap.it.set_to false
204
+ return false
205
+ end
206
+ end
207
+
208
+ #
209
+ # Get the substring from index {index} to the end of the string.
210
+ # Same as split( index, size ). Does not change the string's value.
211
+ #
212
+ def msg_splitr
213
+ return '' unless value
214
+
215
+ if @params&.token_count&.positive?
216
+ expr = Gloo::Expr::Expression.new( @engine, @params.tokens )
217
+ index = expr.evaluate.to_i
218
+
219
+ result = clamped_substring( index, value.length )
220
+ @engine.heap.it.set_to result
221
+ return result
222
+ else
223
+ # Error
224
+ @engine.log.error MISSING_PARAM_MSG
225
+ @engine.heap.it.set_to false
226
+ return false
227
+ end
228
+ end
229
+
230
+ #
231
+ # Split the string by the given delimiter and put the parts into
232
+ # children of the target container, one part per child, in order.
233
+ # The target is a path to a container, or to an alias that points
234
+ # to one. Existing children are matched by position (not name) and
235
+ # have their values set; if there are more parts than children,
236
+ # new (untyped) children are created for the extras, numbered
237
+ # from 1. Existing children beyond the part count are left alone.
238
+ # The string's own value is unchanged; the number of parts is put
239
+ # into 'it'.
240
+ #
241
+ def msg_split_list
242
+ return unless value
243
+
244
+ if @params&.token_count.to_i < 2
245
+ @engine.log.error MISSING_PARAM_MSG
246
+ @engine.heap.it.set_to false
247
+ return false
248
+ end
249
+
250
+ expr = Gloo::Expr::Expression.new( @engine, [ @params.tokens.first ] )
251
+ delim = expr.evaluate
252
+
253
+ target = split_list_target( @params.tokens.last )
254
+ return false unless target
255
+
256
+ parts = value.split( delim )
257
+ existing = target.children
258
+ parts.each_with_index do |part, index|
259
+ child = existing[ index ]
260
+ child ||= target.find_add_child( ( index + 1 ).to_s, 'untyped' )
261
+ child.set_value part
262
+ end
263
+
264
+ count = parts.count
265
+ @engine.heap.it.set_to count
266
+ return count
267
+ end
268
+
269
+ #
113
270
  # Does the string contain the given string?
114
271
  #
115
272
  # This was formerly an overload of obj.contains?
@@ -324,6 +481,59 @@ module Gloo
324
481
  return s
325
482
  end
326
483
 
484
+ #
485
+ # Show the value in a pager, for long content.
486
+ #
487
+ def msg_page
488
+ return unless value
489
+
490
+ @engine.platform.page( value )
491
+ end
492
+
493
+ private
494
+
495
+ #
496
+ # Resolve the target container path for split_list. The path may
497
+ # point directly at a container, or at an alias that points to
498
+ # one. Returns nil (and logs an error, setting 'it' to false)
499
+ # if the path doesn't exist or doesn't resolve to a container.
500
+ #
501
+ def split_list_target( token )
502
+ pn = Gloo::Core::Pn.new( @engine, token )
503
+ unless pn&.exists?
504
+ @engine.log.error 'Target container path does not exist!'
505
+ @engine.heap.it.set_to false
506
+ return nil
507
+ end
508
+
509
+ target = pn.resolve
510
+ target = Gloo::Objs::Alias.resolve_alias( @engine, target )
511
+ unless target&.is_container?
512
+ @engine.log.error 'Target for split_list must be a container!'
513
+ @engine.heap.it.set_to false
514
+ return nil
515
+ end
516
+
517
+ return target
518
+ end
519
+
520
+ #
521
+ # Get the substring from index {from} up to (not including) index {to}.
522
+ # Out-of-range indexes are clamped to the string's own bounds (0 and
523
+ # its length), rather than raising an error. A degenerate range
524
+ # (from at or past to) returns an empty string.
525
+ #
526
+ def clamped_substring( from, to )
527
+ len = value.length
528
+ from = 0 if from.negative?
529
+ from = len if from > len
530
+ to = 0 if to.negative?
531
+ to = len if to > len
532
+ return '' if from >= to
533
+
534
+ return value[ from...to ]
535
+ end
536
+
327
537
  end
328
538
  end
329
539
  end
@@ -58,10 +58,36 @@ module Gloo
58
58
  # Get a list of message names that this object receives.
59
59
  #
60
60
  def self.messages
61
- return super + %w[up down size starts_with? ends_with? substring? sub gsub
62
- count_lines count_words count_chars trim
63
- format_for_html encode64 decode64 escape unescape
64
- gen_alphanumeric gen_uuid gen_hex gen_base64]
61
+ return super + StringMsgs.messages
62
+ end
63
+
64
+ # ---------------------------------------------------------------------
65
+ # Object Documentation
66
+ # ---------------------------------------------------------------------
67
+
68
+ #
69
+ # Get the object's documentation data.
70
+ #
71
+ def self.doc_data
72
+ {
73
+ :name => KEYWORD,
74
+ :shortcut => KEYWORD_SHORT,
75
+ :description => 'A longer, multi-line text string. Use BEGIN ' \
76
+ 'and END to mark the text range. Shares the same messages ' \
77
+ 'as the string object type; the two differ mainly by ' \
78
+ 'convention — text for longer, multi-line blocks, string ' \
79
+ 'for a single word or line.',
80
+ :messages => StringMsgs.message_docs,
81
+ :examples => <<~EXAMPLES.strip
82
+ t [container] :
83
+ msg [txt] : BEGIN
84
+ I will now write a poem
85
+ of two lines or less
86
+ END
87
+ on_load [script] :
88
+ show t.msg
89
+ EXAMPLES
90
+ }
65
91
  end
66
92
 
67
93
  end
@@ -36,6 +36,27 @@ module Gloo
36
36
  return super
37
37
  end
38
38
 
39
+ # ---------------------------------------------------------------------
40
+ # Object Documentation
41
+ # ---------------------------------------------------------------------
42
+
43
+ #
44
+ # Get the object's documentation data.
45
+ #
46
+ def self.doc_data
47
+ {
48
+ :name => KEYWORD,
49
+ :shortcut => KEYWORD_SHORT,
50
+ :description => 'An untyped object. If no type is specified ' \
51
+ 'when an object is created it will be of this type.',
52
+ :examples => <<~EXAMPLES.strip
53
+ > create x
54
+ > put 1 into x
55
+ > put 'string' into x
56
+ EXAMPLES
57
+ }
58
+ end
59
+
39
60
  end
40
61
  end
41
62
  end