module YamlFile

YAML file support

Public Class Methods

read(yaml_file, *args) { |yaml| ... } click to toggle source

encapsulate reading a YAML file Opens the file read-only, with a shared lock, and parses the YAML This is yielded to the block (if provided), whilst holding the lock Otherwise the YAML is returned to the caller, and the lock is released The args are passed to YAML.safe_load, and default to [Symbol]

# File lib/whimsy/asf/yaml.rb, line 96
def self.read(yaml_file, *args)
  args << [Symbol] if args.empty?
  File.open(yaml_file, File::RDONLY) do |file|
    file.flock(File::LOCK_SH)
    yaml = YAML.safe_load(file.read, *args)
    if block_given?
      yield yaml
    else
      return yaml
    end
  end
end
replace_section(content, key, *args) { |section, yaml| ... } click to toggle source

replace a section of YAML text whilst preserving surrounding data including comments. The args are passed to YAML.safe_load, and default to [Symbol] The caller must provide a block, which is passed two JSON parameters:

  • the section related to the key

  • the entire file (this is for validation purposes)

Returns the updated text. If the block returns nil, returns nil so the caller can skip the file update

# File lib/whimsy/asf/yaml.rb, line 36
def self.replace_section(content, key, *args)
  raise ArgumentError, 'block is required' unless block_given?

  args << [Symbol] if args.empty?
  yaml = YAML.safe_load(content, *args)

  section = yaml[key]
  unless section
    raise ArgumentError, "Could not find section #{key.inspect}"
  end

  res = yield(section, yaml) # get the updated JSON

  return nil if res.nil? # i.e. don't update text

  output = content.dup # don't mutate caller data

  # Create the updated section with the correct indentation
  # Use YAML dump to ensure correct syntax; drop the YAML header
  new_section = YAML.dump({key => res}).sub(/\A---\n/, '')

  # replace the old section with the new one
  # assume it is delimited by the key and '...' or another key.
  # Keys may be symbols. Only handles top-level key matching.
  range = %r{^#{Regexp.escape(key.inspect)}:\s*$.*?(?=^(:?\w+:|\.\.\.)$)}m
  output[range] = new_section

  output
end
update(yaml_file, *args) { |yaml)| ... } click to toggle source

encapsulate updates to a YAML file opens the file for exclusive access with an exclusive lock, creating the file if necessary Yields the parsed YAML to the block, and writes the return data to the file The args are passed to YAML.safe_load, and default to [Symbol]

# File lib/whimsy/asf/yaml.rb, line 18
def self.update(yaml_file, *args)
  args << [Symbol] if args.empty?
  File.open(yaml_file, File::RDWR|File::CREAT, 0o644) do |file|
    file.flock(File::LOCK_EX)
    yaml = YAML.safe_load(file.read, *args) || {}
    file.rewind
    file.write YAML.dump(yield yaml)
    file.truncate(file.pos)
  end
end
update_section(yaml_file, key, *args, &block) click to toggle source

encapsulate updates to a section of a YAML file whilst preserving surrounding data including comments. opens the file for exclusive access Yields the parsed YAML to the block, and writes the updated data to the file The args are passed to YAML.safe_load, and default to [Symbol]

originally designed for updating committee-info.yaml
# File lib/whimsy/asf/yaml.rb, line 73
def self.update_section(yaml_file, key, *args, &block)
  raise ArgumentError, 'block is required' unless block_given?

  File.open(yaml_file, File::RDWR) do |file|
    file.flock(File::LOCK_EX)

    content = replace_section(file.read, key, *args, &block)

    unless content.nil?
      # rewrite the file
      file.rewind
      file.write content
      file.truncate(file.pos)
    end
  end
end