gloo 7.1.3 → 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.
- checksums.yaml +4 -4
- data/Rakefile +23 -0
- data/docs/application.md +47 -2
- data/docs/development.md +10 -0
- data/docs/language_objects.md +20 -15
- data/docs/language_scripting.md +6 -2
- data/docs/testing.md +4 -7
- data/docs/verbs.md +23 -3
- data/lib/VERSION +1 -1
- data/lib/VERSION_NOTES +8 -0
- data/lib/dependencies.rb +4 -1
- data/lib/gloo/app/engine.rb +18 -6
- data/lib/gloo/core/obj.rb +0 -23
- data/lib/gloo/objs/basic/script.rb +0 -23
- data/lib/gloo/persist/declaration_ledger.rb +9 -9
- data/lib/gloo/persist/declaration_writer.rb +208 -0
- data/lib/gloo/persist/disc_mech.rb +70 -39
- data/lib/gloo/persist/file_loader.rb +125 -66
- data/lib/gloo/persist/file_saver.rb +130 -198
- data/lib/gloo/persist/file_storage.rb +15 -4
- data/lib/gloo/persist/file_style.rb +89 -0
- data/lib/gloo/persist/line_splitter.rb +16 -1
- data/lib/gloo/persist/persist_man.rb +112 -30
- data/lib/gloo/persist/save_index.rb +211 -0
- data/lib/gloo/persist/script_body_collector.rb +16 -21
- data/lib/gloo/persist/source/blank_node.rb +10 -3
- data/lib/gloo/persist/source/comment_node.rb +10 -2
- data/lib/gloo/persist/source/directive_node.rb +10 -2
- data/lib/gloo/persist/source/obj_node.rb +174 -9
- data/lib/gloo/persist/source/source_doc.rb +54 -0
- data/lib/gloo/persist/subtree_extractor.rb +9 -37
- data/lib/gloo/persist/trivia_buffer.rb +7 -7
- data/lib/gloo/verbs/load.rb +27 -21
- data/lib/gloo/verbs/move.rb +42 -3
- data/lib/gloo/verbs/save.rb +26 -4
- data/test.gloo/app/exit_status.test.gloo +34 -0
- data/test.gloo/app/fixtures/exit_after_error.gloo +9 -0
- data/test.gloo/lang/declaration.test.gloo +36 -0
- data/test.gloo/lang/fixtures/mixed_indent.gloo +5 -0
- data/test.gloo/lang/naming.test.gloo +44 -6
- data/test.gloo/verbs/fixtures/load_beside.gloo +6 -0
- data/test.gloo/verbs/fixtures/lossless_text.gloo +7 -0
- data/test.gloo/verbs/fixtures/lossless_text_saved.gloo +9 -0
- data/test.gloo/verbs/load.test.gloo +18 -0
- data/test.gloo/verbs/move.test.gloo +39 -0
- data/test.gloo/verbs/save.test.gloo +239 -0
- metadata +10 -1
|
@@ -14,6 +14,7 @@ module Gloo
|
|
|
14
14
|
|
|
15
15
|
PATH_EXISTS_ERR = 'Will not overwrite a file not already saved there: '.freeze
|
|
16
16
|
RELOAD_DIRTY_WARNING = 'Reloading will discard unsaved changes in: '.freeze
|
|
17
|
+
NOT_SAVED_WARNING = 'Not saved (no file yet): '.freeze
|
|
17
18
|
|
|
18
19
|
#
|
|
19
20
|
# Constructor for the persistence manager.
|
|
@@ -33,10 +34,25 @@ module Gloo
|
|
|
33
34
|
end
|
|
34
35
|
|
|
35
36
|
#
|
|
36
|
-
# Save every open file.
|
|
37
|
+
# Save every open file. A new top-level object has no file to go
|
|
38
|
+
# in, so it isn't saved; warn, naming each one.
|
|
37
39
|
#
|
|
38
40
|
def save_all
|
|
39
41
|
save_batch( @maps )
|
|
42
|
+
warn_unsaved_roots
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
#
|
|
46
|
+
# Warn about top-level objects no file declares (made since load),
|
|
47
|
+
# which a bare save leaves out.
|
|
48
|
+
#
|
|
49
|
+
def warn_unsaved_roots
|
|
50
|
+
index = Gloo::Persist::SaveIndex.new( @engine, @maps )
|
|
51
|
+
names = @engine.heap.root.children.reject { |o| index.placed?( o ) }.map( &:name )
|
|
52
|
+
return if names.empty?
|
|
53
|
+
|
|
54
|
+
how = names.size == 1 ? "save #{names.first}" : 'save {name} for each'
|
|
55
|
+
@engine.warn "#{NOT_SAVED_WARNING}#{names.join( ', ' )}. Use #{how}."
|
|
40
56
|
end
|
|
41
57
|
|
|
42
58
|
#
|
|
@@ -50,7 +66,7 @@ module Gloo
|
|
|
50
66
|
return unless obj
|
|
51
67
|
|
|
52
68
|
fs_list = find_file_storages( obj )
|
|
53
|
-
return save_new( obj, @mech.resolve_save_path( root_of( obj ).name ) ) if fs_list.empty?
|
|
69
|
+
return save_new( obj, @mech.resolve_save_path( root_of( obj ).name, save_folder ) ) if fs_list.empty?
|
|
54
70
|
|
|
55
71
|
@engine.event_manager.on_save obj
|
|
56
72
|
save_batch( fs_list )
|
|
@@ -72,7 +88,7 @@ module Gloo
|
|
|
72
88
|
obj = resolve_for_save( name )
|
|
73
89
|
return unless obj
|
|
74
90
|
|
|
75
|
-
pn = @mech.resolve_save_path( path )
|
|
91
|
+
pn = @mech.resolve_save_path( path, save_folder )
|
|
76
92
|
mapped = @maps.find { |fs| fs.pn == pn }
|
|
77
93
|
return save_mapped( obj, mapped, pn ) if mapped
|
|
78
94
|
return @engine.err( "#{PATH_EXISTS_ERR}#{pn}" ) if @mech.exist?( pn )
|
|
@@ -87,8 +103,13 @@ module Gloo
|
|
|
87
103
|
# a missing/unresolvable file is reported via engine.err rather
|
|
88
104
|
# than failing silently.
|
|
89
105
|
#
|
|
90
|
-
|
|
91
|
-
|
|
106
|
+
# A relative name is looked for in the given folders, in order
|
|
107
|
+
# (the load verb passes search_folders). Without them (a file
|
|
108
|
+
# named on the command line, the app's start file) it's looked
|
|
109
|
+
# for as given, then in the project folder and gloo home.
|
|
110
|
+
#
|
|
111
|
+
def load( name, folders = nil )
|
|
112
|
+
pns = get_full_path_names( name, folders )
|
|
92
113
|
if pns.nil? || pns.empty?
|
|
93
114
|
@engine.err( Gloo::Core::NotFound.file( name ) ) unless name.to_s.strip.empty?
|
|
94
115
|
return false
|
|
@@ -212,18 +233,63 @@ module Gloo
|
|
|
212
233
|
|
|
213
234
|
|
|
214
235
|
#
|
|
215
|
-
# Get the full path and name of the file
|
|
236
|
+
# Get the full path and name of the file, looking for a relative
|
|
237
|
+
# name in the given folders (see load).
|
|
216
238
|
#
|
|
217
|
-
def get_full_path_names( name )
|
|
239
|
+
def get_full_path_names( name, folders = nil )
|
|
218
240
|
return nil if name.strip.empty?
|
|
219
241
|
|
|
220
242
|
if name.strip[ -1 ] == '*'
|
|
221
|
-
return @mech.get_all_files_in( name[ 0..-2 ] )
|
|
243
|
+
return @mech.get_all_files_in( name[ 0..-2 ], folders )
|
|
222
244
|
else
|
|
223
|
-
return @mech.expand( name )
|
|
245
|
+
return @mech.expand( name, folders )
|
|
224
246
|
end
|
|
225
247
|
end
|
|
226
248
|
|
|
249
|
+
#
|
|
250
|
+
# Where the load verb looks for a relative name, in order.
|
|
251
|
+
# In App mode the app folder comes first, so an app's names
|
|
252
|
+
# mean the same thing in every one of its files. Otherwise the
|
|
253
|
+
# folder next to the loading file comes first, then the projects
|
|
254
|
+
# folder (the project_path setting). The gloo home is last.
|
|
255
|
+
#
|
|
256
|
+
def search_folders
|
|
257
|
+
near = near_folder
|
|
258
|
+
project = @engine.settings.project_path
|
|
259
|
+
home = @engine.settings.user_root
|
|
260
|
+
folders = if @engine.mode == Gloo::App::Mode::APP
|
|
261
|
+
[ project, near, home ]
|
|
262
|
+
else
|
|
263
|
+
[ near, project, home ]
|
|
264
|
+
end
|
|
265
|
+
return folders.compact.uniq
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
#
|
|
269
|
+
# Where a new file saved with a relative name goes: the first
|
|
270
|
+
# place a relative load looks (see search_folders). That's the
|
|
271
|
+
# app folder in App mode; otherwise next to the saving script's
|
|
272
|
+
# file, or the working folder for a command typed at the prompt,
|
|
273
|
+
# or the projects folder for a script that didn't come from a file.
|
|
274
|
+
#
|
|
275
|
+
def save_folder
|
|
276
|
+
return search_folders.first
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
#
|
|
280
|
+
# The folder "next to" the line now running: the folder of the
|
|
281
|
+
# file the running script came from (the same folder source_dir
|
|
282
|
+
# gives). A command typed at the prompt has no file, so it's the
|
|
283
|
+
# working folder. A script that didn't come from a file has none.
|
|
284
|
+
#
|
|
285
|
+
def near_folder
|
|
286
|
+
script = @engine.exec_env.running_script
|
|
287
|
+
return Dir.pwd unless script
|
|
288
|
+
|
|
289
|
+
path = source_file_of( script.obj )
|
|
290
|
+
return path ? File.dirname( path ) : nil
|
|
291
|
+
end
|
|
292
|
+
|
|
227
293
|
#
|
|
228
294
|
# Check to see if a given path name refers to a gloo object file.
|
|
229
295
|
#
|
|
@@ -271,39 +337,55 @@ module Gloo
|
|
|
271
337
|
end
|
|
272
338
|
|
|
273
339
|
#
|
|
274
|
-
# Save a set of files as one batch
|
|
275
|
-
#
|
|
276
|
-
#
|
|
277
|
-
#
|
|
278
|
-
#
|
|
340
|
+
# Save a set of files as one batch, with one index of what every
|
|
341
|
+
# loaded file declares: each file writes back its own declarations
|
|
342
|
+
# and leaves other files' alone, and a new or moved object is
|
|
343
|
+
# written once, by the file its parent's first declaration is in.
|
|
344
|
+
# A file an object moved into is saved too, so the object isn't
|
|
345
|
+
# dropped from one file without being written to another. Each
|
|
346
|
+
# file then keeps a source document for what was written.
|
|
279
347
|
#
|
|
280
348
|
def save_batch( files )
|
|
281
|
-
|
|
349
|
+
index = Gloo::Persist::SaveIndex.new( @engine, @maps )
|
|
350
|
+
files = with_move_targets( files, index )
|
|
351
|
+
saved = files.map { |fs| [ fs, fs.save( :index => index ) ] }
|
|
352
|
+
saved.each { |fs, doc| fs.saved( doc ) }
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
#
|
|
356
|
+
# The files to save, plus any file an object declared in one of
|
|
357
|
+
# them has moved into.
|
|
358
|
+
#
|
|
359
|
+
def with_move_targets( files, index )
|
|
360
|
+
all = files.dup
|
|
282
361
|
files.each do |fs|
|
|
283
|
-
|
|
362
|
+
next unless fs.source_doc
|
|
363
|
+
|
|
364
|
+
fs.source_doc.obj_nodes.each do |node|
|
|
365
|
+
next if index.standing?( node ) || !attached?( node.obj )
|
|
366
|
+
|
|
367
|
+
target, = index.writer_for( node.obj.parent )
|
|
368
|
+
all << target if target && !all.include?( target )
|
|
369
|
+
end
|
|
284
370
|
end
|
|
371
|
+
return all
|
|
285
372
|
end
|
|
286
373
|
|
|
287
374
|
#
|
|
288
|
-
#
|
|
375
|
+
# Is the object attached to a parent (it was moved, not deleted)?
|
|
289
376
|
#
|
|
290
|
-
def
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
return
|
|
377
|
+
def attached?( obj )
|
|
378
|
+
return false unless obj&.parent
|
|
379
|
+
|
|
380
|
+
return obj.parent.children.any? { |c| c.equal?( obj ) }
|
|
294
381
|
end
|
|
295
382
|
|
|
296
383
|
#
|
|
297
|
-
# Every heap object
|
|
298
|
-
# other than fs.
|
|
384
|
+
# Every heap object fs's SourceDoc declares.
|
|
299
385
|
#
|
|
300
|
-
def
|
|
386
|
+
def declared_by( fs )
|
|
301
387
|
owned = {}.compare_by_identity
|
|
302
|
-
|
|
303
|
-
next if other.equal?( fs )
|
|
304
|
-
|
|
305
|
-
collect_owned( other.source_doc&.children, owned )
|
|
306
|
-
end
|
|
388
|
+
collect_owned( fs.source_doc&.children, owned )
|
|
307
389
|
return owned
|
|
308
390
|
end
|
|
309
391
|
|
|
@@ -362,7 +444,7 @@ module Gloo
|
|
|
362
444
|
def save_new( obj, pn )
|
|
363
445
|
@engine.event_manager.on_save obj
|
|
364
446
|
fs = Gloo::Persist::FileStorage.new( @engine, pn, root_of( obj ) )
|
|
365
|
-
fs.save
|
|
447
|
+
fs.saved( fs.save )
|
|
366
448
|
@maps << fs
|
|
367
449
|
end
|
|
368
450
|
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# Author:: Eric Crane (mailto:eric.crane@mac.com)
|
|
2
|
+
# Copyright:: Copyright (c) 2026 Eric Crane. All rights reserved.
|
|
3
|
+
#
|
|
4
|
+
# What every loaded file declares, for one save: which declarations
|
|
5
|
+
# name each object (one object can be declared on several lines, in
|
|
6
|
+
# one file or several), which of them still stand, and where an object
|
|
7
|
+
# no declaration places (new, or moved) should be written.
|
|
8
|
+
#
|
|
9
|
+
# A declaration stands if its object is still under the parent it was
|
|
10
|
+
# loaded under, and the declaration it sits under stands too. The
|
|
11
|
+
# first declaration of an object (in load order) holds its value.
|
|
12
|
+
#
|
|
13
|
+
|
|
14
|
+
module Gloo
|
|
15
|
+
module Persist
|
|
16
|
+
class SaveIndex
|
|
17
|
+
|
|
18
|
+
#
|
|
19
|
+
# Index the source documents of the given file storages, in load
|
|
20
|
+
# order.
|
|
21
|
+
#
|
|
22
|
+
def initialize( engine, storages )
|
|
23
|
+
@engine = engine
|
|
24
|
+
@entries = {}.compare_by_identity
|
|
25
|
+
@standing = {}.compare_by_identity
|
|
26
|
+
@claimed = {}.compare_by_identity
|
|
27
|
+
@targets = {}.compare_by_identity
|
|
28
|
+
@in_order = []
|
|
29
|
+
@implied = {}.compare_by_identity
|
|
30
|
+
@members = {}.compare_by_identity
|
|
31
|
+
storages.each do |fs|
|
|
32
|
+
walk( fs, fs.source_doc.children, true, @engine.heap.root ) if fs.source_doc
|
|
33
|
+
end
|
|
34
|
+
find_shorthand_targets
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
#
|
|
38
|
+
# Does the declaration still stand (it's written back in its
|
|
39
|
+
# file)?
|
|
40
|
+
#
|
|
41
|
+
def standing?( node )
|
|
42
|
+
return @standing.fetch( node, false )
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
#
|
|
46
|
+
# Does the declaration hold its object's value (it's the first one
|
|
47
|
+
# that names the object)?
|
|
48
|
+
#
|
|
49
|
+
def holder?( node )
|
|
50
|
+
first = @entries[ node.obj ]&.first
|
|
51
|
+
return first ? first[ 1 ].equal?( node ) : true
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
#
|
|
55
|
+
# The declarations of an object, in load order, as [ fs, node ].
|
|
56
|
+
#
|
|
57
|
+
def entries_for( obj )
|
|
58
|
+
return @entries.fetch( obj, [] )
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
#
|
|
62
|
+
# Does some standing declaration place the object: its own, or a
|
|
63
|
+
# dotted one that names it on the way to its object?
|
|
64
|
+
#
|
|
65
|
+
def placed?( obj )
|
|
66
|
+
return declared?( obj ) || @implied.key?( obj )
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
#
|
|
70
|
+
# Does a standing declaration of the object's own place it?
|
|
71
|
+
#
|
|
72
|
+
def declared?( obj )
|
|
73
|
+
return entries_for( obj ).any? { |_, node| standing?( node ) }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
#
|
|
77
|
+
# Where a new or moved child of parent is written:
|
|
78
|
+
# [ fs, node, nil ] -- under that declaration of parent, after its
|
|
79
|
+
# last child; [ fs, node, prefix ] -- as a shorthand line after
|
|
80
|
+
# that dotted declaration, named prefix.child; or nil when no file
|
|
81
|
+
# takes it (a new root-level object isn't added to a file).
|
|
82
|
+
#
|
|
83
|
+
def writer_for( parent )
|
|
84
|
+
return nil if parent.nil? || parent.root?
|
|
85
|
+
|
|
86
|
+
fs, node = entries_for( parent ).find { |_, n| standing?( n ) }
|
|
87
|
+
return [ fs, node, nil ] if node
|
|
88
|
+
|
|
89
|
+
return @targets[ parent ]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
#
|
|
93
|
+
# The intermediate containers a dotted declaration writes new
|
|
94
|
+
# children for, as [ parent, prefix ].
|
|
95
|
+
#
|
|
96
|
+
def shorthand_targets( node )
|
|
97
|
+
return @targets.select { |_, t| t[ 1 ].equal?( node ) }.map { |parent, t| [ parent, t[ 2 ] ] }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
#
|
|
101
|
+
# Claim an object for writing in this save; false if a file
|
|
102
|
+
# already wrote it.
|
|
103
|
+
#
|
|
104
|
+
def claim( obj )
|
|
105
|
+
return false if @claimed.key?( obj )
|
|
106
|
+
|
|
107
|
+
@claimed[ obj ] = true
|
|
108
|
+
return true
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
#
|
|
112
|
+
# The children of parent that no standing declaration places (new,
|
|
113
|
+
# or moved in), claimed for writing.
|
|
114
|
+
#
|
|
115
|
+
def unplaced_children( parent )
|
|
116
|
+
return parent.children.select { |c| !placed?( c ) && claim( c ) }
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
#
|
|
122
|
+
# Record the declarations in a list of source nodes. container is
|
|
123
|
+
# the object the list's declarations are under (the heap root at
|
|
124
|
+
# the top of a file).
|
|
125
|
+
#
|
|
126
|
+
def walk( fs, nodes, parent_standing, container )
|
|
127
|
+
nodes.each do |node|
|
|
128
|
+
next unless node.is_a?( Source::ObjNode )
|
|
129
|
+
|
|
130
|
+
stands = parent_standing && stands_alone?( node, container )
|
|
131
|
+
@standing[ node ] = stands
|
|
132
|
+
@in_order << [ fs, node ]
|
|
133
|
+
( @entries[ node.obj ] ||= [] ) << [ fs, node ] if node.obj
|
|
134
|
+
walk( fs, node.children, stands, node.obj || container )
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
#
|
|
139
|
+
# Is a declaration's object where the declaration puts it: under
|
|
140
|
+
# its parent as loaded, and -- for a dotted (shorthand) name --
|
|
141
|
+
# reached from container by that path? A line gloo couldn't make
|
|
142
|
+
# an object for is always kept.
|
|
143
|
+
#
|
|
144
|
+
def stands_alone?( node, container )
|
|
145
|
+
return true unless node.obj
|
|
146
|
+
return false unless node.obj.parent.equal?( node.home ) && child_of?( node.obj, node.home )
|
|
147
|
+
|
|
148
|
+
segments = node.name.split( '.' )
|
|
149
|
+
return true if segments.size == 1
|
|
150
|
+
|
|
151
|
+
o = node.obj.parent
|
|
152
|
+
segments[ 0..-2 ].reverse.each do |seg|
|
|
153
|
+
return false unless o && o.name.casecmp?( seg ) && child_of?( o, o.parent )
|
|
154
|
+
|
|
155
|
+
o = o.parent
|
|
156
|
+
end
|
|
157
|
+
return o.equal?( container )
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
#
|
|
161
|
+
# Is child one of parent's children (by identity)? Each parent's
|
|
162
|
+
# children are gathered once, so a big container stays quick.
|
|
163
|
+
#
|
|
164
|
+
def child_of?( child, parent )
|
|
165
|
+
return false unless parent
|
|
166
|
+
|
|
167
|
+
unless @members.key?( parent )
|
|
168
|
+
set = {}.compare_by_identity
|
|
169
|
+
parent.children.each { |c| set[ c ] = true }
|
|
170
|
+
@members[ parent ] = set
|
|
171
|
+
end
|
|
172
|
+
return @members[ parent ].key?( child )
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
#
|
|
176
|
+
# For each container that only exists through dotted names (it has
|
|
177
|
+
# no declaration of its own), the shorthand line new children go
|
|
178
|
+
# after: the last one through it, in the first file that has one.
|
|
179
|
+
#
|
|
180
|
+
def find_shorthand_targets
|
|
181
|
+
@in_order.each do |fs, node|
|
|
182
|
+
next unless node.obj && standing?( node ) && node.name.include?( '.' )
|
|
183
|
+
|
|
184
|
+
intermediates( node ).each do |parent, prefix|
|
|
185
|
+
next if declared?( parent )
|
|
186
|
+
|
|
187
|
+
@implied[ parent ] = true
|
|
188
|
+
target = @targets[ parent ]
|
|
189
|
+
@targets[ parent ] = [ fs, node, prefix ] if target.nil? || target[ 0 ].equal?( fs )
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
#
|
|
195
|
+
# The containers a dotted declaration names on its way to its
|
|
196
|
+
# object, as [ container, dotted path to it ].
|
|
197
|
+
#
|
|
198
|
+
def intermediates( node )
|
|
199
|
+
segments = node.name.split( '.' )
|
|
200
|
+
found = []
|
|
201
|
+
o = node.obj.parent
|
|
202
|
+
( segments.size - 1 ).downto( 1 ) do |i|
|
|
203
|
+
found.unshift( [ o, segments[ 0, i ].join( '.' ) ] )
|
|
204
|
+
o = o.parent
|
|
205
|
+
end
|
|
206
|
+
return found
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
@@ -36,15 +36,16 @@ module Gloo
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
#
|
|
39
|
-
# One line while a body might be open.
|
|
39
|
+
# One line while a body might be open. line is the line as read
|
|
40
|
+
# (continuations joined), raw its exact source text, line_tabs its
|
|
40
41
|
# indentation (ignored for a blank line, which is always kept).
|
|
41
|
-
# Returns true if the line was consumed as body content, or
|
|
42
|
-
#
|
|
43
|
-
#
|
|
42
|
+
# Returns true if the line was consumed as body content, or false
|
|
43
|
+
# if it closed the body and still needs to be dispatched normally
|
|
44
|
+
# by the caller.
|
|
44
45
|
#
|
|
45
|
-
def handle_line( line, line_tabs )
|
|
46
|
+
def handle_line( line, raw, line_tabs )
|
|
46
47
|
if line.strip.empty?
|
|
47
|
-
|
|
48
|
+
keep( raw )
|
|
48
49
|
return true
|
|
49
50
|
end
|
|
50
51
|
|
|
@@ -53,7 +54,8 @@ module Gloo
|
|
|
53
54
|
return false
|
|
54
55
|
end
|
|
55
56
|
|
|
56
|
-
|
|
57
|
+
keep( raw )
|
|
58
|
+
@obj.add_line( line ) unless line.strip.start_with?( '#' )
|
|
57
59
|
return true
|
|
58
60
|
end
|
|
59
61
|
|
|
@@ -69,14 +71,14 @@ module Gloo
|
|
|
69
71
|
private
|
|
70
72
|
|
|
71
73
|
#
|
|
72
|
-
#
|
|
73
|
-
#
|
|
74
|
-
# comment inside a script body is documentation, not
|
|
75
|
-
# to execute).
|
|
74
|
+
# Keep one body line's exact text: on the node's own text, and in
|
|
75
|
+
# the body's raw value. A comment line is kept but isn't added as a
|
|
76
|
+
# command (a comment inside a script body is documentation, not
|
|
77
|
+
# something to execute).
|
|
76
78
|
#
|
|
77
|
-
def
|
|
78
|
-
@
|
|
79
|
-
@
|
|
79
|
+
def keep( raw )
|
|
80
|
+
@node.raw << raw
|
|
81
|
+
@lines << raw.chomp( "\n" )
|
|
80
82
|
end
|
|
81
83
|
|
|
82
84
|
#
|
|
@@ -90,13 +92,6 @@ module Gloo
|
|
|
90
92
|
@obj = nil
|
|
91
93
|
end
|
|
92
94
|
|
|
93
|
-
#
|
|
94
|
-
# A line's raw text with its trailing newline removed.
|
|
95
|
-
#
|
|
96
|
-
def chomped( line )
|
|
97
|
-
return line.chomp( "\n" )
|
|
98
|
-
end
|
|
99
|
-
|
|
100
95
|
end
|
|
101
96
|
end
|
|
102
97
|
end
|
|
@@ -13,14 +13,21 @@ module Gloo
|
|
|
13
13
|
attr_reader :raw
|
|
14
14
|
|
|
15
15
|
#
|
|
16
|
-
# Set up a blank line node. raw is the full source line
|
|
17
|
-
#
|
|
18
|
-
# trailing whitespace
|
|
16
|
+
# Set up a blank line node. raw is the full source line with its
|
|
17
|
+
# line ending (none on a file's last line if the file doesn't end
|
|
18
|
+
# with one); any trailing whitespace on the blank line is kept.
|
|
19
19
|
#
|
|
20
20
|
def initialize( raw = '' )
|
|
21
21
|
@raw = raw
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
#
|
|
25
|
+
# The node's exact source text.
|
|
26
|
+
#
|
|
27
|
+
def text
|
|
28
|
+
return @raw
|
|
29
|
+
end
|
|
30
|
+
|
|
24
31
|
end
|
|
25
32
|
end
|
|
26
33
|
end
|
|
@@ -14,13 +14,21 @@ module Gloo
|
|
|
14
14
|
attr_reader :raw
|
|
15
15
|
|
|
16
16
|
#
|
|
17
|
-
# Set up a comment node. raw is the full source line,
|
|
18
|
-
# its
|
|
17
|
+
# Set up a comment node. raw is the full source line, with
|
|
18
|
+
# its line ending (none on a file's last line if the file doesn't
|
|
19
|
+
# end with one).
|
|
19
20
|
#
|
|
20
21
|
def initialize( raw )
|
|
21
22
|
@raw = raw
|
|
22
23
|
end
|
|
23
24
|
|
|
25
|
+
#
|
|
26
|
+
# The node's exact source text.
|
|
27
|
+
#
|
|
28
|
+
def text
|
|
29
|
+
return @raw
|
|
30
|
+
end
|
|
31
|
+
|
|
24
32
|
end
|
|
25
33
|
end
|
|
26
34
|
end
|
|
@@ -13,13 +13,21 @@ module Gloo
|
|
|
13
13
|
attr_reader :raw
|
|
14
14
|
|
|
15
15
|
#
|
|
16
|
-
# Set up a directive node. raw is the full source line,
|
|
17
|
-
# its
|
|
16
|
+
# Set up a directive node. raw is the full source line, with
|
|
17
|
+
# its line ending (none on a file's last line if the file doesn't
|
|
18
|
+
# end with one).
|
|
18
19
|
#
|
|
19
20
|
def initialize( raw )
|
|
20
21
|
@raw = raw
|
|
21
22
|
end
|
|
22
23
|
|
|
24
|
+
#
|
|
25
|
+
# The node's exact source text.
|
|
26
|
+
#
|
|
27
|
+
def text
|
|
28
|
+
return @raw
|
|
29
|
+
end
|
|
30
|
+
|
|
23
31
|
end
|
|
24
32
|
end
|
|
25
33
|
end
|