gloo 7.0.3 → 7.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.
- checksums.yaml +4 -4
- data/README.md +2 -1
- data/docs/application.md +14 -2
- data/docs/development.md +1 -1
- data/docs/language_objects.md +37 -0
- data/docs/language_syntax.md +25 -0
- data/docs/objects.md +1 -1
- data/docs/plugins.md +9 -3
- data/docs/testing.md +206 -0
- data/docs/verbs.md +13 -0
- data/lib/VERSION +1 -1
- data/lib/VERSION_NOTES +15 -2
- data/lib/gloo/app/engine.rb +19 -9
- data/lib/gloo/app/settings.rb +20 -3
- data/lib/gloo/core/gloo_system.rb +7 -0
- data/lib/gloo/core/obj.rb +22 -1
- data/lib/gloo/core/tokens.rb +20 -0
- data/lib/gloo/core/verb.rb +20 -0
- data/lib/gloo/objs/basic/string_msgs.rb +6 -7
- data/lib/gloo/objs/basic/untyped.rb +2 -1
- data/lib/gloo/objs/system/file_handle.rb +5 -2
- data/lib/gloo/objs/web/json.rb +7 -5
- data/lib/gloo/persist/file_loader.rb +10 -0
- data/lib/gloo/persist/file_storage.rb +8 -2
- data/lib/gloo/persist/persist_man.rb +28 -0
- data/lib/gloo/verbs/context.rb +10 -2
- data/lib/gloo/verbs/exists.rb +14 -2
- data/lib/gloo/verbs/load.rb +16 -2
- data/lib/gloo/verbs/save.rb +14 -3
- data/test.gloo/ctrl/each.test.gloo +21 -7
- data/test.gloo/lang/declaration.test.gloo +7 -2
- data/test.gloo/lang/gloo_sys.test.gloo +12 -0
- data/test.gloo/lang/naming.test.gloo +7 -2
- data/test.gloo/objs/file.test.gloo +18 -2
- data/test.gloo/objs/fixtures/source_dir_fixture.gloo +6 -0
- data/test.gloo/objs/json.test.gloo +2 -0
- data/test.gloo/objs/obj.test.gloo +33 -0
- data/test.gloo/objs/string.test.gloo +2 -2
- data/test.gloo/verbs/context.test.gloo +12 -0
- data/test.gloo/verbs/exists.test.gloo +19 -0
- data/test.gloo/verbs/load.test.gloo +101 -2
- data/test.gloo/verbs/reload.test.gloo +9 -4
- data/test.gloo/verbs/save.test.gloo +41 -16
- metadata +5 -6
data/lib/gloo/core/obj.rb
CHANGED
|
@@ -338,7 +338,7 @@ module Gloo
|
|
|
338
338
|
# Get a list of message names that this object receives.
|
|
339
339
|
#
|
|
340
340
|
def self.messages
|
|
341
|
-
return %w[save reload unload blank? contains? responds_to? doc]
|
|
341
|
+
return %w[save reload unload blank? contains? responds_to? doc source_file source_dir]
|
|
342
342
|
end
|
|
343
343
|
|
|
344
344
|
#
|
|
@@ -492,6 +492,27 @@ module Gloo
|
|
|
492
492
|
return self.doc
|
|
493
493
|
end
|
|
494
494
|
|
|
495
|
+
#
|
|
496
|
+
# The path of the file this object was loaded from, into 'it';
|
|
497
|
+
# blank if it didn't come from a file.
|
|
498
|
+
#
|
|
499
|
+
def msg_source_file
|
|
500
|
+
path = @engine.persist_man.source_file_of( self ).to_s
|
|
501
|
+
@engine.heap.it.set_to path
|
|
502
|
+
return path
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
#
|
|
506
|
+
# The folder of the file this object was loaded from, with no
|
|
507
|
+
# trailing slash, into 'it'; blank if it didn't come from a file.
|
|
508
|
+
#
|
|
509
|
+
def msg_source_dir
|
|
510
|
+
path = @engine.persist_man.source_file_of( self )
|
|
511
|
+
dir = path ? File.dirname( path ) : ''
|
|
512
|
+
@engine.heap.it.set_to dir
|
|
513
|
+
return dir
|
|
514
|
+
end
|
|
515
|
+
|
|
495
516
|
|
|
496
517
|
# ---------------------------------------------------------------------
|
|
497
518
|
# Render
|
data/lib/gloo/core/tokens.rb
CHANGED
|
@@ -176,6 +176,17 @@ module Gloo
|
|
|
176
176
|
return nil
|
|
177
177
|
end
|
|
178
178
|
|
|
179
|
+
#
|
|
180
|
+
# A copy of these tokens with one more token added at the end,
|
|
181
|
+
# taken as is (not split on spaces or quotes). The original is
|
|
182
|
+
# left alone.
|
|
183
|
+
#
|
|
184
|
+
def with_token( token )
|
|
185
|
+
copy = dup
|
|
186
|
+
copy.append_token token
|
|
187
|
+
return copy
|
|
188
|
+
end
|
|
189
|
+
|
|
179
190
|
#
|
|
180
191
|
# Get the item after a given token.
|
|
181
192
|
#
|
|
@@ -186,6 +197,15 @@ module Gloo
|
|
|
186
197
|
return nil
|
|
187
198
|
end
|
|
188
199
|
|
|
200
|
+
protected
|
|
201
|
+
|
|
202
|
+
#
|
|
203
|
+
# Add a token at the end, in a new list (see with_token).
|
|
204
|
+
#
|
|
205
|
+
def append_token( token )
|
|
206
|
+
@tokens += [ token ]
|
|
207
|
+
end
|
|
208
|
+
|
|
189
209
|
# ---------------------------------------------------------------------
|
|
190
210
|
# Private functions
|
|
191
211
|
# ---------------------------------------------------------------------
|
data/lib/gloo/core/verb.rb
CHANGED
|
@@ -83,6 +83,26 @@ module Gloo
|
|
|
83
83
|
return true
|
|
84
84
|
end
|
|
85
85
|
|
|
86
|
+
#
|
|
87
|
+
# For a verb whose arguments are literal text (a file name, a
|
|
88
|
+
# path, a name): a trailing ( expr ) is worked out and its value
|
|
89
|
+
# added as the last argument, so the text can be computed, as in
|
|
90
|
+
# load ( $.gloo_tmp '/x.gloo' ). The parser has already split the
|
|
91
|
+
# parenthetical off into params. Returns false if the expression
|
|
92
|
+
# couldn't be worked out (already reported), true otherwise.
|
|
93
|
+
#
|
|
94
|
+
def take_evaluated_arg
|
|
95
|
+
return true unless @params
|
|
96
|
+
|
|
97
|
+
errors_before = @engine.heap.error.error_count
|
|
98
|
+
value = Gloo::Expr::Expression.new( @engine, @params.tokens ).evaluate
|
|
99
|
+
return false if @engine.heap.error.error_count > errors_before
|
|
100
|
+
|
|
101
|
+
@tokens = @tokens.with_token( value.to_s )
|
|
102
|
+
@params = nil
|
|
103
|
+
return true
|
|
104
|
+
end
|
|
105
|
+
|
|
86
106
|
#
|
|
87
107
|
# Run the verb.
|
|
88
108
|
#
|
|
@@ -57,7 +57,7 @@ module Gloo
|
|
|
57
57
|
'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.',
|
|
58
58
|
'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.',
|
|
59
59
|
'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.',
|
|
60
|
-
'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
|
|
60
|
+
'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 be true (also when the string is empty), or false if a parameter or the target is bad.',
|
|
61
61
|
'word_wrap ({width}) — Word-wrap the string to {width} columns, breaking on whitespace (never mid-word; an overlong word is left on its own line rather than broken). The {width} parameter is optional; it defaults to the terminal\'s current width. This message changes the value of the string. It will have the wrapped string.',
|
|
62
62
|
'page — Show the value in a pager (less), for viewing long content a screen at a time.'
|
|
63
63
|
]
|
|
@@ -239,11 +239,12 @@ module Gloo
|
|
|
239
239
|
# have their values set; if there are more parts than children,
|
|
240
240
|
# new (untyped) children are created for the extras, numbered
|
|
241
241
|
# from 1. Existing children beyond the part count are left alone.
|
|
242
|
-
# The string's own value is unchanged
|
|
243
|
-
#
|
|
242
|
+
# The string's own value is unchanged. 'it' is true once the parts
|
|
243
|
+
# are in place (also when there's nothing to split), or false if
|
|
244
|
+
# the parameters or the target are bad.
|
|
244
245
|
#
|
|
245
246
|
def msg_split_list
|
|
246
|
-
return @engine.heap.it.set_to(
|
|
247
|
+
return @engine.heap.it.set_to( true ) unless value
|
|
247
248
|
|
|
248
249
|
if @params&.token_count.to_i < 2
|
|
249
250
|
@engine.syntax_err MISSING_PARAM_MSG
|
|
@@ -265,9 +266,7 @@ module Gloo
|
|
|
265
266
|
child.set_value part
|
|
266
267
|
end
|
|
267
268
|
|
|
268
|
-
|
|
269
|
-
@engine.heap.it.set_to count
|
|
270
|
-
return count
|
|
269
|
+
return @engine.heap.it.set_to( true )
|
|
271
270
|
end
|
|
272
271
|
|
|
273
272
|
#
|
|
@@ -50,7 +50,8 @@ module Gloo
|
|
|
50
50
|
:description => 'An object with no type. A type declares ' \
|
|
51
51
|
'which messages an object can receive; an untyped object ' \
|
|
52
52
|
'receives only the base object messages (blank?, ' \
|
|
53
|
-
'contains?, responds_to?,
|
|
53
|
+
'contains?, responds_to?, doc, source_dir, source_file, ' \
|
|
54
|
+
'reload, unload) but can hold a ' \
|
|
54
55
|
'value of any kind. If no type is specified when an object ' \
|
|
55
56
|
'is created, it is untyped. Prefer untyped when you only ' \
|
|
56
57
|
'need to hold, compare, or show a value and do not need ' \
|
|
@@ -74,7 +74,9 @@ module Gloo
|
|
|
74
74
|
end
|
|
75
75
|
|
|
76
76
|
#
|
|
77
|
-
# Read the contents of the file
|
|
77
|
+
# Read the contents of the file. With a destination, the contents
|
|
78
|
+
# go into it and 'it' is true; with none, the contents go in 'it'.
|
|
79
|
+
# 'it' is false if the file can't be read.
|
|
78
80
|
#
|
|
79
81
|
def msg_read
|
|
80
82
|
return @engine.heap.it.set_to( false ) unless check_file_exists?
|
|
@@ -91,6 +93,7 @@ module Gloo
|
|
|
91
93
|
end
|
|
92
94
|
|
|
93
95
|
o.set_value data
|
|
96
|
+
return @engine.heap.it.set_to( true )
|
|
94
97
|
end
|
|
95
98
|
@engine.heap.it.set_to data
|
|
96
99
|
end
|
|
@@ -290,7 +293,7 @@ module Gloo
|
|
|
290
293
|
'on disk. The string value of the file object is the path ' \
|
|
291
294
|
'and name of the file.',
|
|
292
295
|
:messages => [
|
|
293
|
-
'read ({into.obj}) — Read the file and put the data in the specified object. If {into.obj} is not specified, the data will be in it. If {into.obj} does not exist, that is an error.',
|
|
296
|
+
'read ({into.obj}) — Read the file and put the data in the specified object; it will be true. If {into.obj} is not specified, the data will be in it. If {into.obj} does not exist, or the file can\'t be read, that is an error and it will be false.',
|
|
294
297
|
'write ({from.obj}) — Write the data in {from.obj} into the file.',
|
|
295
298
|
'append ({data}) — Append the given data to the file as a new line. Adds a leading newline first if the file doesn\'t already end with one.',
|
|
296
299
|
'delete — Delete the file.',
|
data/lib/gloo/objs/web/json.rb
CHANGED
|
@@ -131,26 +131,28 @@ module Gloo
|
|
|
131
131
|
#
|
|
132
132
|
# Parse the JSON data and put it in objects.
|
|
133
133
|
# The additional parameter is the path to the destination
|
|
134
|
-
# for the parsed objects.
|
|
134
|
+
# for the parsed objects. 'it' is true once they're built, or
|
|
135
|
+
# false if the destination or the JSON is bad.
|
|
135
136
|
#
|
|
136
137
|
def msg_parse
|
|
137
138
|
if @params&.token_count&.positive?
|
|
138
139
|
pn = Gloo::Core::Pn.new( @engine, @params.tokens.first )
|
|
139
140
|
unless pn&.exists?
|
|
140
141
|
@engine.err Gloo::Core::NotFound.object( @params.tokens.first )
|
|
141
|
-
return
|
|
142
|
+
return @engine.heap.it.set_to( false )
|
|
142
143
|
end
|
|
143
144
|
else
|
|
144
145
|
@engine.syntax_err 'Destination path for parsed objects is required'
|
|
145
|
-
return
|
|
146
|
+
return @engine.heap.it.set_to( false )
|
|
146
147
|
end
|
|
147
148
|
parent = pn.resolve
|
|
148
149
|
|
|
149
150
|
json = nil
|
|
150
|
-
return unless attempt( "parse the JSON in #{self.pn}", JSON::ParserError
|
|
151
|
+
return false unless attempt( "parse the JSON in #{self.pn}", JSON::ParserError ) do
|
|
151
152
|
json = JSON.parse( self.value )
|
|
152
153
|
end
|
|
153
154
|
self.handle_json_to_obj( json, parent )
|
|
155
|
+
return @engine.heap.it.set_to( true )
|
|
154
156
|
end
|
|
155
157
|
|
|
156
158
|
# ---------------------------------------------------------------------
|
|
@@ -240,7 +242,7 @@ module Gloo
|
|
|
240
242
|
:messages => [
|
|
241
243
|
'get ({path}) — Get a value from the JSON data. Example: tell myjson to get (\'title\'). The parameter is the path in JSON to the value we want. The value is put into it.',
|
|
242
244
|
'set ({obj.path}) — Convert an object to an approximate JSON value. Example: tell myjson to set (my.object). The parameter is the path to the object used as the source. Note this is an approximate conversion — a gloo object can have both a simple value and be a container for child objects, which JSON can\'t represent the same way.',
|
|
243
|
-
'parse ({dst.path}) — Parse the JSON data and put values in the object specified by the parameter. Example: tell myjson to parse (path.to.dst).',
|
|
245
|
+
'parse ({dst.path}) — Parse the JSON data and put values in the object specified by the parameter; it will be true. If the destination is missing or the JSON is bad, that is an error and it will be false. Example: tell myjson to parse (path.to.dst).',
|
|
244
246
|
'pretty — Make the JSON format pretty. The value of the json is changed. It will have the pretty JSON.'
|
|
245
247
|
],
|
|
246
248
|
:examples => <<~EXAMPLES.strip
|
|
@@ -48,6 +48,7 @@ module Gloo
|
|
|
48
48
|
@mech = @engine.platform.get_file_mech( @engine )
|
|
49
49
|
@pn = pn
|
|
50
50
|
@obj = nil
|
|
51
|
+
@found = false
|
|
51
52
|
@source_doc = Gloo::Persist::Source::SourceDoc.new
|
|
52
53
|
@trivia = Gloo::Persist::TriviaBuffer.new
|
|
53
54
|
@body = Gloo::Persist::ScriptBodyCollector.new
|
|
@@ -66,6 +67,14 @@ module Gloo
|
|
|
66
67
|
return @ledger.roots
|
|
67
68
|
end
|
|
68
69
|
|
|
70
|
+
#
|
|
71
|
+
# Was the file there to read? False before load, or when the
|
|
72
|
+
# file couldn't be found (already reported as an error).
|
|
73
|
+
#
|
|
74
|
+
def found?
|
|
75
|
+
return @found
|
|
76
|
+
end
|
|
77
|
+
|
|
69
78
|
#
|
|
70
79
|
# Load the objects from the file.
|
|
71
80
|
#
|
|
@@ -74,6 +83,7 @@ module Gloo
|
|
|
74
83
|
@engine.err Gloo::Core::NotFound.file( @pn )
|
|
75
84
|
return
|
|
76
85
|
end
|
|
86
|
+
@found = true
|
|
77
87
|
|
|
78
88
|
@engine.log.debug "Loading file '#{@pn}'"
|
|
79
89
|
@indent_stack = IndentStack.new( @engine.heap.root, @source_doc )
|
|
@@ -8,6 +8,8 @@ module Gloo
|
|
|
8
8
|
module Persist
|
|
9
9
|
class FileStorage
|
|
10
10
|
|
|
11
|
+
NO_OBJECTS_WARNING = 'No objects in file; nothing loaded from '.freeze
|
|
12
|
+
|
|
11
13
|
attr_reader :obj, :pn, :roots, :source_doc
|
|
12
14
|
|
|
13
15
|
#
|
|
@@ -50,6 +52,10 @@ module Gloo
|
|
|
50
52
|
|
|
51
53
|
#
|
|
52
54
|
# Load the object from the file.
|
|
55
|
+
# A file that has no objects (empty, comments only, or only
|
|
56
|
+
# 'load lib' lines) isn't an error, so it's reported as a
|
|
57
|
+
# warning. A file that couldn't be found was already reported
|
|
58
|
+
# by the loader.
|
|
53
59
|
#
|
|
54
60
|
def load
|
|
55
61
|
fl = FileLoader.new( @engine, @pn )
|
|
@@ -59,8 +65,8 @@ module Gloo
|
|
|
59
65
|
@source_doc = fl.source_doc
|
|
60
66
|
if @obj
|
|
61
67
|
@engine.log.debug "Loaded object: #{@obj.name}"
|
|
62
|
-
|
|
63
|
-
@engine.
|
|
68
|
+
elsif fl.found?
|
|
69
|
+
@engine.warn "#{NO_OBJECTS_WARNING}#{@pn}"
|
|
64
70
|
end
|
|
65
71
|
end
|
|
66
72
|
|
|
@@ -191,6 +191,25 @@ module Gloo
|
|
|
191
191
|
return @maps.select { |fs| fs.roots.include?( root ) }
|
|
192
192
|
end
|
|
193
193
|
|
|
194
|
+
#
|
|
195
|
+
# The path of the file an object was loaded from, or nil if it
|
|
196
|
+
# didn't come from one. Walks up from the object to the first one
|
|
197
|
+
# a loaded file declares, so an object created at runtime under a
|
|
198
|
+
# loaded one gets that file. When several files declare it (a
|
|
199
|
+
# namespace container), the first one loaded wins.
|
|
200
|
+
#
|
|
201
|
+
def source_file_of( obj )
|
|
202
|
+
declared = @maps.map { |fs| [ fs, declared_by( fs ) ] }
|
|
203
|
+
o = obj
|
|
204
|
+
while o && !o.root?
|
|
205
|
+
match = declared.find { |_, owned| owned.key?( o ) }
|
|
206
|
+
return match.first.pn if match
|
|
207
|
+
|
|
208
|
+
o = o.parent
|
|
209
|
+
end
|
|
210
|
+
return nil
|
|
211
|
+
end
|
|
212
|
+
|
|
194
213
|
|
|
195
214
|
#
|
|
196
215
|
# Get the full path and name of the file.
|
|
@@ -265,6 +284,15 @@ module Gloo
|
|
|
265
284
|
end
|
|
266
285
|
end
|
|
267
286
|
|
|
287
|
+
#
|
|
288
|
+
# Every heap object fs's SourceDoc declares.
|
|
289
|
+
#
|
|
290
|
+
def declared_by( fs )
|
|
291
|
+
owned = {}.compare_by_identity
|
|
292
|
+
collect_owned( fs.source_doc&.children, owned )
|
|
293
|
+
return owned
|
|
294
|
+
end
|
|
295
|
+
|
|
268
296
|
#
|
|
269
297
|
# Every heap object declared by some loaded file's SourceDoc
|
|
270
298
|
# other than fs.
|
data/lib/gloo/verbs/context.rb
CHANGED
|
@@ -16,6 +16,8 @@ module Gloo
|
|
|
16
16
|
# Run the verb.
|
|
17
17
|
#
|
|
18
18
|
def run
|
|
19
|
+
return unless take_evaluated_arg
|
|
20
|
+
|
|
19
21
|
set_context if @tokens.token_count > 1
|
|
20
22
|
show_context
|
|
21
23
|
end
|
|
@@ -74,9 +76,12 @@ module Gloo
|
|
|
74
76
|
"to that path. Use 'context root' to set the context back to " \
|
|
75
77
|
'the root level. When context has been set, the pathname can ' \
|
|
76
78
|
'start from the context container by use of the @. prefix.',
|
|
77
|
-
:syntax => [ 'context {path.to.new.context}' ],
|
|
79
|
+
:syntax => [ 'context {path.to.new.context}', 'context ( {expression} )' ],
|
|
78
80
|
:parameters => [
|
|
79
|
-
'{path.to.new.context} — Optional. The path to the new context when setting the context.'
|
|
81
|
+
'{path.to.new.context} — Optional. The path to the new context when setting the context.',
|
|
82
|
+
'( {expression} ) — Instead of a literal path, an expression ' \
|
|
83
|
+
'in parentheses: it is worked out and its value is used as ' \
|
|
84
|
+
'the path to the new context.'
|
|
80
85
|
],
|
|
81
86
|
:result => 'Context is optionally set. It will be set to the ' \
|
|
82
87
|
'new context path when we are changing context. Context is ' \
|
|
@@ -100,6 +105,9 @@ module Gloo
|
|
|
100
105
|
@ context.sub
|
|
101
106
|
show @.msg
|
|
102
107
|
@ root
|
|
108
|
+
|
|
109
|
+
# The path can be worked out:
|
|
110
|
+
> context ( 'context.' 'sub' )
|
|
103
111
|
EXAMPLES
|
|
104
112
|
}
|
|
105
113
|
end
|
data/lib/gloo/verbs/exists.rb
CHANGED
|
@@ -23,6 +23,8 @@ module Gloo
|
|
|
23
23
|
# Run the verb.
|
|
24
24
|
#
|
|
25
25
|
def run
|
|
26
|
+
return @engine.heap.it.set_to( false ) unless take_evaluated_arg
|
|
27
|
+
|
|
26
28
|
if @tokens.token_count == 3
|
|
27
29
|
type = @tokens.second.strip.downcase
|
|
28
30
|
keyword = @tokens.last
|
|
@@ -105,7 +107,10 @@ module Gloo
|
|
|
105
107
|
:description => 'Check to see if a verb or object type has ' \
|
|
106
108
|
'been defined (or loaded), or if an object instance exists ' \
|
|
107
109
|
'in the heap. With no kind, any of the three counts.',
|
|
108
|
-
:syntax => [
|
|
110
|
+
:syntax => [
|
|
111
|
+
'exists? |any,object,verb,instance| {keyword}',
|
|
112
|
+
'exists? |any,object,verb,instance| ( {expression} )'
|
|
113
|
+
],
|
|
109
114
|
:parameters => [
|
|
110
115
|
"Kind of Keyword — Optional; any is the default. " \
|
|
111
116
|
"any checks for a verb, an object type, or an object instance; " \
|
|
@@ -115,7 +120,10 @@ module Gloo
|
|
|
115
120
|
"instance exists at the pathname represented by keyword. " \
|
|
116
121
|
"`exists? any markdown` is identical to `exists? markdown`.",
|
|
117
122
|
'{keyword} — A verb or object type keyword or keyword shortcut, ' \
|
|
118
|
-
'or the pathname to an object instance. Must be specified.'
|
|
123
|
+
'or the pathname to an object instance. Must be specified.',
|
|
124
|
+
'( {expression} ) — Instead of a literal keyword, an expression ' \
|
|
125
|
+
'in parentheses: it is worked out and its value is used as ' \
|
|
126
|
+
'the keyword or pathname.'
|
|
119
127
|
],
|
|
120
128
|
:result => 'If the verb, object type or instance exists, then ' \
|
|
121
129
|
'true, otherwise false. The result will be in it.',
|
|
@@ -142,6 +150,10 @@ module Gloo
|
|
|
142
150
|
|
|
143
151
|
\t\texists? instance exists.on_load
|
|
144
152
|
\t\tshow it # true
|
|
153
|
+
|
|
154
|
+
\t\t# The keyword can be worked out
|
|
155
|
+
\t\texists? object ( 'mark' 'down' )
|
|
156
|
+
\t\tshow it # true
|
|
145
157
|
EXAMPLES
|
|
146
158
|
}
|
|
147
159
|
end
|
data/lib/gloo/verbs/load.rb
CHANGED
|
@@ -23,6 +23,8 @@ module Gloo
|
|
|
23
23
|
# Run the verb.
|
|
24
24
|
#
|
|
25
25
|
def run
|
|
26
|
+
return unless take_evaluated_arg
|
|
27
|
+
|
|
26
28
|
if @tokens.token_count == 2
|
|
27
29
|
opt = FILE_OPT
|
|
28
30
|
fn = @tokens.second
|
|
@@ -109,13 +111,18 @@ module Gloo
|
|
|
109
111
|
:syntax => [
|
|
110
112
|
'load {file_name}',
|
|
111
113
|
'load ext {my_ext}',
|
|
112
|
-
'load lib {lib_name}'
|
|
114
|
+
'load lib {lib_name}',
|
|
115
|
+
'load [ext|lib] ( {expression} )'
|
|
113
116
|
],
|
|
114
117
|
:parameters => [
|
|
115
118
|
'{file_name} — Name of the object file that is to be loaded.',
|
|
116
119
|
'reference type — By default, the type is file. File can be ' \
|
|
117
120
|
'specified, but does not need to be. Use ext to load a ' \
|
|
118
|
-
'User Extension. Use lib to load a Core Library.'
|
|
121
|
+
'User Extension. Use lib to load a Core Library.',
|
|
122
|
+
'( {expression} ) — Instead of a literal name, an expression ' \
|
|
123
|
+
'in parentheses: it is worked out and its value is used as ' \
|
|
124
|
+
'the file, extension or library name. Wildcards and the ' \
|
|
125
|
+
'.gloo extension work as they do for a literal name.'
|
|
119
126
|
],
|
|
120
127
|
:result => 'Objects are loaded into the heap. on_load scripts ' \
|
|
121
128
|
'are run within the loaded objects.',
|
|
@@ -132,6 +139,9 @@ module Gloo
|
|
|
132
139
|
'they merge in the heap. If two loaded files declare the ' \
|
|
133
140
|
'same object with different values, the first one loaded ' \
|
|
134
141
|
"wins and a warning is logged.\n\n" \
|
|
142
|
+
'A file with no objects in it (empty, comments only, or ' \
|
|
143
|
+
"only 'load lib' lines) loads nothing and logs a warning " \
|
|
144
|
+
"naming the file; it isn't an error.\n\n" \
|
|
135
145
|
'A contiguous run of whole-line comments declared ' \
|
|
136
146
|
'immediately above an object (same indent, no blank line ' \
|
|
137
147
|
"in between) becomes that object's doc, captured as the " \
|
|
@@ -145,6 +155,10 @@ module Gloo
|
|
|
145
155
|
> load my/app/*
|
|
146
156
|
> load ~/.my_app/settings.gloo
|
|
147
157
|
|
|
158
|
+
# Load a file whose name is worked out
|
|
159
|
+
> load ( $.gloo_tmp '/scratch.gloo' )
|
|
160
|
+
> load ( my.file_obj )
|
|
161
|
+
|
|
148
162
|
# Load a User Extension
|
|
149
163
|
> load ext my_ext
|
|
150
164
|
|
data/lib/gloo/verbs/save.rb
CHANGED
|
@@ -48,9 +48,11 @@ module Gloo
|
|
|
48
48
|
# Run 'save {obj} to {path}'.
|
|
49
49
|
#
|
|
50
50
|
def run_save_to
|
|
51
|
+
return unless take_evaluated_arg
|
|
52
|
+
|
|
51
53
|
name = @tokens.before_token( TO )[ 1 ]
|
|
52
54
|
path = @tokens.after_token( TO )
|
|
53
|
-
|
|
55
|
+
if path.blank?
|
|
54
56
|
@engine.syntax_err MISSING_PATH_ERR
|
|
55
57
|
return
|
|
56
58
|
end
|
|
@@ -84,18 +86,26 @@ module Gloo
|
|
|
84
86
|
:syntax => [
|
|
85
87
|
'save',
|
|
86
88
|
'save {path.to.object}',
|
|
87
|
-
'save {path.to.object} to {path}'
|
|
89
|
+
'save {path.to.object} to {path}',
|
|
90
|
+
'save {path.to.object} to ( {expression} )'
|
|
88
91
|
],
|
|
89
92
|
:parameters => [
|
|
90
93
|
'{path.to.object} — Name of the object to save.',
|
|
91
94
|
'{path} — Where to save it, relative to the project root ' \
|
|
92
95
|
'(.gloo appended if omitted). Registers the mapping, so ' \
|
|
93
|
-
'a later bare save includes it.'
|
|
96
|
+
'a later bare save includes it.',
|
|
97
|
+
'( {expression} ) — Instead of a literal path, an expression ' \
|
|
98
|
+
'in parentheses: it is worked out and its value is used as ' \
|
|
99
|
+
'the path.'
|
|
94
100
|
],
|
|
95
101
|
:result => 'The file(s) are updated with the latest object ' \
|
|
96
102
|
'state. This is a rewrite, not a regeneration: comments, ' \
|
|
97
103
|
'blank lines, and the original formatting are preserved, ' \
|
|
98
104
|
'and only values that actually changed are re-written. ' \
|
|
105
|
+
'A declaration you never touched comes back byte-for-byte. ' \
|
|
106
|
+
'An object counts as changed only when its value is set or ' \
|
|
107
|
+
'it is created, moved, or deleted (its doc comment goes with ' \
|
|
108
|
+
'it); changes to its children do not change it. ' \
|
|
99
109
|
'With a path, the object also stops being declared in its ' \
|
|
100
110
|
'old file (if it had one) -- both files are rewritten ' \
|
|
101
111
|
'together.',
|
|
@@ -125,6 +135,7 @@ module Gloo
|
|
|
125
135
|
> save my_obj
|
|
126
136
|
> save my_obj to sub/my_obj
|
|
127
137
|
> save app.core.settings to config/settings
|
|
138
|
+
> save my_obj to ( $.gloo_tmp '/my_obj' )
|
|
128
139
|
EXAMPLES
|
|
129
140
|
}
|
|
130
141
|
end
|
|
@@ -38,23 +38,37 @@ tests.ctrl.each [can] :
|
|
|
38
38
|
|
|
39
39
|
#
|
|
40
40
|
# Each file / each dir over a nested folder tree.
|
|
41
|
-
# The tree is built
|
|
41
|
+
# The tree is built in $.gloo_tmp and removed at the end. It's built
|
|
42
|
+
# and removed with shell commands, so these tests need a Unix shell.
|
|
42
43
|
#
|
|
43
44
|
|
|
44
45
|
tests.ctrl.each_folder [can] :
|
|
45
46
|
|
|
46
47
|
count [int] : 0
|
|
48
|
+
walk_name [string] : /gloo_each_walk
|
|
49
|
+
walk [string] :
|
|
47
50
|
|
|
48
51
|
make_tree [system] :
|
|
49
|
-
command [string] :
|
|
52
|
+
command [string] :
|
|
50
53
|
get_output [bool] : false
|
|
51
54
|
remove_tree [system] :
|
|
52
|
-
command [string] :
|
|
55
|
+
command [string] :
|
|
53
56
|
get_output [bool] : false
|
|
54
57
|
|
|
58
|
+
# The tree lives in the gloo tmp folder ($.gloo_tmp); paths are
|
|
59
|
+
# quoted in the commands in case that folder has spaces.
|
|
60
|
+
on_load [script] :
|
|
61
|
+
put $.gloo_tmp ^.walk_name into ^.walk
|
|
62
|
+
put "rm -rf '" ^.walk "' && mkdir -p '" ^.walk "/a/deep' '" ^.walk "/.obs' && cd '" ^.walk "' && touch top.md notes.txt a/x.md a/deep/Y.MD .obs/o.md" into ^.make_tree.command
|
|
63
|
+
put "rm -rf '" ^.walk "'" into ^.remove_tree.command
|
|
64
|
+
put ^.walk into ^.files.in
|
|
65
|
+
put ^.walk '/**/' into ^.wild.in
|
|
66
|
+
put ^.walk into ^.top.in
|
|
67
|
+
put ^.walk into ^.dirs.in
|
|
68
|
+
|
|
55
69
|
files [each] :
|
|
56
70
|
file [file] :
|
|
57
|
-
in [file] :
|
|
71
|
+
in [file] :
|
|
58
72
|
ext [string] : md
|
|
59
73
|
recursive [bool] : true
|
|
60
74
|
do [script] :
|
|
@@ -63,7 +77,7 @@ tests.ctrl.each_folder [can] :
|
|
|
63
77
|
|
|
64
78
|
wild [each] :
|
|
65
79
|
file [file] :
|
|
66
|
-
in [file] :
|
|
80
|
+
in [file] :
|
|
67
81
|
ext [string] : md
|
|
68
82
|
do [script] :
|
|
69
83
|
eval ^^.count + 1
|
|
@@ -71,14 +85,14 @@ tests.ctrl.each_folder [can] :
|
|
|
71
85
|
|
|
72
86
|
top [each] :
|
|
73
87
|
file [file] :
|
|
74
|
-
in [file] :
|
|
88
|
+
in [file] :
|
|
75
89
|
do [script] :
|
|
76
90
|
eval ^^.count + 1
|
|
77
91
|
put it into ^^.count
|
|
78
92
|
|
|
79
93
|
dirs [each] :
|
|
80
94
|
dir [file] :
|
|
81
|
-
in [file] :
|
|
95
|
+
in [file] :
|
|
82
96
|
recursive [bool] : true
|
|
83
97
|
do [script] :
|
|
84
98
|
eval ^^.count + 1
|
|
@@ -7,7 +7,12 @@ tests.lang.declaration [can] :
|
|
|
7
7
|
# A fixture deliberately missing the ']' after its type, loaded inside
|
|
8
8
|
# its test so the syntax error it reports belongs to that test.
|
|
9
9
|
# Loading reads the type as 'int' anyway.
|
|
10
|
-
unclosed_file [file] :
|
|
10
|
+
unclosed_file [file] :
|
|
11
|
+
|
|
12
|
+
# Scratch files live in the gloo tmp folder ($.gloo_tmp).
|
|
13
|
+
on_load [script] :
|
|
14
|
+
put $.gloo_tmp '/gloo_declaration_test.gloo' into ^.unclosed_file
|
|
15
|
+
|
|
11
16
|
unclosed_src [text] : BEGIN
|
|
12
17
|
declaration_fixture [container] :
|
|
13
18
|
unclosed [int : 7
|
|
@@ -20,7 +25,7 @@ END
|
|
|
20
25
|
expect_errors [bool] : true
|
|
21
26
|
on_test [script] :
|
|
22
27
|
tell ^^.unclosed_file to write ( ^^.unclosed_src )
|
|
23
|
-
load
|
|
28
|
+
load ( ^^.unclosed_file )
|
|
24
29
|
eval declaration_fixture.unclosed = 7
|
|
25
30
|
assert "expected the value 7"
|
|
26
31
|
|
|
@@ -79,6 +79,18 @@ tests.lang.gloo_sys [can] :
|
|
|
79
79
|
eval it = ''
|
|
80
80
|
refute "expected gloo_log to not be empty"
|
|
81
81
|
|
|
82
|
+
get_gloo_tmp [test] :
|
|
83
|
+
description [string] : $.gloo_tmp is a folder that exists.
|
|
84
|
+
f [file] :
|
|
85
|
+
on_test [script] :
|
|
86
|
+
eval $.gloo_tmp
|
|
87
|
+
eval it = ''
|
|
88
|
+
refute "expected gloo_tmp to not be empty"
|
|
89
|
+
|
|
90
|
+
put $.gloo_tmp into ^.f
|
|
91
|
+
check ^.f for is_dir?
|
|
92
|
+
assert "expected gloo_tmp to be an existing folder"
|
|
93
|
+
|
|
82
94
|
get_screen_dims [test] :
|
|
83
95
|
description [string] : Get the screen dimensions.
|
|
84
96
|
i [int] :
|
|
@@ -16,7 +16,12 @@ tests.lang.naming [can] :
|
|
|
16
16
|
|
|
17
17
|
# A fixture with a deliberately nameless object, loaded inside its
|
|
18
18
|
# test so the syntax error it reports belongs to that test.
|
|
19
|
-
nameless_file [file] :
|
|
19
|
+
nameless_file [file] :
|
|
20
|
+
|
|
21
|
+
# Scratch files live in the gloo tmp folder ($.gloo_tmp).
|
|
22
|
+
on_load [script] :
|
|
23
|
+
put $.gloo_tmp '/gloo_naming_test.gloo' into ^.nameless_file
|
|
24
|
+
|
|
20
25
|
nameless_src [text] : BEGIN
|
|
21
26
|
naming_fixture [container] :
|
|
22
27
|
[string] : no name given
|
|
@@ -54,7 +59,7 @@ END
|
|
|
54
59
|
expect_errors [bool] : true
|
|
55
60
|
on_test [script] :
|
|
56
61
|
tell ^^.nameless_file to write ( ^^.nameless_src )
|
|
57
|
-
load
|
|
62
|
+
load ( ^^.nameless_file )
|
|
58
63
|
eval naming_fixture.unnamed_1 = 'no name given'
|
|
59
64
|
assert "expected the nameless object to be reached as unnamed_1"
|
|
60
65
|
|