1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
//! §12/§13 checkout-verb argv parsing — `bl prime` / `bl sync` flags, split
//! from [`crate::checkout`] so the dispatch there stays orchestration (the
//! [`crate::mutate`]/`mutate_args` convention). `--remote` is the per-op store-
//! remote override every store-touching verb shares (the ONE §12 ladder's top
//! tier, bl-c2de); `--center` is PRIME-ONLY and means enrollment, not an alias
//! (bl-35e5) — the rule is `--remote` shapes one op, `--center` enrolls a checkout.
use std::io;
/// Parsed `bl sync` flags: an optional positional branch, `--as`, and the
/// per-op `--remote` override (the §12 ladder's top tier, bl-c2de).
pub(super) struct SyncOpts {
pub(super) actor: String,
pub(super) branch: Option<String>,
pub(super) remote: Option<String>,
}
/// Parsed `bl prime` flags: the resolved actor, the per-op `--remote` store-
/// remote override (over XDG for this op, §12), the `--install CENTER` that
/// triggers config adoption (§13), the `--center URL` that ENROLLS this checkout
/// (bl-35e5 — the durable binding write + config adoption in one, prime-only),
/// and `--stealth` — the §12 consent opt-out (sugar for `conf set task-remote
/// none`: the landing sentinel binds every later op, bl-9df0). `install` also
/// seeds the remote when `remote` is unset (the center is where the adopted
/// `tasks_branch` lives), resolved in [`crate::checkout::prime`].
pub(super) struct PrimeOpts {
pub(super) actor: String,
pub(super) remote: Option<String>,
pub(super) install: Option<String>,
pub(super) center: Option<String>,
pub(super) stealth: bool,
}
/// Parse `bl prime [--as ID] [--remote URL] [--center URL] [--install CENTER]
/// [--stealth]`. `--remote URL` is the per-op store-remote override, unchanged.
/// `--center URL` ENROLLS this checkout (bl-35e5): the durable per-clone binding
/// write PLUS config adoption from the center — so it SUBSUMES `--install`, and
/// the two are mutually exclusive (both name the center whose config we adopt;
/// one flag enrolls, so passing both is a redundant/contradictory pair — fail
/// loud, don't guess which URL wins). `--install` names the center to adopt config
/// from WITHOUT the durable binding (§13). `--stealth` opts out of any store
/// remote (§12) and so CONTRADICTS every flag that names one — fail loud, never
/// pick a winner silently. An unknown flag or positional is an error.
pub(super) fn parse_prime(args: &[String], default_actor: &str) -> io::Result<PrimeOpts> {
let mut o = PrimeOpts { actor: default_actor.to_string(), remote: None, install: None, center: None, stealth: false };
let mut i = 0;
while i < args.len() {
match args[i].as_str() {
"--as" => o.actor = value(args, &mut i, "--as")?,
"--remote" => o.remote = Some(value(args, &mut i, "--remote")?),
"--center" => o.center = Some(value(args, &mut i, "--center")?),
"--install" => o.install = Some(value(args, &mut i, "--install")?),
"--stealth" => o.stealth = true,
other => return Err(crate::usage(format!("prime: unexpected argument '{other}'"))),
}
i += 1;
}
if o.center.is_some() && o.install.is_some() {
return Err(crate::usage(
"prime: --center already adopts CENTER's config — pass --center (enroll) or --install (adopt only), not both",
));
}
if o.stealth && (o.remote.is_some() || o.center.is_some() || o.install.is_some()) {
return Err(crate::usage(
"prime: --stealth contradicts --remote/--center/--install — stealth opts out of any store remote",
));
}
Ok(o)
}
/// Parse `bl sync [BRANCH] [--as ID] [--remote URL]` — the positional is the sync
/// target (§13), `--remote` the per-op store-remote override (bl-c2de). `--center`
/// is NOT accepted here: it enrolls a checkout (prime-only, bl-35e5), so on `sync`
/// it falls through to the unexpected-flag error like any other unknown flag.
pub(super) fn parse_sync(args: &[String], default_actor: &str) -> io::Result<SyncOpts> {
let mut o = SyncOpts { actor: default_actor.to_string(), branch: None, remote: None };
let mut i = 0;
while i < args.len() {
match args[i].as_str() {
"--as" => o.actor = value(args, &mut i, "--as")?,
"--remote" => o.remote = Some(value(args, &mut i, "--remote")?),
flag if flag.starts_with('-') => {
return Err(crate::usage(format!("sync: unexpected flag '{flag}'")));
}
_ => {
if o.branch.replace(args[i].clone()).is_some() {
return Err(crate::usage("sync: at most one branch"));
}
}
}
i += 1;
}
Ok(o)
}
/// The value following a `--flag`, advancing the cursor; missing value is an
/// error — the parse step the checkout-lifecycle verbs (and `bl install`,
/// [`crate::install::run`]) share.
pub(crate) fn value(args: &[String], i: &mut usize, flag: &str) -> io::Result<String> {
*i += 1;
args.get(*i)
.cloned()
.ok_or_else(|| crate::usage(format!("{flag} needs a value")))
}