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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -1
  3. data/docs/application.md +14 -2
  4. data/docs/development.md +1 -1
  5. data/docs/language_objects.md +37 -0
  6. data/docs/language_syntax.md +25 -0
  7. data/docs/objects.md +1 -1
  8. data/docs/plugins.md +9 -3
  9. data/docs/testing.md +206 -0
  10. data/docs/verbs.md +13 -0
  11. data/lib/VERSION +1 -1
  12. data/lib/VERSION_NOTES +15 -2
  13. data/lib/gloo/app/engine.rb +19 -9
  14. data/lib/gloo/app/settings.rb +20 -3
  15. data/lib/gloo/core/gloo_system.rb +7 -0
  16. data/lib/gloo/core/obj.rb +22 -1
  17. data/lib/gloo/core/tokens.rb +20 -0
  18. data/lib/gloo/core/verb.rb +20 -0
  19. data/lib/gloo/objs/basic/string_msgs.rb +6 -7
  20. data/lib/gloo/objs/basic/untyped.rb +2 -1
  21. data/lib/gloo/objs/system/file_handle.rb +5 -2
  22. data/lib/gloo/objs/web/json.rb +7 -5
  23. data/lib/gloo/persist/file_loader.rb +10 -0
  24. data/lib/gloo/persist/file_storage.rb +8 -2
  25. data/lib/gloo/persist/persist_man.rb +28 -0
  26. data/lib/gloo/verbs/context.rb +10 -2
  27. data/lib/gloo/verbs/exists.rb +14 -2
  28. data/lib/gloo/verbs/load.rb +16 -2
  29. data/lib/gloo/verbs/save.rb +14 -3
  30. data/test.gloo/ctrl/each.test.gloo +21 -7
  31. data/test.gloo/lang/declaration.test.gloo +7 -2
  32. data/test.gloo/lang/gloo_sys.test.gloo +12 -0
  33. data/test.gloo/lang/naming.test.gloo +7 -2
  34. data/test.gloo/objs/file.test.gloo +18 -2
  35. data/test.gloo/objs/fixtures/source_dir_fixture.gloo +6 -0
  36. data/test.gloo/objs/json.test.gloo +2 -0
  37. data/test.gloo/objs/obj.test.gloo +33 -0
  38. data/test.gloo/objs/string.test.gloo +2 -2
  39. data/test.gloo/verbs/context.test.gloo +12 -0
  40. data/test.gloo/verbs/exists.test.gloo +19 -0
  41. data/test.gloo/verbs/load.test.gloo +101 -2
  42. data/test.gloo/verbs/reload.test.gloo +9 -4
  43. data/test.gloo/verbs/save.test.gloo +41 -16
  44. 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
@@ -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
  # ---------------------------------------------------------------------
@@ -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 have the number of parts.',
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; the number of parts is put
243
- # into 'it'.
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( 0 ) unless value
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
- count = parts.count
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?, reload, unload) but can hold a ' \
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 into the object.
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.',
@@ -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, set_it: false ) do
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
- else
63
- @engine.err "Error loading file at #{@pn}"
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.
@@ -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
@@ -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 => [ 'exists? |any,object,verb,instance| {keyword}' ],
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
@@ -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
 
@@ -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
- unless path
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 under /tmp and removed at the end.
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] : rm -rf /tmp/gloo_each_walk && mkdir -p /tmp/gloo_each_walk/a/deep /tmp/gloo_each_walk/.obs && touch /tmp/gloo_each_walk/top.md /tmp/gloo_each_walk/notes.txt /tmp/gloo_each_walk/a/x.md /tmp/gloo_each_walk/a/deep/Y.MD /tmp/gloo_each_walk/.obs/o.md
52
+ command [string] :
50
53
  get_output [bool] : false
51
54
  remove_tree [system] :
52
- command [string] : rm -rf /tmp/gloo_each_walk
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] : /tmp/gloo_each_walk
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] : /tmp/gloo_each_walk/**/
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] : /tmp/gloo_each_walk
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] : /tmp/gloo_each_walk
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] : /tmp/gloo_declaration_test.gloo
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 /tmp/gloo_declaration_test.gloo
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] : /tmp/gloo_naming_test.gloo
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 /tmp/gloo_naming_test.gloo
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