import: read standing data from data/import/config.yml
Assisted-by: GLM-5.3 via pi
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