import: read standing data from data/import/config.yml
Assisted-by: GLM-5.3 via pi
change lzmlurpsqppktlzwquvqvqqvktyptsws
commit 2a09a2359582795be8203a9519fdb31fa5434ad2
author Alpha Chen <alpha@kejadlen.dev>
date
parent kuwtsqqw
diff --git a/lib/pro_tacts/import/config.rb b/lib/pro_tacts/import/config.rb
new file mode 100644
index 0000000..0747208
--- /dev/null
+++ b/lib/pro_tacts/import/config.rb
@@ -0,0 +1,29 @@
+require "yaml"
+
+module ProTacts
+  module Import
+    # The standing data the import tasks keep between plans, as a YAML
+    # mapping: data/import/config.yml, beside the plans under
+    # data/imports. Not committed — /data is ignored — because it holds
+    # this deployment's own host and choices. The keys are the tasks'
+    # to grow as they take standing data off the command line.
+    class Config
+      # A missing file is no configuration and an empty one is none
+      # either; a file whose top level is not a mapping is refused
+      # rather than half-read, naming the file.
+      #: (Pathname path) -> Hash[untyped, untyped]
+      def self.read(path)
+        return {} unless path.file?
+
+        document = YAML.safe_load_file(path)
+        return {} if document.nil?
+
+        unless document.is_a?(Hash)
+          raise ArgumentError, "#{path} is not a YAML mapping"
+        end
+
+        document
+      end
+    end
+  end
+end
diff --git a/tasks/import.rake b/tasks/import.rake
index 98bfcce..37b189f 100644
--- a/tasks/import.rake
+++ b/tasks/import.rake
@@ -8,6 +8,12 @@ require "pathname"
 module ImportTasks
   DIR = Pathname.new("data/imports")
 
+  # Where the import tasks keep their standing data between plans:
+  # data/import/config.yml (ProTacts::Import::Config reads it). The
+  # singular beside DIR's plural is deliberate — the directory of
+  # plans beside the one file of standing state.
+  CONFIG = Pathname.new("data/import/config.yml")
+
   # What each carrying task waits on — a contact still to carry through
   # that step — named so a status read can say what those tasks would
   # pick next without restating their choice.
diff --git a/test/pro_tacts/import/test_config.rb b/test/pro_tacts/import/test_config.rb
new file mode 100644
index 0000000..5681d08
--- /dev/null
+++ b/test/pro_tacts/import/test_config.rb
@@ -0,0 +1,48 @@
+require_relative "../../test_helper"
+
+require "pathname"
+require "tmpdir"
+
+require "pro_tacts/import/config"
+
+class ImportConfigTest < Minitest::Test
+  Config = ProTacts::Import::Config
+
+  def test_a_missing_file_is_no_configuration
+    Dir.mktmpdir do |dir|
+      assert_equal({}, Config.read(Pathname.new(dir) / "config.yml"))
+    end
+  end
+
+  def test_an_empty_file_is_no_configuration
+    with_config_file("") do |path|
+      assert_equal({}, Config.read(path))
+    end
+  end
+
+  def test_the_mapping_is_read
+    with_config_file("host: https://contacts\n") do |path|
+      assert_equal({"host" => "https://contacts"}, Config.read(path))
+    end
+  end
+
+  def test_a_file_whose_top_level_is_not_a_mapping_is_refused
+    with_config_file("- one\n- two\n") do |path|
+      error = assert_raises(ArgumentError) { Config.read(path) }
+
+      assert_equal "#{path} is not a YAML mapping", error.message
+    end
+  end
+
+  private
+
+  #: (String content) { (Pathname) -> void } -> void
+  def with_config_file(content)
+    Dir.mktmpdir do |dir|
+      path = Pathname.new(dir) / "config.yml"
+      path.write(content)
+
+      yield path
+    end
+  end
+end