This is an automated email from the ASF dual-hosted git repository.
sebb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/whimsy.git
The following commit(s) were added to refs/heads/master by this push:
new a625aea Add YAML in-place updates and tests
a625aea is described below
commit a625aeabe9d06dcf69a2efa41582a1c70675da30
Author: Sebb <[email protected]>
AuthorDate: Fri Oct 16 22:46:54 2020 +0100
Add YAML in-place updates and tests
---
lib/spec/fixtures/sample.yaml | 24 ++++++++++++++++
lib/spec/lib/yaml_spec.rb | 67 +++++++++++++++++++++++++++++++++++++++++++
lib/whimsy/asf/yaml.rb | 59 ++++++++++++++++++++++++++++++-------
3 files changed, 139 insertions(+), 11 deletions(-)
diff --git a/lib/spec/fixtures/sample.yaml b/lib/spec/fixtures/sample.yaml
new file mode 100644
index 0000000..e7d64f1
--- /dev/null
+++ b/lib/spec/fixtures/sample.yaml
@@ -0,0 +1,24 @@
+# Header comment
+
+---
+
+# body comment
+
+:key1:
+ subkey1:
+ att1: value1
+ subkey2:
+ att1: value1
+:key2:
+ subkey1:
+ att1: value1
+ subkey2:
+ att1: value1
+:key3:
+ subkey1:
+ att1: value1
+ subkey2:
+ att1: value1
+...
+
+# Trailer comment
diff --git a/lib/spec/lib/yaml_spec.rb b/lib/spec/lib/yaml_spec.rb
new file mode 100644
index 0000000..93f1599
--- /dev/null
+++ b/lib/spec/lib/yaml_spec.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'whimsy/asf/yaml'
+
+describe YamlFile do
+ tmpdir = Dir.mktmpdir
+ tmpname = File.join(tmpdir, 'yaml_spec1.yaml')
+ describe "YamlFile.read" do
+ it "should fail ENOENT" do
+ expect do
+ YamlFile.read(tmpname)
+ end.to raise_error(Errno::ENOENT)
+ end
+ end
+ describe "YamlFile.update_section" do
+ tmpdir = Dir.mktmpdir
+ tmpname = File.join(tmpdir, 'yaml_spec1.yaml')
+ it "should fail ENOENT" do
+ expect do
+ YamlFile.update_section(tmpname, nil) {|yaml| yaml}
+ end.to raise_error(Errno::ENOENT)
+ end
+ testfile = fixture_path('sample.yaml')
+ workfile = File.join(tmpdir, 'yaml_spec2.yaml')
+ FileUtils.cp testfile, workfile
+ # Check copied file is OK
+ it "read should return 3 entries" do
+ yaml = YamlFile.read(workfile)
+ expect(yaml.size).to equal(3)
+ end
+ it "should fail with missing section" do
+ expect do
+ YamlFile.update_section(workfile, 'none') {|yaml| yaml}
+ end.to raise_error(ArgumentError)
+ end
+ it "should find 2 entries" do
+ YamlFile.update_section(workfile, :key1) do |yaml|
+ expect(yaml.size).to eql(2)
+ yaml # return it unchanged
+ end
+ end
+ # check it is still OK after dummy update
+ it "read should return 3 entries" do
+ yaml = YamlFile.read(workfile)
+ expect(yaml.size).to equal(3)
+ end
+ it "should be unchanged" do
+ expect(File.read(testfile)).to eql(File.read(workfile))
+ end
+ end
+ describe "YamlFile.update" do
+ it "should create empty file" do
+ YamlFile.update(tmpname) do |yaml|
+ expect(yaml.class).to equal(Hash)
+ expect(yaml.size).to equal(0)
+ yaml['test'] = {a: 'b'}
+ expect(yaml.size).to equal(1)
+ yaml
+ end
+ end
+ it "read should return single entry" do
+ yaml = YamlFile.read(tmpname)
+ expect(yaml.size).to equal(1)
+ end
+ end
+end
diff --git a/lib/whimsy/asf/yaml.rb b/lib/whimsy/asf/yaml.rb
index f202336..1a95e73 100644
--- a/lib/whimsy/asf/yaml.rb
+++ b/lib/whimsy/asf/yaml.rb
@@ -26,6 +26,54 @@ module YamlFile
end
end
+ # replace a section of YAML text whilst preserving surrounding data
including comments.
+ # The args are passed to YAML.safe_load, and default to [Symbol]
+ 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
+
+ # Create the updated section with the correct indentation
+ # Use YAML dump to ensure correct syntax; drop the YAML header
+ new_section = YAML.dump({key => yield(section)}).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{^#{key.inspect}:\s*$.*?(?=^(:?\w+:|\.\.\.)$)}m
+ content[range] = new_section
+
+ content
+ end
+
+ # 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]
+ 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)
+
+ # rewrite the file
+ file.rewind
+ file.write content
+ file.truncate(file.pos)
+ end
+ end
+
#
# encapsulate reading a YAML file
# Opens the file read-only, with a shared lock, and parses the YAML
@@ -45,14 +93,3 @@ module YamlFile
end
end
end
-
-if __FILE__ == $0
- file = "/tmp/test.yaml"
- YamlFile.update(file) do |yaml|
- yaml['y'] = {ac: 'c'}
- end
- YamlFile.read(file) do |yaml|
- p yaml
- end
- p YamlFile.read(file)
-end