module Puppet::Util::FileParsing
A mini-language for parsing files. This is only used file the ParsedFile provider, but it makes more sense to split it out so it's easy to maintain in one place.
You can use this module to create simple parser/generator classes. For instance, the following parser should go most of the way to parsing /etc/passwd:
class Parser include Puppet::Util::FileParsing record_line :user, :fields => %w{name password uid gid gecos home shell}, :separator => ":" end
You would use it like this:
parser = Parser.new lines = parser.parse(File.read("/etc/passwd")) lines.each do |type, hash| # type will always be :user, since we only have one p hash end
Each line in this case would be a hash, with each field set appropriately. You could then call 'parser.to_line(hash)' on any of those hashes to generate the text line again.
Attributes
Public Instance Methods
Clear all existing record definitions. Only used for testing.
# File lib/puppet/util/fileparsing.rb 147 def clear_records 148 @record_types.clear 149 @record_order.clear 150 end
# File lib/puppet/util/fileparsing.rb 152 def fields(type) 153 record = record_type(type) 154 if record 155 record.fields.dup 156 else 157 nil 158 end 159 end
Try to match a record.
@param [String] line The line to be parsed @param [Puppet::Util::FileType] record The filetype to use for parsing
@return [Hash<Symbol, Object>] The parsed elements of the line
# File lib/puppet/util/fileparsing.rb 172 def handle_record_line(line, record) 173 ret = nil 174 if record.respond_to?(:process) 175 ret = record.send(:process, line.dup) 176 if ret 177 unless ret.is_a?(Hash) 178 raise Puppet::DevError, _("Process record type %{record_name} returned non-hash") % { record_name: record.name } 179 end 180 else 181 return nil 182 end 183 else 184 regex = record.match 185 if regex 186 # In this case, we try to match the whole line and then use the 187 # match captures to get our fields. 188 match = regex.match(line) 189 if match 190 ret = {} 191 record.fields.zip(match.captures).each do |field, value| 192 if value == record.absent 193 ret[field] = :absent 194 else 195 ret[field] = value 196 end 197 end 198 else 199 nil 200 end 201 else 202 ret = {} 203 sep = record.separator 204 205 # String "helpfully" replaces ' ' with /\s+/ in splitting, so we 206 # have to work around it. 207 if sep == " " 208 sep = / / 209 end 210 line_fields = line.split(sep) 211 record.fields.each do |param| 212 value = line_fields.shift 213 if value and value != record.absent 214 ret[param] = value 215 else 216 ret[param] = :absent 217 end 218 end 219 220 if record.rollup and ! line_fields.empty? 221 last_field = record.fields[-1] 222 val = ([ret[last_field]] + line_fields).join(record.joiner) 223 ret[last_field] = val 224 end 225 end 226 end 227 228 if ret 229 ret[:record_type] = record.name 230 return ret 231 else 232 return nil 233 end 234 end
Try to match a specific text line.
# File lib/puppet/util/fileparsing.rb 162 def handle_text_line(line, record) 163 line =~ record.match ? {:record_type => record.name, :line => line} : nil 164 end
# File lib/puppet/util/fileparsing.rb 236 def line_separator 237 @line_separator ||= "\n" 238 239 @line_separator 240 end
Split text into separate lines using the record separator.
# File lib/puppet/util/fileparsing.rb 243 def lines(text) 244 # NOTE: We do not have to remove trailing separators because split will ignore 245 # them by default (unless you pass -1 as a second parameter) 246 text.split(self.line_separator) 247 end
Split a bunch of text into lines and then parse them individually.
# File lib/puppet/util/fileparsing.rb 250 def parse(text) 251 count = 1 252 lines(text).collect do |line| 253 count += 1 254 val = parse_line(line) 255 if val 256 val 257 else 258 error = Puppet::ResourceError.new(_("Could not parse line %{line}") % { line: line.inspect }) 259 error.line = count 260 raise error 261 end 262 end 263 end
Handle parsing a single line.
# File lib/puppet/util/fileparsing.rb 266 def parse_line(line) 267 raise Puppet::DevError, _("No record types defined; cannot parse lines") unless records? 268 269 @record_order.each do |record| 270 # These are basically either text or record lines. 271 method = "handle_#{record.type}_line" 272 if respond_to?(method) 273 result = send(method, line, record) 274 if result 275 record.send(:post_parse, result) if record.respond_to?(:post_parse) 276 return result 277 end 278 else 279 raise Puppet::DevError, _("Somehow got invalid line type %{record_type}") % { record_type: record.type } 280 end 281 end 282 283 nil 284 end
Define a new type of record. These lines get split into hashes. Valid options are:
-
:absent
: What to use as value within a line, when a field is absent. Note that in the record object, the literal :absent symbol is used, and not this value. Defaults to “”. -
:fields
: The list of fields, as an array. By default, all fields are considered required. -
:joiner
: How to join fields together. Defaults to 't'. -
:optional
: Which fields are optional. If these are missing, you'll just get the 'absent' value instead of an ArgumentError. -
:rts
: Whether to remove trailing whitespace. Defaults to false. If true, whitespace will be removed; if a regex, then whatever matches the regex will be removed. -
:separator
: The record separator. Defaults to /s+/.
# File lib/puppet/util/fileparsing.rb 300 def record_line(name, options, &block) 301 raise ArgumentError, _("Must include a list of fields") unless options.include?(:fields) 302 303 record = FileRecord.new(:record, **options, &block) 304 record.name = name.intern 305 306 new_line_type(record) 307 end
Are there any record types defined?
# File lib/puppet/util/fileparsing.rb 310 def records? 311 defined?(@record_types) and ! @record_types.empty? 312 end
Define a new type of text record.
# File lib/puppet/util/fileparsing.rb 315 def text_line(name, options, &block) 316 raise ArgumentError, _("You must provide a :match regex for text lines") unless options.include?(:match) 317 318 record = FileRecord.new(:text, **options, &block) 319 record.name = name.intern 320 321 new_line_type(record) 322 end
Generate
a file from a bunch of hash records.
# File lib/puppet/util/fileparsing.rb 325 def to_file(records) 326 text = records.collect { |record| to_line(record) }.join(line_separator) 327 328 text += line_separator if trailing_separator 329 330 text 331 end
Convert our parsed record into a text record.
# File lib/puppet/util/fileparsing.rb 334 def to_line(details) 335 record = record_type(details[:record_type]) 336 unless record 337 raise ArgumentError, _("Invalid record type %{record_type}") % { record_type: details[:record_type].inspect } 338 end 339 340 if record.respond_to?(:pre_gen) 341 details = details.dup 342 record.send(:pre_gen, details) 343 end 344 345 case record.type 346 when :text; return details[:line] 347 else 348 return record.to_line(details) if record.respond_to?(:to_line) 349 350 line = record.join(details) 351 352 regex = record.rts 353 if regex 354 # If they say true, then use whitespace; else, use their regex. 355 if regex == true 356 regex = /\s+$/ 357 end 358 return line.sub(regex,'') 359 else 360 return line 361 end 362 end 363 end
Whether to add a trailing separator to the file. Defaults to true
# File lib/puppet/util/fileparsing.rb 366 def trailing_separator 367 if defined?(@trailing_separator) 368 return @trailing_separator 369 else 370 return true 371 end 372 end
# File lib/puppet/util/fileparsing.rb 374 def valid_attr?(type, attr) 375 type = type.intern 376 record = record_type(type) 377 if record && record.fields.include?(attr.intern) 378 return true 379 else 380 if attr.intern == :ensure 381 return true 382 else 383 false 384 end 385 end 386 end
Private Instance Methods
Define a new type of record.
# File lib/puppet/util/fileparsing.rb 391 def new_line_type(record) 392 @record_types ||= {} 393 @record_order ||= [] 394 395 raise ArgumentError, _("Line type %{name} is already defined") % { name: record.name } if @record_types.include?(record.name) 396 397 @record_types[record.name] = record 398 @record_order << record 399 400 record 401 end
Retrieve the record object.
# File lib/puppet/util/fileparsing.rb 404 def record_type(type) 405 @record_types[type.intern] 406 end