1use std::collections::{BTreeMap, HashMap};
22use std::path::PathBuf;
23
24use clap::{Args, Parser, Subcommand, ValueEnum};
25use serde::{Deserialize, Serialize};
26pub use tokmd_types::{
27 ChildIncludeMode, ChildrenMode, ConfigMode, ExportFormat, RedactMode, TableFormat,
28};
29
30#[derive(Parser, Debug)]
34#[command(name = "tokmd", version, about, long_about = None)]
35pub struct Cli {
36 #[command(flatten)]
37 pub global: GlobalArgs,
38
39 #[command(flatten)]
41 pub lang: CliLangArgs,
42
43 #[command(subcommand)]
44 pub command: Option<Commands>,
45
46 #[arg(long, visible_alias = "view", global = true)]
48 pub profile: Option<String>,
49}
50
51#[derive(Args, Debug, Clone, Default)]
52pub struct GlobalArgs {
53 #[arg(long = "exclude", visible_alias = "ignore", value_name = "PATTERN")]
59 pub excluded: Vec<String>,
60
61 #[arg(long, value_enum, default_value_t = ConfigMode::Auto)]
63 pub config: ConfigMode,
64
65 #[arg(long)]
67 pub hidden: bool,
68
69 #[arg(long)]
73 pub no_ignore: bool,
74
75 #[arg(long)]
77 pub no_ignore_parent: bool,
78
79 #[arg(long)]
81 pub no_ignore_dot: bool,
82
83 #[arg(long, visible_alias = "no-ignore-git")]
85 pub no_ignore_vcs: bool,
86
87 #[arg(long)]
89 pub treat_doc_strings_as_comments: bool,
90
91 #[arg(short = 'v', long = "verbose", action = clap::ArgAction::Count)]
93 pub verbose: u8,
94
95 #[arg(long, global = true)]
97 pub no_progress: bool,
98}
99
100#[derive(Subcommand, Debug, Clone)]
101pub enum Commands {
102 Lang(CliLangArgs),
104
105 Module(CliModuleArgs),
107
108 Export(CliExportArgs),
110
111 Analyze(CliAnalyzeArgs),
113
114 Badge(BadgeArgs),
116
117 Init(InitArgs),
119
120 Completions(CompletionsArgs),
122
123 Run(RunArgs),
125
126 Diff(DiffArgs),
128
129 Context(CliContextArgs),
131
132 CheckIgnore(CliCheckIgnoreArgs),
134
135 Tools(ToolsArgs),
137
138 Gate(CliGateArgs),
140
141 Cockpit(CockpitArgs),
143}
144
145#[derive(Debug, Clone, Serialize, Deserialize, Default)]
146pub struct UserConfig {
147 pub profiles: BTreeMap<String, Profile>,
148 pub repos: BTreeMap<String, String>, }
150
151#[derive(Debug, Clone, Serialize, Deserialize, Default)]
152pub struct Profile {
153 pub format: Option<String>, pub top: Option<usize>,
156
157 pub files: Option<bool>,
159
160 pub module_roots: Option<Vec<String>>,
162 pub module_depth: Option<usize>,
163 pub min_code: Option<usize>,
164 pub max_rows: Option<usize>,
165 pub redact: Option<RedactMode>,
166 pub meta: Option<bool>,
167
168 pub children: Option<String>,
170}
171
172#[derive(Args, Debug, Clone)]
173pub struct RunArgs {
174 #[arg(value_name = "PATH", default_value = ".")]
176 pub paths: Vec<PathBuf>,
177
178 #[arg(long)]
180 pub output_dir: Option<PathBuf>,
181
182 #[arg(long)]
184 pub name: Option<String>,
185
186 #[arg(long, value_enum)]
188 pub analysis: Option<AnalysisPreset>,
189
190 #[arg(long, value_enum)]
192 pub redact: Option<RedactMode>,
193}
194
195#[derive(Args, Debug, Clone)]
196pub struct DiffArgs {
197 #[arg(long)]
199 pub from: Option<String>,
200
201 #[arg(long)]
203 pub to: Option<String>,
204
205 #[arg(value_name = "REF", num_args = 2)]
207 pub refs: Vec<String>,
208
209 #[arg(long, value_enum, default_value_t = DiffFormat::Md)]
211 pub format: DiffFormat,
212}
213
214#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
215#[serde(rename_all = "kebab-case")]
216pub enum DiffFormat {
217 #[default]
219 Md,
220 Json,
222}
223
224#[derive(Args, Debug, Clone)]
225pub struct CompletionsArgs {
226 #[arg(value_enum)]
228 pub shell: Shell,
229}
230
231#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
232#[serde(rename_all = "kebab-case")]
233pub enum Shell {
234 Bash,
235 Elvish,
236 Fish,
237 Powershell,
238 Zsh,
239}
240
241#[derive(Args, Debug, Clone, Default)]
242pub struct CliLangArgs {
243 #[arg(value_name = "PATH")]
245 pub paths: Option<Vec<PathBuf>>,
246
247 #[arg(long, value_enum)]
249 pub format: Option<TableFormat>,
250
251 #[arg(long)]
254 pub top: Option<usize>,
255
256 #[arg(long)]
258 pub files: bool,
259
260 #[arg(long, value_enum)]
262 pub children: Option<ChildrenMode>,
263}
264
265#[derive(Args, Debug, Clone)]
266pub struct CliModuleArgs {
267 #[arg(value_name = "PATH")]
269 pub paths: Option<Vec<PathBuf>>,
270
271 #[arg(long, value_enum)]
273 pub format: Option<TableFormat>,
274
275 #[arg(long)]
278 pub top: Option<usize>,
279
280 #[arg(long, value_delimiter = ',')]
285 pub module_roots: Option<Vec<String>>,
286
287 #[arg(long)]
293 pub module_depth: Option<usize>,
294
295 #[arg(long, value_enum)]
297 pub children: Option<ChildIncludeMode>,
298}
299
300#[derive(Args, Debug, Clone)]
301pub struct CliExportArgs {
302 #[arg(value_name = "PATH")]
304 pub paths: Option<Vec<PathBuf>>,
305
306 #[arg(long, value_enum)]
308 pub format: Option<ExportFormat>,
309
310 #[arg(long, value_name = "PATH")]
312 pub out: Option<PathBuf>,
313
314 #[arg(long, value_delimiter = ',')]
316 pub module_roots: Option<Vec<String>>,
317
318 #[arg(long)]
320 pub module_depth: Option<usize>,
321
322 #[arg(long, value_enum)]
324 pub children: Option<ChildIncludeMode>,
325
326 #[arg(long)]
328 pub min_code: Option<usize>,
329
330 #[arg(long)]
332 pub max_rows: Option<usize>,
333
334 #[arg(long, action = clap::ArgAction::Set)]
336 pub meta: Option<bool>,
337
338 #[arg(long, value_enum)]
340 pub redact: Option<RedactMode>,
341
342 #[arg(long, value_name = "PATH")]
344 pub strip_prefix: Option<PathBuf>,
345}
346
347#[derive(Args, Debug, Clone)]
348pub struct CliAnalyzeArgs {
349 #[arg(value_name = "INPUT", default_value = ".")]
351 pub inputs: Vec<PathBuf>,
352
353 #[arg(long, value_enum)]
355 pub preset: Option<AnalysisPreset>,
356
357 #[arg(long, value_enum)]
359 pub format: Option<AnalysisFormat>,
360
361 #[arg(long)]
363 pub window: Option<usize>,
364
365 #[arg(long, action = clap::ArgAction::SetTrue, conflicts_with = "no_git")]
367 pub git: bool,
368
369 #[arg(long = "no-git", action = clap::ArgAction::SetTrue, conflicts_with = "git")]
371 pub no_git: bool,
372
373 #[arg(long)]
375 pub output_dir: Option<PathBuf>,
376
377 #[arg(long)]
379 pub max_files: Option<usize>,
380
381 #[arg(long)]
383 pub max_bytes: Option<u64>,
384
385 #[arg(long)]
387 pub max_file_bytes: Option<u64>,
388
389 #[arg(long)]
391 pub max_commits: Option<usize>,
392
393 #[arg(long)]
395 pub max_commit_files: Option<usize>,
396
397 #[arg(long, value_enum)]
399 pub granularity: Option<ImportGranularity>,
400}
401
402#[derive(Args, Debug, Clone)]
403pub struct BadgeArgs {
404 #[arg(value_name = "INPUT", default_value = ".")]
406 pub inputs: Vec<PathBuf>,
407
408 #[arg(long, value_enum)]
410 pub metric: BadgeMetric,
411
412 #[arg(long, value_enum)]
414 pub preset: Option<AnalysisPreset>,
415
416 #[arg(long, action = clap::ArgAction::SetTrue, conflicts_with = "no_git")]
418 pub git: bool,
419
420 #[arg(long = "no-git", action = clap::ArgAction::SetTrue, conflicts_with = "git")]
422 pub no_git: bool,
423
424 #[arg(long)]
426 pub max_commits: Option<usize>,
427
428 #[arg(long)]
430 pub max_commit_files: Option<usize>,
431
432 #[arg(long)]
434 pub out: Option<PathBuf>,
435}
436
437#[derive(Args, Debug, Clone)]
438pub struct InitArgs {
439 #[arg(long, value_name = "DIR", default_value = ".")]
441 pub dir: PathBuf,
442
443 #[arg(long)]
445 pub force: bool,
446
447 #[arg(long)]
449 pub print: bool,
450
451 #[arg(long, value_enum, default_value_t = InitProfile::Default)]
453 pub template: InitProfile,
454
455 #[arg(long)]
457 pub non_interactive: bool,
458}
459
460#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
461#[serde(rename_all = "kebab-case")]
462pub enum AnalysisFormat {
463 Md,
464 Json,
465 Jsonld,
466 Xml,
467 Svg,
468 Mermaid,
469 Obj,
470 Midi,
471 Tree,
472 Html,
473}
474
475#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
476#[serde(rename_all = "kebab-case")]
477pub enum AnalysisPreset {
478 Receipt,
479 Health,
480 Risk,
481 Supply,
482 Architecture,
483 Topics,
484 Security,
485 Identity,
486 Git,
487 Deep,
488 Fun,
489}
490
491#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
492#[serde(rename_all = "kebab-case")]
493pub enum ImportGranularity {
494 Module,
495 File,
496}
497
498#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
499#[serde(rename_all = "kebab-case")]
500pub enum BadgeMetric {
501 Lines,
502 Tokens,
503 Bytes,
504 Doc,
505 Blank,
506 Hotspot,
507}
508
509#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
510#[serde(rename_all = "kebab-case")]
511pub enum InitProfile {
512 Default,
513 Rust,
514 Node,
515 Mono,
516 Python,
517 Go,
518 Cpp,
519}
520
521#[derive(Args, Debug, Clone)]
522pub struct CliContextArgs {
523 #[arg(value_name = "PATH")]
525 pub paths: Option<Vec<PathBuf>>,
526
527 #[arg(long, default_value = "128k")]
529 pub budget: String,
530
531 #[arg(long, value_enum, default_value_t = ContextStrategy::Greedy)]
533 pub strategy: ContextStrategy,
534
535 #[arg(long, value_enum, default_value_t = ValueMetric::Code)]
537 pub rank_by: ValueMetric,
538
539 #[arg(long, value_enum, default_value_t = ContextOutput::List)]
541 pub output: ContextOutput,
542
543 #[arg(long)]
545 pub compress: bool,
546
547 #[arg(long, value_delimiter = ',')]
549 pub module_roots: Option<Vec<String>>,
550
551 #[arg(long)]
553 pub module_depth: Option<usize>,
554
555 #[arg(long)]
557 pub git: bool,
558
559 #[arg(long = "no-git")]
561 pub no_git: bool,
562
563 #[arg(long, default_value = "1000")]
565 pub max_commits: usize,
566
567 #[arg(long, default_value = "100")]
569 pub max_commit_files: usize,
570
571 #[arg(long, value_name = "PATH")]
573 pub out: Option<PathBuf>,
574
575 #[arg(long)]
577 pub force: bool,
578
579 #[arg(long, value_name = "DIR", conflicts_with = "out")]
581 pub bundle_dir: Option<PathBuf>,
582
583 #[arg(long, default_value = "10485760")]
585 pub max_output_bytes: u64,
586
587 #[arg(long, value_name = "PATH")]
589 pub log: Option<PathBuf>,
590}
591
592#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
593#[serde(rename_all = "kebab-case")]
594pub enum ContextStrategy {
595 #[default]
597 Greedy,
598 Spread,
600}
601
602#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
603#[serde(rename_all = "kebab-case")]
604pub enum ValueMetric {
605 #[default]
607 Code,
608 Tokens,
610 Churn,
612 Hotspot,
614}
615
616#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
617#[serde(rename_all = "kebab-case")]
618pub enum ContextOutput {
619 #[default]
621 List,
622 Bundle,
624 Json,
626}
627
628#[derive(Args, Debug, Clone)]
629pub struct CliCheckIgnoreArgs {
630 #[arg(value_name = "PATH", required = true)]
632 pub paths: Vec<PathBuf>,
633
634 #[arg(long, short = 'v')]
636 pub verbose: bool,
637}
638
639#[derive(Args, Debug, Clone)]
640pub struct ToolsArgs {
641 #[arg(long, value_enum, default_value_t = ToolSchemaFormat::Jsonschema)]
643 pub format: ToolSchemaFormat,
644
645 #[arg(long)]
647 pub pretty: bool,
648}
649
650#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
651#[serde(rename_all = "kebab-case")]
652pub enum ToolSchemaFormat {
653 Openai,
655 Anthropic,
657 #[default]
659 Jsonschema,
660 Clap,
662}
663
664#[derive(Args, Debug, Clone)]
665pub struct CliGateArgs {
666 #[arg(value_name = "INPUT")]
668 pub input: Option<PathBuf>,
669
670 #[arg(long)]
672 pub policy: Option<PathBuf>,
673
674 #[arg(long, value_enum)]
676 pub preset: Option<AnalysisPreset>,
677
678 #[arg(long, value_enum, default_value_t = GateFormat::Text)]
680 pub format: GateFormat,
681
682 #[arg(long)]
684 pub fail_fast: bool,
685}
686
687#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
688#[serde(rename_all = "kebab-case")]
689pub enum GateFormat {
690 #[default]
692 Text,
693 Json,
695}
696
697#[derive(Args, Debug, Clone)]
698pub struct CockpitArgs {
699 #[arg(long, default_value = "main")]
701 pub base: String,
702
703 #[arg(long, default_value = "HEAD")]
705 pub head: String,
706
707 #[arg(long, value_enum, default_value_t = CockpitFormat::Json)]
709 pub format: CockpitFormat,
710
711 #[arg(long, value_name = "PATH")]
713 pub output: Option<std::path::PathBuf>,
714}
715
716#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
717#[serde(rename_all = "kebab-case")]
718pub enum CockpitFormat {
719 #[default]
721 Json,
722 Md,
724 Sections,
726}
727
728#[derive(Debug, Clone, Default, Serialize, Deserialize)]
734#[serde(default)]
735pub struct TomlConfig {
736 pub scan: ScanConfig,
738
739 pub module: ModuleConfig,
741
742 pub export: ExportConfig,
744
745 pub analyze: AnalyzeConfig,
747
748 pub context: ContextConfig,
750
751 pub badge: BadgeConfig,
753
754 pub gate: GateConfig,
756
757 #[serde(default)]
759 pub view: HashMap<String, ViewProfile>,
760}
761
762#[derive(Debug, Clone, Default, Serialize, Deserialize)]
764#[serde(default)]
765pub struct ScanConfig {
766 pub paths: Option<Vec<String>>,
768
769 pub exclude: Option<Vec<String>>,
771
772 pub hidden: Option<bool>,
774
775 pub config: Option<String>,
777
778 pub no_ignore: Option<bool>,
780
781 pub no_ignore_parent: Option<bool>,
783
784 pub no_ignore_dot: Option<bool>,
786
787 pub no_ignore_vcs: Option<bool>,
789
790 pub doc_comments: Option<bool>,
792}
793
794#[derive(Debug, Clone, Default, Serialize, Deserialize)]
796#[serde(default)]
797pub struct ModuleConfig {
798 pub roots: Option<Vec<String>>,
800
801 pub depth: Option<usize>,
803
804 pub children: Option<String>,
806}
807
808#[derive(Debug, Clone, Default, Serialize, Deserialize)]
810#[serde(default)]
811pub struct ExportConfig {
812 pub min_code: Option<usize>,
814
815 pub max_rows: Option<usize>,
817
818 pub redact: Option<String>,
820
821 pub format: Option<String>,
823
824 pub children: Option<String>,
826}
827
828#[derive(Debug, Clone, Default, Serialize, Deserialize)]
830#[serde(default)]
831pub struct AnalyzeConfig {
832 pub preset: Option<String>,
834
835 pub window: Option<usize>,
837
838 pub format: Option<String>,
840
841 pub git: Option<bool>,
843
844 pub max_files: Option<usize>,
846
847 pub max_bytes: Option<u64>,
849
850 pub max_file_bytes: Option<u64>,
852
853 pub max_commits: Option<usize>,
855
856 pub max_commit_files: Option<usize>,
858
859 pub granularity: Option<String>,
861}
862
863#[derive(Debug, Clone, Default, Serialize, Deserialize)]
865#[serde(default)]
866pub struct ContextConfig {
867 pub budget: Option<String>,
869
870 pub strategy: Option<String>,
872
873 pub rank_by: Option<String>,
875
876 pub output: Option<String>,
878
879 pub compress: Option<bool>,
881}
882
883#[derive(Debug, Clone, Default, Serialize, Deserialize)]
885#[serde(default)]
886pub struct BadgeConfig {
887 pub metric: Option<String>,
889}
890
891#[derive(Debug, Clone, Default, Serialize, Deserialize)]
893#[serde(default)]
894pub struct GateConfig {
895 pub policy: Option<String>,
897
898 pub preset: Option<String>,
900
901 pub fail_fast: Option<bool>,
903
904 pub rules: Option<Vec<GateRule>>,
906}
907
908#[derive(Debug, Clone, Serialize, Deserialize)]
910pub struct GateRule {
911 pub name: String,
913
914 pub pointer: String,
916
917 pub op: String,
919
920 #[serde(default)]
922 pub value: Option<serde_json::Value>,
923
924 #[serde(default)]
926 pub values: Option<Vec<serde_json::Value>>,
927
928 #[serde(default)]
930 pub negate: bool,
931
932 #[serde(default)]
934 pub level: Option<String>,
935
936 #[serde(default)]
938 pub message: Option<String>,
939}
940
941#[derive(Debug, Clone, Default, Serialize, Deserialize)]
943#[serde(default)]
944pub struct ViewProfile {
945 pub format: Option<String>,
948
949 pub top: Option<usize>,
951
952 pub files: Option<bool>,
955
956 pub module_roots: Option<Vec<String>>,
959
960 pub module_depth: Option<usize>,
962
963 pub min_code: Option<usize>,
965
966 pub max_rows: Option<usize>,
968
969 pub redact: Option<String>,
971
972 pub meta: Option<bool>,
974
975 pub children: Option<String>,
977
978 pub preset: Option<String>,
981
982 pub window: Option<usize>,
984
985 pub budget: Option<String>,
988
989 pub strategy: Option<String>,
991
992 pub rank_by: Option<String>,
994
995 pub output: Option<String>,
997
998 pub compress: Option<bool>,
1000
1001 pub metric: Option<String>,
1004}
1005
1006impl TomlConfig {
1007 pub fn parse(s: &str) -> Result<Self, toml::de::Error> {
1009 toml::from_str(s)
1010 }
1011
1012 pub fn from_file(path: &Path) -> std::io::Result<Self> {
1014 let content = std::fs::read_to_string(path)?;
1015 toml::from_str(&content)
1016 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
1017 }
1018}
1019
1020use std::path::Path;
1021
1022pub type TomlResult<T> = Result<T, toml::de::Error>;