tv-anarchy/Sources/TVAnarchyCore/CommandTemplate.swift
Natalie 92b38b1bae refactor(tv-anarchy): rename PlumTV→TVAnarchy and land session work
Renames Sources/PlumTV→TVAnarchy and PlumTVCore→TVAnarchyCore (the rename
the auto-commit service couldn't stage — it git-add'd the old, now-gone
paths and aborted every cycle), and commits the accumulated work:

- Library: black-built index fast path (LibraryIndex + scanFromIndex) with
  NFS-walk fallback; incremental --add on download-complete; mtime staleness
  gate; loose-file series-collapse fix; determinate scan/index progress.
- Cover art: keyless TVmaze cartoon-vs-live-action disambiguation (type/year).
- Player: sleep timer (timed + end-of-episode); visibility-gated polling.
- Home: Continue Watching cover art + live refresh; Recently Added; adult gate.
- Logs: multi-line selection + copy; truncated giant tx-list errors.
- Hover previews (opt-in) via black ffmpeg + scp.

Also gitignores foreign project trees (governor/mcp/fleet/recommender) that
sit in this directory but belong to their own repos.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 22:04:22 -07:00

34 lines
1.6 KiB
Swift

import Foundation
/// Renders a per-host command template (an argv array from hosts.json) into a
/// single remote command string. Tokens are either literals or placeholders:
/// `{name}` required; substituted with `subs[name]` (empty if absent)
/// `{name?}` optional; the whole word is dropped if the value is nil/empty
/// Every emitted word is single-quote-escaped, so titles/paths with spaces,
/// quotes, or `$` can never break out into shell syntax (argv-array semantics:
/// one array element exactly one shell word).
public enum CommandTemplate {
public static func render(_ template: [String], _ subs: [String: String?]) -> String {
var words: [String] = []
for token in template {
if let ph = placeholder(token) {
let value = subs[ph.name] ?? nil
if ph.optional, value == nil || value!.isEmpty { continue }
words.append(value ?? "")
} else {
words.append(token)
}
}
return words.map(SSHTransport.shq).joined(separator: " ")
}
/// "{name}" (name,false); "{name?}" (name,true); otherwise nil (literal).
static func placeholder(_ token: String) -> (name: String, optional: Bool)? {
guard token.hasPrefix("{"), token.hasSuffix("}") else { return nil }
var inner = String(token.dropFirst().dropLast())
let optional = inner.hasSuffix("?")
if optional { inner = String(inner.dropLast()) }
guard !inner.isEmpty else { return nil }
return (inner, optional)
}
}