2026-07-10 17:36:08 +00:00
|
|
|
module Backlog
|
2026-07-12 20:04:12 +00:00
|
|
|
# The parsed contents of a repo's `.cc-os/config`. The file format is
|
2026-07-12 21:47:01 +00:00
|
|
|
# '#'-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.
|
2026-07-10 17:36:08 +00:00
|
|
|
class Config
|
|
|
|
|
def initialize(contents)
|
|
|
|
|
@data = {}
|
2026-07-12 20:04:12 +00:00
|
|
|
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
|
2026-07-10 17:36:08 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# @return [String, nil] the explicit board name override, if configured
|
|
|
|
|
def board
|
|
|
|
|
@data["board"]
|
|
|
|
|
end
|
|
|
|
|
|
2026-07-12 19:54:08 +00:00
|
|
|
# @return [String, nil] the tracker key (e.g. "forgejo:owner/repo"), if configured
|
|
|
|
|
def tracker
|
|
|
|
|
@data["tracker"]
|
|
|
|
|
end
|
|
|
|
|
|
2026-07-13 16:27:43 +00:00
|
|
|
# @return [String, nil] the effective Planka board name: the explicit
|
|
|
|
|
# `board` key when present, else the board named by a
|
|
|
|
|
# `tracker=planka:<board>` key (the form config-write actually
|
|
|
|
|
# writes). Non-planka trackers contribute nothing.
|
|
|
|
|
def planka_board
|
|
|
|
|
return board if board
|
|
|
|
|
return nil unless tracker&.start_with?("planka:")
|
|
|
|
|
|
|
|
|
|
tracker.delete_prefix("planka:")
|
|
|
|
|
end
|
|
|
|
|
|
2026-07-13 13:18:45 +00:00
|
|
|
# @return [String, nil] the cached Planka board id, if present. Only a
|
|
|
|
|
# cache — the tracker's board *name* stays the source of truth
|
|
|
|
|
# (issue #22); BoardResolver decides whether to honor it.
|
|
|
|
|
def board_id
|
|
|
|
|
@data["board_id"]
|
|
|
|
|
end
|
|
|
|
|
|
2026-07-10 17:36:08 +00:00
|
|
|
def configured? = !board.nil?
|
2026-07-12 19:54:08 +00:00
|
|
|
def tracker_configured? = !tracker.nil?
|
|
|
|
|
|
|
|
|
|
# @return [Hash] a copy of the parsed config data
|
|
|
|
|
def to_h = @data.dup
|
|
|
|
|
|
2026-07-12 20:04:12 +00:00
|
|
|
# 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
|
2026-07-12 19:54:08 +00:00
|
|
|
# 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
|
2026-07-12 20:04:12 +00:00
|
|
|
# @return [String] the updated key=value contents
|
2026-07-12 19:54:08 +00:00
|
|
|
def self.merge(contents, key, value)
|
|
|
|
|
data = new(contents).to_h
|
2026-07-12 20:04:12 +00:00
|
|
|
data[key.to_s] = value.to_s
|
|
|
|
|
data.map { |k, v| "#{k}=#{v}\n" }.join
|
2026-07-12 19:54:08 +00:00
|
|
|
end
|
2026-07-13 13:18:45 +00:00
|
|
|
|
|
|
|
|
# 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
|
2026-07-10 17:36:08 +00:00
|
|
|
end
|
|
|
|
|
end
|