use anyhow::Result;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct ExportIncludeSet {
pub subagents: bool,
pub tools: bool,
pub system_reminders: bool,
pub mcp: bool,
pub tool_output: bool,
pub history: bool,
}
impl ExportIncludeSet {
pub fn default_clean() -> Self {
Self {
subagents: true,
tools: false,
system_reminders: false,
mcp: false,
tool_output: false,
history: false,
}
}
pub fn all() -> Self {
Self {
subagents: true,
tools: true,
system_reminders: true,
mcp: true,
tool_output: true,
history: true,
}
}
pub fn none() -> Self {
Self::default()
}
pub fn parse(s: &str) -> Result<Self> {
let mut set = Self::default();
let mut saw_all = false;
let mut saw_none = false;
for (idx, raw) in s.split(',').enumerate() {
let token = raw.trim().to_ascii_lowercase();
if token.is_empty() {
if s.trim().is_empty() && idx == 0 {
continue;
}
anyhow::bail!(
"empty token in --include list (got '{}'); \
remove the stray comma or use --include none",
s
);
}
match token.as_str() {
"all" => {
saw_all = true;
set = Self::all();
}
"none" => {
saw_none = true;
set = Self::none();
}
"subagents" => set.subagents = true,
"tools" => set.tools = true,
"system-reminders" => set.system_reminders = true,
"mcp" => set.mcp = true,
"tool-output" => set.tool_output = true,
"history" => set.history = true,
other => {
eprintln!(
"warning: ignoring unknown --include token '{}' \
(recognized: subagents, tools, system-reminders, mcp, \
tool-output, history, all, none)",
other
);
}
}
}
if saw_all && saw_none {
anyhow::bail!(
"--include cannot contain both 'all' and 'none' (got '{}'); \
pick one — they are mutually exclusive",
s
);
}
Ok(set)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_clean_matches_brief() {
let s = ExportIncludeSet::default_clean();
assert!(s.subagents);
assert!(!s.tools);
assert!(!s.system_reminders);
assert!(!s.mcp);
assert!(!s.tool_output);
assert!(!s.history);
}
#[test]
fn parse_subagents_only_is_default_clean() {
let s = ExportIncludeSet::parse("subagents").unwrap();
assert_eq!(s, ExportIncludeSet::default_clean());
}
#[test]
fn parse_all_token() {
let s = ExportIncludeSet::parse("all").unwrap();
assert_eq!(s, ExportIncludeSet::all());
}
#[test]
fn parse_none_token() {
let s = ExportIncludeSet::parse("none").unwrap();
assert_eq!(s, ExportIncludeSet::none());
}
#[test]
fn parse_export_only_tokens() {
let s = ExportIncludeSet::parse("tools,system-reminders").unwrap();
assert!(s.tools);
assert!(s.system_reminders);
assert!(!s.subagents);
}
#[test]
fn parse_case_insensitive() {
let s = ExportIncludeSet::parse("SubAgents,TOOLS,System-Reminders,MCP").unwrap();
assert!(s.subagents);
assert!(s.tools);
assert!(s.system_reminders);
assert!(s.mcp);
}
#[test]
fn parse_all_and_none_together_is_rejected() {
let err = ExportIncludeSet::parse("all,none").unwrap_err();
let msg = format!("{err:#}");
assert!(msg.to_lowercase().contains("all"));
assert!(msg.to_lowercase().contains("none"));
}
#[test]
fn parse_empty_token_between_commas_is_rejected() {
let err = ExportIncludeSet::parse("subagents,,mcp").unwrap_err();
let msg = format!("{err:#}");
assert!(msg.contains("empty token"), "got: {msg}");
}
#[test]
fn parse_empty_string_yields_none() {
let s = ExportIncludeSet::parse("").unwrap();
assert_eq!(s, ExportIncludeSet::none());
}
#[test]
fn parse_trims_whitespace() {
let s = ExportIncludeSet::parse(" subagents , history ").unwrap();
assert!(s.subagents);
assert!(s.history);
}
#[test]
fn parse_unknown_token_warns_and_skips() {
let s = ExportIncludeSet::parse("subagents,bogus,history").unwrap();
assert!(s.subagents);
assert!(s.history);
assert!(!s.tools);
}
}