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>
34 lines
1.6 KiB
Swift
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)
|
|
}
|
|
}
|