module Backlog # The parsed contents of a repo's `.cc-os/config`. The file format is # '#'-commented `key=value` lines, no spaces around `=` (ADR-026/#23) — # the contract is owned by os-status (plugins/os-status/hooks/state.py). # Reads tolerate legacy `key = value` spacing and legacy `key: value` # YAML lines from earlier os-backlog versions; #merge always re-emits # every key in the normalized `key=value` form, matching os-status's # write_config_value. Never touches the filesystem itself — callers read # the file and pass its contents (or nil, if absent) in. class Config def initialize(contents) @data = {} contents.to_s.each_line do |line| line = line.strip next if line.empty? || line.start_with?("#") || line == "---" if line.include?("=") key, value = line.split("=", 2) elsif line.include?(": ") key, value = line.split(": ", 2) else next end @data[key.strip] = value.strip end end # @return [String, nil] the explicit board name override, if configured def board @data["board"] end # @return [String, nil] the tracker key (e.g. "forgejo:owner/repo"), if configured def tracker @data["tracker"] end # @return [String, nil] the cached Planka board id, if present (legacy; # Planka is retired per ADR-0042 — kept only so old .cc-os/config # files with this key still parse without error). def board_id @data["board_id"] end def configured? = !board.nil? def tracker_configured? = !tracker.nil? # @return [Hash] a copy of the parsed config data def to_h = @data.dup # Return new key=value contents with `key` set to `value`, preserving # every other key already present in `contents` (legacy YAML-style # lines are rewritten to key=value). Pure — does not touch the # filesystem; callers read/write the file themselves. # # @param contents [String, nil] existing raw `.cc-os/config` contents # @param key [String, Symbol] key to set # @param value [Object] value to set it to # @return [String] the updated key=value contents def self.merge(contents, key, value) data = new(contents).to_h data[key.to_s] = value.to_s data.map { |k, v| "#{k}=#{v}\n" }.join end # Like .merge but comment/format-preserving: every non-key line # (comments, blanks) is left byte-for-byte untouched; existing # key=value lines are re-emitted in the normalized no-spaces form; the # key is replaced in place or appended if absent. Mirrors os-status's # write_config_value (plugins/os-status/hooks/state.py) — the shared # .cc-os/config contract owner. Pure — no filesystem. # # @param contents [String, nil] existing raw `.cc-os/config` contents # @param key [String, Symbol] key to set # @param value [Object] value to set it to # @return [String] the updated contents def self.set(contents, key, value) key = key.to_s found = false out = contents.to_s.split("\n").map do |line| stripped = line.strip next line if stripped.empty? || stripped.start_with?("#") || !stripped.include?("=") k, v = stripped.split("=", 2) if k.strip == key found = true "#{key}=#{value}" else "#{k.strip}=#{v.strip}" end end out << "#{key}=#{value}" unless found "#{out.join("\n")}\n" end end end