1use clap::{Args, Parser, Subcommand, ValueEnum};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
4pub enum Format {
5 Auto,
6 Text,
7 Json,
8}
9
10#[derive(Debug, Parser)]
11#[command(name = "onenote", version, about = "Your OneNote notebooks, from the terminal",
12 styles = styles(),
13 after_help = "Start with your Windows desktop session:\n onenote doctor\n onenote notebooks list\n onenote sections list --notebook ID\n onenote pages list --section ID\n onenote search 'quarterly review'\n onenote pages read ID\n onenote pages create --section ID --title Notes --file notes.txt --dry-run\n onenote pages append ID --text 'Follow up tomorrow'\n\nFor automation:\n onenote notebooks list -o json\n onenote schema\n\nRequires Windows OneNote desktop; WSL can use Windows PowerShell interop.\nNo app registration or separate sign-in. Writes use explicit pages create/append commands; preview with --dry-run.")]
14pub struct Cli {
15 #[arg(short = 'o', long, global = true, value_enum, default_value = "auto")]
17 pub output: Format,
18 #[arg(long, global = true)]
20 pub no_color: bool,
21 #[command(subcommand)]
22 pub command: Option<Command>,
23}
24
25#[derive(Debug, Subcommand)]
26pub enum Command {
27 Notebooks {
29 #[command(subcommand)]
30 command: Notebooks,
31 },
32 Sections {
34 #[command(subcommand)]
35 command: Sections,
36 },
37 Pages {
39 #[command(subcommand)]
40 command: Pages,
41 },
42 Search {
44 #[arg(value_parser = query)]
45 query: String,
46 #[arg(long, value_parser = id)]
48 scope: Option<String>,
49 #[arg(long)]
51 scan: bool,
52 #[arg(long, requires = "scan", value_parser = clap::value_parser!(u16).range(1..=500))]
54 scan_limit: Option<u16>,
55 #[arg(long, requires = "scan", value_parser = clap::value_parser!(u32).range(0..=1_000_000))]
57 scan_offset: Option<u32>,
58 #[command(flatten)]
59 page: PageArgs,
60 },
61 Doctor {
63 #[arg(long)]
65 offline: bool,
66 },
67 Capabilities,
69 Schema {
71 #[arg(long)]
72 command: Option<String>,
73 },
74 Completions { shell: clap_complete::Shell },
76}
77#[derive(Debug, Subcommand)]
78pub enum Notebooks {
79 List(PageArgs),
80}
81#[derive(Debug, Subcommand)]
82pub enum Sections {
83 List {
84 #[arg(long, value_parser = id)]
86 notebook: String,
87 #[command(flatten)]
88 page: PageArgs,
89 },
90}
91#[derive(Debug, Subcommand)]
92pub enum Pages {
93 List {
94 #[arg(long, value_parser = id)]
96 section: String,
97 #[command(flatten)]
98 page: PageArgs,
99 },
100 Create {
102 #[arg(long, value_parser = id)]
103 section: String,
104 #[arg(long)]
105 title: String,
106 #[command(flatten)]
107 content: ContentArgs,
108 },
109 Append {
111 #[arg(value_parser = id)]
112 id: String,
113 #[command(flatten)]
114 content: ContentArgs,
115 },
116 Read {
117 #[arg(value_parser = id)]
119 id: String,
120 #[arg(long)]
122 xml: bool,
123 },
124}
125#[derive(Debug, Args)]
126#[group(skip)]
127pub struct ContentArgs {
128 #[arg(long, required_unless_present = "file", conflicts_with = "file")]
130 pub text: Option<String>,
131 #[arg(long)]
133 pub file: Option<std::path::PathBuf>,
134 #[arg(long)]
136 pub dry_run: bool,
137}
138#[derive(Debug, Args)]
139pub struct PageArgs {
140 #[arg(long, default_value_t = 25, value_parser = clap::value_parser!(u16).range(1..=100))]
142 pub limit: u16,
143 #[arg(long, default_value_t = 0, value_parser = clap::value_parser!(u32).range(0..=1_000_000))]
145 pub offset: u32,
146 #[arg(long, value_delimiter = ',')]
148 pub fields: Vec<String>,
149}
150pub fn id(value: &str) -> Result<String, String> {
151 if value.trim().is_empty() || value.len() > 4096 || value.chars().any(char::is_control) {
152 Err("use a nonempty OneNote desktop ID returned by this CLI (maximum 4096 bytes)".into())
153 } else {
154 Ok(value.into())
155 }
156}
157fn query(value: &str) -> Result<String, String> {
158 if value.trim().is_empty() || value.len() > 8192 || value.contains('\0') {
159 Err("search must contain 1–8192 bytes of text".into())
160 } else {
161 Ok(value.into())
162 }
163}
164fn styles() -> clap::builder::Styles {
165 use clap::builder::styling::AnsiColor;
166 clap::builder::Styles::styled()
167 .header(AnsiColor::Magenta.on_default().bold())
168 .literal(AnsiColor::Magenta.on_default())
169 .usage(AnsiColor::Magenta.on_default().bold())
170 .placeholder(AnsiColor::Cyan.on_default())
171}