gloo 7.1.2 → 7.2

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +23 -0
  3. data/docs/application.md +47 -2
  4. data/docs/development.md +10 -0
  5. data/docs/iterators.md +22 -5
  6. data/docs/language_objects.md +20 -15
  7. data/docs/language_scripting.md +6 -2
  8. data/docs/plugins.md +1 -1
  9. data/docs/testing.md +4 -7
  10. data/docs/verbs.md +23 -3
  11. data/lib/VERSION +1 -1
  12. data/lib/VERSION_NOTES +13 -0
  13. data/lib/dependencies.rb +4 -1
  14. data/lib/gloo/app/engine.rb +18 -6
  15. data/lib/gloo/core/obj.rb +0 -23
  16. data/lib/gloo/exec/script.rb +12 -2
  17. data/lib/gloo/objs/basic/script.rb +5 -28
  18. data/lib/gloo/objs/ctrl/each.rb +4 -3
  19. data/lib/gloo/objs/ctrl/repeat.rb +4 -3
  20. data/lib/gloo/persist/declaration_ledger.rb +9 -9
  21. data/lib/gloo/persist/declaration_writer.rb +208 -0
  22. data/lib/gloo/persist/disc_mech.rb +70 -39
  23. data/lib/gloo/persist/file_loader.rb +125 -66
  24. data/lib/gloo/persist/file_saver.rb +130 -198
  25. data/lib/gloo/persist/file_storage.rb +15 -4
  26. data/lib/gloo/persist/file_style.rb +89 -0
  27. data/lib/gloo/persist/line_splitter.rb +16 -1
  28. data/lib/gloo/persist/persist_man.rb +112 -30
  29. data/lib/gloo/persist/save_index.rb +211 -0
  30. data/lib/gloo/persist/script_body_collector.rb +16 -21
  31. data/lib/gloo/persist/source/blank_node.rb +10 -3
  32. data/lib/gloo/persist/source/comment_node.rb +10 -2
  33. data/lib/gloo/persist/source/directive_node.rb +10 -2
  34. data/lib/gloo/persist/source/obj_node.rb +174 -9
  35. data/lib/gloo/persist/source/source_doc.rb +54 -0
  36. data/lib/gloo/persist/subtree_extractor.rb +9 -37
  37. data/lib/gloo/persist/trivia_buffer.rb +7 -7
  38. data/lib/gloo/verbs/break.rb +99 -10
  39. data/lib/gloo/verbs/load.rb +27 -21
  40. data/lib/gloo/verbs/move.rb +42 -3
  41. data/lib/gloo/verbs/save.rb +26 -4
  42. data/test.gloo/app/exit_status.test.gloo +34 -0
  43. data/test.gloo/app/fixtures/exit_after_error.gloo +9 -0
  44. data/test.gloo/lang/declaration.test.gloo +36 -0
  45. data/test.gloo/lang/fixtures/mixed_indent.gloo +5 -0
  46. data/test.gloo/lang/naming.test.gloo +44 -6
  47. data/test.gloo/verbs/break.test.gloo +104 -11
  48. data/test.gloo/verbs/fixtures/load_beside.gloo +6 -0
  49. data/test.gloo/verbs/fixtures/lossless_text.gloo +7 -0
  50. data/test.gloo/verbs/fixtures/lossless_text_saved.gloo +9 -0
  51. data/test.gloo/verbs/load.test.gloo +18 -0
  52. data/test.gloo/verbs/move.test.gloo +39 -0
  53. data/test.gloo/verbs/save.test.gloo +239 -0
  54. metadata +10 -1
@@ -13,9 +13,15 @@ module Gloo
13
13
  TO = 'to'.freeze
14
14
  MISSING_SRC_ERR = 'Object to move was not specified!'.freeze
15
15
  MISSING_DST_ERR = "'Move' must include 'to' parent object!".freeze
16
+ NAME_TAKEN_ERR = "Could not move '%s': '%s' already has a child named '%s'.".freeze
17
+ INSIDE_ITSELF_ERR = "Could not move '%s': it can't go inside itself.".freeze
16
18
 
17
19
  #
18
20
  # Run the verb.
21
+ # A name is unique in its container, so an object can't move into
22
+ # one that already has a child with its name; nor can it move
23
+ # inside itself. Moving it to the parent it's already in does
24
+ # nothing.
19
25
  #
20
26
  def run
21
27
  dst = lookup_dst
@@ -23,6 +29,9 @@ module Gloo
23
29
 
24
30
  o = lookup_obj
25
31
  return unless o
32
+ return if dst.equal?( o.parent )
33
+ return refuse( format( INSIDE_ITSELF_ERR, o.pn ) ) if inside?( dst, o )
34
+ return refuse( format( NAME_TAKEN_ERR, o.pn, dst.pn, o.name ) ) if dst.contains_child?( o.name )
26
35
 
27
36
  o.parent.remove_child o
28
37
  dst.add_child o
@@ -48,6 +57,28 @@ module Gloo
48
57
 
49
58
  private
50
59
 
60
+ #
61
+ # Report why the object can't be moved; nothing moves, and it is
62
+ # false.
63
+ #
64
+ def refuse( msg )
65
+ @engine.err msg
66
+ @engine.heap.it.set_to false
67
+ end
68
+
69
+ #
70
+ # Is dst the object itself, or somewhere under it?
71
+ #
72
+ def inside?( dst, obj )
73
+ o = dst
74
+ while o
75
+ return true if o.equal?( obj )
76
+
77
+ o = o.parent
78
+ end
79
+ return false
80
+ end
81
+
51
82
  #
52
83
  # Lookup the object that we're moving.
53
84
  #
@@ -97,18 +128,26 @@ module Gloo
97
128
  {
98
129
  :name => KEYWORD,
99
130
  :shortcut => KEYWORD_SHORT,
100
- :description => 'Move an object to a new parent.',
131
+ :description => 'Move an object to a new parent. A name is unique ' \
132
+ 'in its container, so the new parent must not already have a ' \
133
+ 'child with the same name.',
101
134
  :syntax => [ 'move {path.to.object} to {new.parent}' ],
102
135
  :parameters => [
103
136
  '{path.to.object} — The object that we want to move.',
104
137
  '{new.parent} — The new location for the object.'
105
138
  ],
106
- :result => 'The object will now be in the new location.',
139
+ :result => 'The object will now be in the new location. Moving it ' \
140
+ 'to the parent it is already in does nothing. A pure action: it ' \
141
+ 'is left alone, unless the move is refused.',
107
142
  :errors => [
108
143
  "#{MISSING_SRC_ERR} The {path.to.object} is not specified.",
109
144
  "Object '{path.to.object}' was not found. — The {path.to.object} cannot be resolved.",
110
145
  "#{MISSING_DST_ERR} The {new.parent} is not specified.",
111
- "Object '{new.parent}' was not found. — The {new.parent} cannot be resolved."
146
+ "Object '{new.parent}' was not found. — The {new.parent} cannot be resolved.",
147
+ "#{format( NAME_TAKEN_ERR, '{path.to.object}', '{new.parent}', '{name}' )} — " \
148
+ 'The new parent already has a child with that name. Nothing moves, and it is false.',
149
+ "#{format( INSIDE_ITSELF_ERR, '{path.to.object}' )} — The new parent is the " \
150
+ 'object itself or something under it. Nothing moves, and it is false.'
112
151
  ],
113
152
  :examples => <<~EXAMPLES.strip
114
153
  can [can] :
@@ -91,8 +91,13 @@ module Gloo
91
91
  ],
92
92
  :parameters => [
93
93
  '{path.to.object} — Name of the object to save.',
94
- '{path} — Where to save it, relative to the project root ' \
95
- '(.gloo appended if omitted). Registers the mapping, so ' \
94
+ '{path} — Where to save it: a full path, one starting ' \
95
+ 'with ~, or a relative name (.gloo appended if omitted). ' \
96
+ 'A relative name goes in the first place a relative load ' \
97
+ 'looks: the app folder in App mode; otherwise next to the ' \
98
+ "saving script's file (the projects folder for a script " \
99
+ 'that did not come from a file), or the working folder for ' \
100
+ 'a command typed at the prompt. Registers the mapping, so ' \
96
101
  'a later bare save includes it.',
97
102
  '( {expression} ) — Instead of a literal path, an expression ' \
98
103
  'in parentheses: it is worked out and its value is used as ' \
@@ -105,7 +110,14 @@ module Gloo
105
110
  'A declaration you never touched comes back byte-for-byte. ' \
106
111
  'An object counts as changed only when its value is set or ' \
107
112
  'it is created, moved, or deleted (its doc comment goes with ' \
108
- 'it); changes to its children do not change it. ' \
113
+ 'it); changes to its children do not change it. A changed ' \
114
+ 'declaration keeps its name, type and spacing; only its ' \
115
+ 'value is written again. A new object goes after the last ' \
116
+ "child of its parent's first declaration, in the file's " \
117
+ 'indentation and type spellings. An object declared on more ' \
118
+ 'than one line has its value on the first: a change rewrites ' \
119
+ 'only that line, and deleting or moving it takes all its ' \
120
+ 'lines. ' \
109
121
  'With a path, the object also stops being declared in its ' \
110
122
  'old file (if it had one) -- both files are rewritten ' \
111
123
  'together.',
@@ -122,7 +134,17 @@ module Gloo
122
134
  'container from nested-container shorthand; extract a ' \
123
135
  'child that does, or a shallower ancestor that does.'
124
136
  ],
125
- :notes => 'An object can also be told to save itself: ' \
137
+ :notes => 'A value that cannot be written exactly (a block ' \
138
+ 'value with no final line break, a line that is just END, ' \
139
+ 'bytes that are not UTF-8) is written as closely as possible, ' \
140
+ "with a warning: Could not save '{path}' exactly: {why}. A " \
141
+ 'changed script body loses the comments and blank lines ' \
142
+ 'inside it. A new top-level object has no file yet: a bare ' \
143
+ 'save leaves it out and warns (Not saved (no file yet): ' \
144
+ '{name}.); save {name} gives it a file of its own, named ' \
145
+ 'after it and placed the same way as a relative path. ' \
146
+ 'See doc verbs, Load & Save. ' \
147
+ 'An object can also be told to save itself: ' \
126
148
  '`tell my_obj to save`. When several files contribute to ' \
127
149
  'one container (the namespace pattern), each file save only ' \
128
150
  "rewrites that file's own declarations. Extraction (save " \
@@ -0,0 +1,34 @@
1
+ #
2
+ # Exit status of a script run.
3
+ # `gloo file.gloo` exits 1 if anything went wrong at any point in the
4
+ # run, not just on its last line. The engine running these tests was
5
+ # started in Test mode, so the check runs gloo again with [system],
6
+ # on a fixture beside this file.
7
+ #
8
+
9
+ tests.app.exit_status [can] :
10
+
11
+ # The interpreter and the fixture, both found from source_dir.
12
+ run_rb [string] : /../../lib/run.rb
13
+ fixture [string] : /fixtures/exit_after_error.gloo
14
+ quiet [string] : --quiet
15
+ # The fixture's error output isn't part of these tests' output.
16
+ discard [string] : > /dev/null 2>&1
17
+ where [string] :
18
+
19
+ run_gloo [system] :
20
+ command [string] :
21
+ get_output [bool] : false
22
+ result [bool] :
23
+
24
+ error_then_success_exits_1 [test] :
25
+ description [string] : A run with an error on an earlier line exits non-zero, though its last line works.
26
+ on_test [script] :
27
+ check ^^ for source_dir
28
+ put it into ^^.where
29
+ # Paths are quoted in case the folder has spaces.
30
+ put "ruby '" ^^.where ^^.run_rb "' " ^^.quiet " '" ^^.where ^^.fixture "' " ^^.discard into ^^.run_gloo.command
31
+ tell ^^.run_gloo to run
32
+ # result is false when the command exits non-zero.
33
+ eval ^^.run_gloo.result
34
+ refute "expected the run to exit non-zero"
@@ -0,0 +1,9 @@
1
+ #
2
+ # Run by exit_status.test.gloo as `gloo` would run it: an error, then
3
+ # a line that works. The run must still exit 1.
4
+ #
5
+ a [can] :
6
+ n [int] : 0
7
+ on_load [script] :
8
+ show no.such.object
9
+ put 1 into a.n
@@ -41,3 +41,39 @@ END
41
41
  on_test [script] :
42
42
  eval ^^.closed_then_colon = 8
43
43
  assert "expected the value 8"
44
+
45
+ # A fixture beside this file whose lines mix tabs and spaces, on one
46
+ # line and from line to line: a tab counts as two spaces.
47
+ mixed_indent_path [string] : /fixtures/mixed_indent.gloo
48
+ where [string] :
49
+
50
+ a_tab_and_spaces_on_one_line_add_up [test] :
51
+ description [string] : a tab plus two spaces is two levels deep, in either order.
52
+ on_test [script] :
53
+ check ^^ for source_dir
54
+ put it into ^^.where
55
+ load ( ^^.where ^^.mixed_indent_path )
56
+ tell mixed_indent_fixture.a to count
57
+ eval it = 2
58
+ assert "expected x and y under a"
59
+ eval mixed_indent_fixture.b = 3
60
+ assert "expected b beside a"
61
+ tell mixed_indent_fixture to unload
62
+
63
+ # Spaces just inside the brackets are allowed.
64
+ spaced_int [ int ] : 5
65
+ spaced_after [int ]: 6
66
+ spaced_text [text ] : BEGIN
67
+ one
68
+ END
69
+
70
+ spaces_inside_the_brackets_are_allowed [test] :
71
+ description [string] : a space just inside a type's brackets doesn't change the type.
72
+ on_test [script] :
73
+ eval ^^.spaced_int = 5
74
+ assert "expected [ int ] to be an integer 5"
75
+ eval ^^.spaced_after + 1 = 7
76
+ assert "expected [int ]: to be an integer 6"
77
+ check ^^.spaced_text for starts_with? ( 'one' )
78
+ assert "expected [text ] to read its block"
79
+
@@ -0,0 +1,5 @@
1
+ mixed_indent_fixture [can] :
2
+ a [can] :
3
+ x [int] : 1
4
+ y [int] : 2
5
+ b [int] : 3
@@ -4,9 +4,6 @@
4
4
 
5
5
  tests.lang.naming [can] :
6
6
 
7
- dup [string] : first
8
- dup [string] : second
9
-
10
7
  Alpha [string] : mixed case
11
8
 
12
9
  1 [string] : numeric name
@@ -18,9 +15,24 @@ tests.lang.naming [can] :
18
15
  # test so the syntax error it reports belongs to that test.
19
16
  nameless_file [file] :
20
17
 
18
+ # A fixture that declares one name twice, loaded inside its tests so
19
+ # the warning about the ignored value belongs to them.
20
+ dup_file [file] :
21
+
21
22
  # Scratch files live in the gloo tmp folder ($.gloo_tmp).
22
23
  on_load [script] :
23
24
  put $.gloo_tmp '/gloo_naming_test.gloo' into ^.nameless_file
25
+ put $.gloo_tmp '/gloo_naming_dup_test.gloo' into ^.dup_file
26
+
27
+ dup_src [text] : BEGIN
28
+ dup_fixture [container] :
29
+ dup [string] : first
30
+ dup [string] : second
31
+ box [container] :
32
+ x [string] : ex
33
+ box [container] :
34
+ y [string] : why
35
+ END
24
36
 
25
37
  nameless_src [text] : BEGIN
26
38
  naming_fixture [container] :
@@ -28,10 +40,36 @@ naming_fixture [container] :
28
40
  END
29
41
 
30
42
  first_of_a_duplicate_name_wins [test] :
31
- description [string] : when two siblings share a name, the path always reaches the first.
43
+ description [string] : a second declaration of a name is the same object; the first value wins.
44
+ expect_errors [bool] : true
45
+ on_test [script] :
46
+ tell ^^.dup_file to write ( ^^.dup_src )
47
+ load ( ^^.dup_file )
48
+ eval dup_fixture.dup = 'first'
49
+ assert "expected the first value to win"
50
+
51
+ tell dup_fixture to count
52
+ eval it = 2
53
+ assert "expected one 'dup' and one 'box', not four objects"
54
+
55
+ tell dup_fixture to unload
56
+ tell ^^.dup_file to delete
57
+
58
+ a_container_declared_twice_merges [test] :
59
+ description [string] : a container declared twice is one container with the children of both.
60
+ expect_errors [bool] : true
32
61
  on_test [script] :
33
- eval ^^.dup = 'first'
34
- assert "expected the first 'dup' to be the one reached"
62
+ tell ^^.dup_file to write ( ^^.dup_src )
63
+ load ( ^^.dup_file )
64
+ tell dup_fixture.box to count
65
+ eval it = 2
66
+ assert "expected x and y in the one box"
67
+
68
+ eval dup_fixture.box.y = 'why'
69
+ assert "expected the second declaration's child to be there"
70
+
71
+ tell dup_fixture to unload
72
+ tell ^^.dup_file to delete
35
73
 
36
74
  path_lookup_is_case_insensitive [test] :
37
75
  description [string] : a pathname segment matches a name regardless of case.
@@ -8,7 +8,7 @@ tests.verbs.break [can] :
8
8
 
9
9
  s [script] :
10
10
  if ^.b then break
11
-
11
+
12
12
  # Should not get here
13
13
  put false into ^.b
14
14
 
@@ -19,16 +19,27 @@ tests.verbs.break [can] :
19
19
  eval ^^.b
20
20
  assert "expected b to be true"
21
21
 
22
- # Loops whose do script breaks on its second pass.
22
+ # The last error, for the break loop errors.
23
+ on_error [script] :
24
+ error_data [can] :
25
+ message [string] :
26
+
27
+ not_in_a_loop [string] : Could not break loop: not in a loop.
28
+
29
+ # Loops whose do script breaks on one pass: skip_on for a plain
30
+ # break, end_on for break loop (0 never breaks).
23
31
  passes [int] : 0
24
32
  after [int] : 0
33
+ skip_on [int] : 0
34
+ end_on [int] : 0
25
35
 
26
36
  rep [repeat] :
27
37
  times [int] : 4
28
38
  index [int] :
29
39
  do [script] :
30
40
  put tests.verbs.break.passes + 1 into tests.verbs.break.passes
31
- if tests.verbs.break.passes = 2 then break
41
+ if tests.verbs.break.passes = tests.verbs.break.skip_on then break
42
+ if tests.verbs.break.passes = tests.verbs.break.end_on then break loop
32
43
  put tests.verbs.break.after + 1 into tests.verbs.break.after
33
44
 
34
45
  kids [can] :
@@ -41,14 +52,15 @@ tests.verbs.break [can] :
41
52
  in [alias] : tests.verbs.break.kids
42
53
  do [script] :
43
54
  put tests.verbs.break.passes + 1 into tests.verbs.break.passes
44
- if tests.verbs.break.passes = 2 then break
55
+ if tests.verbs.break.passes = tests.verbs.break.skip_on then break
56
+ if tests.verbs.break.passes = tests.verbs.break.end_on then break loop
45
57
  put tests.verbs.break.after + 1 into tests.verbs.break.after
46
58
 
47
59
  inner [repeat] :
48
60
  times [int] : 3
49
61
  index [int] :
50
62
  do [script] :
51
- if tests.verbs.break.inner.index = 1 then break
63
+ if tests.verbs.break.inner.index = 1 then break loop
52
64
  put tests.verbs.break.after + 1 into tests.verbs.break.after
53
65
 
54
66
  outer [repeat] :
@@ -57,30 +69,111 @@ tests.verbs.break [can] :
57
69
  do [script] :
58
70
  tell tests.verbs.break.inner to run
59
71
 
60
- break_ends_a_repeat [test] :
61
- description [string] : break in a repeat's do script ends the loop.
72
+ # The form the apps use to skip an item: unless ... do break.
73
+ skipped [string] :
74
+ skip_b [each] :
75
+ child [alias] :
76
+ in [alias] : tests.verbs.break.kids
77
+ do [script] :
78
+ check tests.verbs.break.skip_b.child for starts_with?( 'b' )
79
+ unless it = false do break
80
+ put tests.verbs.break.skipped + tests.verbs.break.skip_b.child into tests.verbs.break.skipped
81
+
82
+ # break loop is only valid directly in a loop's do script.
83
+ lone [script] :
84
+ break loop
85
+
86
+ loop_helper [script] :
87
+ break loop
88
+
89
+ via_loop [repeat] :
90
+ times [int] : 2
91
+ index [int] :
92
+ do [script] :
93
+ put tests.verbs.break.passes + 1 into tests.verbs.break.passes
94
+ run tests.verbs.break.loop_helper
95
+
96
+ break_skips_the_rest_of_a_repeat_pass [test] :
97
+ description [string] : break in a repeat's do script skips to the next pass.
62
98
  on_test [script] :
63
99
  put 0 into ^^.passes
64
100
  put 0 into ^^.after
101
+ put 0 into ^^.end_on
102
+ put 2 into ^^.skip_on
103
+ tell ^^.rep to run
104
+ eval ^^.passes = 4
105
+ assert "expected every pass to run"
106
+ eval ^^.after = 3
107
+ assert "expected only the pass that broke to stop early"
108
+
109
+ break_skips_the_rest_of_an_each_pass [test] :
110
+ description [string] : break in an each's do script skips to the next child.
111
+ on_test [script] :
112
+ put 0 into ^^.passes
113
+ put 0 into ^^.after
114
+ put 0 into ^^.end_on
115
+ put 2 into ^^.skip_on
116
+ tell ^^.each_kid to run
117
+ eval ^^.after = 2
118
+ assert "expected every child but the one that broke to get past it"
119
+
120
+ unless_do_break_skips_an_item [test] :
121
+ description [string] : unless ... do break in an each skips that item, as the apps use it.
122
+ on_test [script] :
123
+ put '' into ^^.skipped
124
+ tell ^^.skip_b to run
125
+ eval ^^.skipped = 'ac'
126
+ assert "expected b to be skipped and c still reached"
127
+
128
+ break_loop_ends_a_repeat [test] :
129
+ description [string] : break loop in a repeat's do script ends the loop.
130
+ on_test [script] :
131
+ put 0 into ^^.passes
132
+ put 0 into ^^.after
133
+ put 0 into ^^.skip_on
134
+ put 2 into ^^.end_on
65
135
  tell ^^.rep to run
66
136
  eval ^^.passes = 2
67
137
  assert "expected the loop to stop on the pass that broke"
68
138
  eval ^^.after = 1
69
139
  assert "expected only the first pass to get past the break"
70
140
 
71
- break_ends_an_each [test] :
72
- description [string] : break in an each's do script ends the loop.
141
+ break_loop_ends_an_each [test] :
142
+ description [string] : break loop in an each's do script ends the loop.
73
143
  on_test [script] :
74
144
  put 0 into ^^.passes
75
145
  put 0 into ^^.after
146
+ put 0 into ^^.skip_on
147
+ put 2 into ^^.end_on
76
148
  tell ^^.each_kid to run
77
149
  eval ^^.passes = 2
78
150
  assert "expected the loop to stop on the pass that broke"
79
151
 
80
- break_ends_only_the_inner_loop [test] :
81
- description [string] : break in an inner loop ends that loop, not the outer one.
152
+ break_loop_ends_only_the_inner_loop [test] :
153
+ description [string] : break loop in an inner loop ends that loop, not the outer one.
82
154
  on_test [script] :
83
155
  put 0 into ^^.after
84
156
  tell ^^.outer to run
85
157
  eval ^^.after = 2
86
158
  assert "expected each of the 2 inner loops to get past its break once"
159
+
160
+ break_loop_outside_a_loop_is_an_error [test] :
161
+ description [string] : break loop in a script that isn't a loop's do is an error.
162
+ expect_errors [bool] : true
163
+ on_test [script] :
164
+ put '' into ^^.error_data.message
165
+ run ^^.lone
166
+ eval ^^.error_data.message = ^^.not_in_a_loop
167
+ assert "expected the not-in-a-loop error"
168
+
169
+ break_loop_in_a_helper_is_an_error [test] :
170
+ description [string] : break loop in a script the do runs is an error, and the loop goes on.
171
+ expect_errors [bool] : true
172
+ on_test [script] :
173
+ put '' into ^^.error_data.message
174
+ put 0 into ^^.passes
175
+ tell ^^.via_loop to run
176
+ eval ^^.error_data.message = ^^.not_in_a_loop
177
+ assert "expected the not-in-a-loop error"
178
+ eval ^^.passes = 2
179
+ assert "expected the loop to go on"
@@ -0,0 +1,6 @@
1
+ #
2
+ # Loaded by load.test.gloo with a plain relative name, from the
3
+ # folder beside that test file. Not a test file itself.
4
+ #
5
+ load_beside_demo [can] :
6
+ s [string] : beside
@@ -0,0 +1,7 @@
1
+ lossless_text [can] :
2
+ log [text] : BEGIN
3
+ Started.
4
+ END
5
+ entry [text] : BEGIN
6
+ Saved.
7
+ END
@@ -0,0 +1,9 @@
1
+ lossless_text [can] :
2
+ log [text] : BEGIN
3
+ Started.
4
+ Saved.
5
+ Saved.
6
+ END
7
+ entry [text] : BEGIN
8
+ Saved.
9
+ END
@@ -172,3 +172,21 @@ END
172
172
  assert "expected the full path without .gloo to load"
173
173
  tell computed_demo to unload
174
174
  tell ^^.computed_f to delete
175
+
176
+ # A relative name looks next to the loading file first; these scripts
177
+ # are declared in this file, so fixtures/ is the folder beside it.
178
+ load_a_file_beside_this_one [test] :
179
+ description [string] : A relative load finds a file next to the file the script came from.
180
+ on_test [script] :
181
+ load fixtures/load_beside
182
+ eval load_beside_demo.s = 'beside'
183
+ assert "expected fixtures/load_beside.gloo to load from beside this file"
184
+ tell load_beside_demo to unload
185
+
186
+ load_a_prefix_beside_this_one [test] :
187
+ description [string] : A relative wildcard with a file name prefix finds the files next to the loading file.
188
+ on_test [script] :
189
+ load fixtures/load_beside*
190
+ eval load_beside_demo.s = 'beside'
191
+ assert "expected fixtures/load_beside*.gloo to load from beside this file"
192
+ tell load_beside_demo to unload
@@ -16,3 +16,42 @@ tests.verbs.move [can] :
16
16
  assert "expected a to be in t"
17
17
  exists? instance tests.verbs.move.c.a
18
18
  refute "expected a not to be in c"
19
+
20
+ # A name is unique in its container: a move onto a taken name, or
21
+ # inside itself, is refused.
22
+ taken [container] :
23
+ a [string] : moving
24
+ dst [container] :
25
+ a [string] : already here
26
+ outer [container] :
27
+ inner [container] :
28
+ msg [string] :
29
+
30
+ on_error [script] :
31
+ error_data [can] :
32
+ message [string] :
33
+
34
+ moving_onto_a_taken_name_is_refused [test] :
35
+ description [string] : A move into a container with a child of the same name is an error; nothing moves.
36
+ expect_errors [bool] : true
37
+ on_test [script] :
38
+ move ^^.taken.a to ^^.dst
39
+ eval it = false
40
+ assert "expected it to be false"
41
+ eval ^^.dst.a = 'already here'
42
+ assert "expected the child already there to be untouched"
43
+ exists? instance ^^.taken.a
44
+ assert "expected the object not to have moved"
45
+ put ^^.error_data.message into ^^.msg
46
+ check ^^.msg for starts_with? ( 'Could not move' )
47
+ assert "expected a could-not-move error"
48
+
49
+ moving_inside_itself_is_refused [test] :
50
+ description [string] : A move into the object's own child is an error; nothing moves.
51
+ expect_errors [bool] : true
52
+ on_test [script] :
53
+ move ^^.outer to ^^.outer.inner
54
+ eval it = false
55
+ assert "expected it to be false"
56
+ exists? instance ^^.outer.inner
57
+ assert "expected outer to still be in place"