gloo-yaml 1.1 → 1.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 (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/yaml_obj.rb +135 -30
  3. metadata +15 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 26032d41440f6a2857d9a2ce7317d00a9a0940e7b4ada4d237a9928cba6238f1
4
- data.tar.gz: 335a5da2eb3bfbf0aacedf4d264faaab1a24f6ab71696a43a8435329da15ecd4
3
+ metadata.gz: 5c52213b581759c71164627cd649a22d13702a526d695701f6abb7f6469b5bcd
4
+ data.tar.gz: 947b874647d5e232fd87c32d530fa9bff5d926877b12f3f2cfab50d0e63244ea
5
5
  SHA512:
6
- metadata.gz: 1e96a78608e74f1301d13b2934f0e9daa7b98ed2f71ab410fadbce09b21e251d6dbc7667aa3bb312de0cdf1d3c080d79510f7e80ebfa94e23a3cb0513fcdb0c9
7
- data.tar.gz: f076f137cd4c3f443a65decfbffe573e4dec2f5b956d6c748b3b7e51460f0b597aa67e5baa8a3ca67e2b11d11e74d4217bf46ce2a44d3cb145369dd899173d6a
6
+ metadata.gz: 87e99e16ef94698cbc6dd471538a31724c61002674063845ccb179b7d4a2f2d2b26e9c88f17ad39c1ce1d5d1af5b56d3412512942a521413d3272efdff0635c8
7
+ data.tar.gz: 6a8380687d34b2a2e76e5082acbe2c9294c89c2f7d22ac60922df96c72912e2cefbf5b1a67f0fefed01e810c2375ee6f891b27c683c7bbd644ad629c4654a8ca
data/lib/yaml_obj.rb CHANGED
@@ -5,6 +5,7 @@
5
5
  # loading and saving named fields via a container object.
6
6
  #
7
7
  require 'yaml'
8
+ require 'date'
8
9
 
9
10
  class YamlObj < Gloo::Core::Obj
10
11
 
@@ -48,40 +49,68 @@ class YamlObj < Gloo::Core::Obj
48
49
  # Load fields from the YAML file into a container.
49
50
  # The param is a path to a container object whose children
50
51
  # are matched by name to YAML keys.
52
+ # It is true when the file was loaded, false if it couldn't be.
51
53
  #
52
54
  def msg_load
53
- return unless @params&.token_count&.positive?
55
+ container = param_container( 'load' )
56
+ return @engine.heap.it.set_to( false ) unless container
54
57
 
55
- container = resolve_container
56
- return unless container
58
+ path = resolve_path
59
+ return @engine.heap.it.set_to( false ) unless path
57
60
 
58
- data = read_yaml_file
59
- return unless data
61
+ data = read_yaml_file( path )
62
+ return @engine.heap.it.set_to( false ) unless data
60
63
 
61
64
  container.children.each do |child|
62
65
  key = child.name
63
- child.set_value data[ key ].to_s if data.key?( key )
66
+ next unless data.key?( key )
67
+
68
+ # Nested data is planned for an upcoming release; until then a
69
+ # nested block or list is skipped, not loaded as Ruby's text for it.
70
+ if nested?( data[ key ] )
71
+ warn_nested( key )
72
+ next
73
+ end
74
+
75
+ child.set_value scalar_text( data[ key ] )
64
76
  end
77
+ @engine.heap.it.set_to true
65
78
  end
66
79
 
67
80
  #
68
81
  # Save fields from a container into the YAML file.
69
82
  # The param is a path to a container object whose children
70
- # are matched by name to YAML keys.
83
+ # are matched by name to YAML keys. Creates the file if it does
84
+ # not exist; keys without a matching child are kept.
71
85
  #
72
86
  def msg_save
73
- return unless @params&.token_count&.positive?
74
-
75
- container = resolve_container
87
+ container = param_container( 'save' )
76
88
  return unless container
77
89
 
78
- data = read_yaml_file || {}
90
+ path = resolve_path
91
+ return unless path
92
+
93
+ data = {}
94
+ if File.exist?( path )
95
+ data = read_yaml_file( path )
96
+ # The file couldn't be read (reported); don't overwrite it.
97
+ return unless data
98
+ end
79
99
 
80
100
  container.children.each do |child|
81
- data[ child.name ] = child.value
101
+ key = child.name
102
+
103
+ # Nested data is planned for an upcoming release; until then a
104
+ # nested block or list in the file is left as it is.
105
+ if nested?( data[ key ] )
106
+ warn_nested( key )
107
+ next
108
+ end
109
+
110
+ data[ key ] = child.value
82
111
  end
83
112
 
84
- write_yaml_file data
113
+ file_op( 'write', path ) { File.write( path, data.to_yaml ) }
85
114
  end
86
115
 
87
116
 
@@ -92,31 +121,101 @@ class YamlObj < Gloo::Core::Obj
92
121
  private
93
122
 
94
123
  #
95
- # Resolve the container object from the first param.
124
+ # Resolve the container named by the first param. Reports a syntax
125
+ # error if there's no param, or an error if the container doesn't
126
+ # exist, and returns nil.
96
127
  #
97
- def resolve_container
98
- pn = Gloo::Core::Pn.new( @engine, @params.first )
99
- pn.resolve
128
+ def param_container( msg )
129
+ unless @params&.token_count&.positive?
130
+ @engine.syntax_err "Missing the container to #{msg}! eg. tell #{name} to #{msg} (data)"
131
+ return nil
132
+ end
133
+
134
+ container = Gloo::Core::Pn.new( @engine, @params.first ).resolve
135
+ @engine.err Gloo::Core::NotFound.object( @params.first ) unless container
136
+ return container
137
+ end
138
+
139
+ #
140
+ # Get the expanded path of the YAML file (the object's value).
141
+ # Reports an error and returns nil if there's no path.
142
+ #
143
+ def resolve_path
144
+ if self.value.to_s.strip.empty?
145
+ @engine.err "yaml '#{name}' has no path; put a file path into #{name} first."
146
+ return nil
147
+ end
148
+
149
+ return File.expand_path( self.value )
100
150
  end
101
151
 
102
152
  #
103
- # Read and parse the YAML file. Returns a hash, or nil on error.
153
+ # Read and parse the YAML file. Returns a hash of its keys, or nil
154
+ # after reporting an error: a missing or unreadable file, invalid
155
+ # YAML, or YAML that isn't key: value pairs. Dates and times are
156
+ # allowed.
104
157
  #
105
- def read_yaml_file
106
- path = File.expand_path( self.value )
158
+ def read_yaml_file( path )
107
159
  unless File.exist?( path )
108
- @engine.log.error "YAML file not found: #{path}"
160
+ @engine.err Gloo::Core::NotFound.file( path )
109
161
  return nil
110
162
  end
111
- YAML.load_file( path ) || {}
163
+
164
+ content = file_op( 'read', path ) { File.read( path ) }
165
+ return nil unless content
166
+
167
+ data = YAML.safe_load( content, permitted_classes: [ Date, Time ] ) || {}
168
+ return data if data.is_a?( Hash )
169
+
170
+ return yaml_err( path, 'it is not a list of key: value pairs' )
171
+ rescue Psych::SyntaxError => e
172
+ return yaml_err( path, e.problem )
173
+ rescue Psych::Exception => e
174
+ return yaml_err( path, e.message )
175
+ end
176
+
177
+ #
178
+ # Report YAML that can't be used, and return nil.
179
+ #
180
+ def yaml_err( path, reason )
181
+ @engine.err "Couldn't read the YAML in '#{path}': #{reason}."
182
+ return nil
183
+ end
184
+
185
+ #
186
+ # Run a file operation, reporting a failure (eg. no permission, a
187
+ # missing folder) as an error. Returns nil if it failed.
188
+ #
189
+ def file_op( action, path )
190
+ return yield
191
+ rescue SystemCallError => e
192
+ @engine.err "Could not #{action} '#{path}': #{e.message}"
193
+ return nil
194
+ end
195
+
196
+ #
197
+ # Is the value a nested block or a list (not yet supported)?
198
+ #
199
+ def nested?( val )
200
+ return val.is_a?( Hash ) || val.is_a?( Array )
112
201
  end
113
202
 
114
203
  #
115
- # Serialize data hash and write it back to the YAML file.
204
+ # Warn that a nested block or list was left unchanged.
116
205
  #
117
- def write_yaml_file( data )
118
- path = File.expand_path( self.value )
119
- File.write( path, data.to_yaml )
206
+ def warn_nested( key )
207
+ @engine.warn "YAML key '#{key}' is a nested block or list, which isn't supported yet; it was left unchanged."
208
+ end
209
+
210
+ #
211
+ # The text for a simple YAML value. YAML reads a time with no zone
212
+ # as UTC, so times are shown in UTC rather than converted to local
213
+ # time (the same as gloo-md's md_doc).
214
+ #
215
+ def scalar_text( val )
216
+ return val.utc.strftime( '%Y-%m-%d %H:%M:%S UTC' ) if val.is_a?( Time )
217
+
218
+ return val.to_s
120
219
  end
121
220
 
122
221
  # ---------------------------------------------------------------------
@@ -134,11 +233,17 @@ class YamlObj < Gloo::Core::Obj
134
233
  'file (as its own value) and supports loading and saving ' \
135
234
  'named fields via a container object.',
136
235
  :messages => [
137
- 'load ({container.path}) — Load fields from the YAML file into the given container. Children of the container are matched by name to YAML keys. A parameter is required.',
138
- 'save ({container.path}) — Save fields from the given container into the YAML file, matching container children by name to YAML keys. A parameter is required.'
236
+ 'load ({container.path}) — Load fields from the YAML file into the given container. Children of the container are matched by name to YAML keys; dates and times load as text. A parameter is required. It is true when the file was loaded, false if it could not be.',
237
+ 'save ({container.path}) — Save fields from the given container into the YAML file, matching container children by name to YAML keys. Creates the file if it does not exist; keys with no matching child are kept. A parameter is required.'
139
238
  ],
140
- :notes => 'No vault documentation exists for this object type — ' \
141
- 'this was authored directly from the code.',
239
+ :notes => 'Errors: no container (a syntax error), a container ' \
240
+ 'that does not exist, an empty path, a file that does not ' \
241
+ 'exist or cannot be read or written, and YAML that is not ' \
242
+ 'valid or is not key: value pairs. load puts false in it on ' \
243
+ 'an error; save does not overwrite a file it could not read. ' \
244
+ 'Nested blocks and lists are not supported yet (planned for ' \
245
+ 'an upcoming release): load skips them with a warning, and ' \
246
+ 'save leaves them in the file as they are.',
142
247
  :examples => <<~EXAMPLES.strip
143
248
  settings [can] :
144
249
  path [yaml] : ~/.my_app/settings.yml
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gloo-yaml
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.1'
4
+ version: '1.2'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Crane
@@ -9,6 +9,20 @@ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: gloo
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '7.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '7.0'
12
26
  - !ruby/object:Gem::Dependency
13
27
  name: bundler
14
28
  requirement: !ruby/object:Gem::Requirement