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

Reply via email to