YAML Editor class
Do you manually edit YAML in your DevOps repos?
Do you rely on commments being in place and position order for your YAML fragments?
Do you wish you could occasionally update elements within those YAML files without losing comments or existing order?
Is sed just a little crude for the job?
If so then maybe this gem is useful to you.
About
This gem provides a class that can be utilised to update or add entries into YAML and takes an approach of using ordered fields to access portions of the YAML document and locate sections with an API that will allow arbitrary values to be set on keys.
The initial implementation is more about getting you to the point of being able to enter new values or update existing values and does not attempt to wrap access into a plain Ruby object as a collection.
It's main API is the update API that allows one to update or append values into a YAML document with some limitations. I've taken the approach of being pragmatic and requiring the caller to be sensible about quoting values for multi word strings throwing exceptions if they are needed but not provided and chosen this initial approach to keep consistent with the simple value access APIs.
Functionally this implementation is not exhaustive but meets our use cases.
High level API
#new(yaml)
Pass a YAML document as a string.
#to_s/#to_yaml
Output the YAML document following an edit session.
#update(*fields, val:)
Update a field within the YAML document using val indexed by the fields list.
Example use (from examples folder)
example1.rb
require_relative '../lib/yaml_editor'
testfile = File.expand_path('../../test/data/testdata.yml', __FILE__)
ed = YamlEditor.new(File.read(testfile))
ed.update('keyb', 1, 'KEY_B', val: '"This was changed"')
ed.update('keyb', 1, 'KEY_E', val: 'appended')
puts ed
As we can see the position and comment associated with KEY_B have been preserved and the new KEY_E value has been appended to the 2nd array element of the 'keyb' node.
[Running] ruby "/Users/andrew.smith/Ruby/yaml-formatted/examples/example1.rb"
---
keya:
second:
KEY_A: valuea
KEY_B: valueb # Comment
KEY_C: 'value c'
KEY_D: "value d" # Comment
keyb:
-
KEY_A: valuea
KEY_B: valueb # Comment
KEY_C: 'value c'
KEY_D: "value d" # Comment
-
KEY_A: valuee
KEY_B: "This was changed" # Comment
KEY_C: 'value g'
KEY_D: "value h" # Comment
KEY_E: appended
- # Empty elements should be ignored
-
KEY_A: valuei
KEY_B: valuej # Comment
KEY_C: 'value k'
KEY_D: "value l" # Comment
- SAMELINE: "a value m"
ANOTHER: 'extra value'
- ONITSOWN: 'standalone value'
keyc:
coll:
- name: 'index 0'
- name: 'index 1'
Mid level API
The mid level API may change over time, however it is currently public.
The low level API is used to read portions of the doucment, locate entries and break lines down into component parts.
#bracket(*fields)
Use fields and index numbers to determine start and end line range for a document section, the kind of element being referenced and where a value to also return an array containing the line split by tag, value and comment if present.
Low level API (semi private)
As above the low level API may be used to read portions of the document in a custom manner.
#inner_bracket(field, spos, epos)
Used by the bracket function this allows a single field to be searched within a hash between two line ranges.
#array(spos, epos)
When used with a line range containing an array it returns an array of tuples containig the start end end position for each child section.
For example use of mid and low level API please see the unit tests.
#lines(range)
Provide a group of lines as an array from the internal document.
#value(line)
Split the line into the tagged region (containing the leading spaces) up to the value, the actual value (including quotes as necessary, and any remaining text on the line such as commments.