1use clap::Parser;
5use std::path::{Path, PathBuf};
6
7#[derive(Parser)]
8#[command(name = "blockworx")]
9pub(crate) struct Cli {
10 #[command(subcommand)]
11 pub(crate) command: Command,
12}
13
14#[derive(clap::Subcommand)]
15pub(crate) enum Command {
16 Log {
22 container: PathBuf,
24 },
25 Verify {
31 container: PathBuf,
33 },
34 Migrate {
41 container: PathBuf,
43 },
44}
45
46impl Command {
47 pub(crate) fn run(&self) -> ! {
48 match self {
49 Command::Log { container } => print_log(container),
50 Command::Verify { container } => print_verification(container),
51 Command::Migrate { container } => migrate_container(container),
52 }
53 }
54}
55
56fn migrate_container(container: &Path) -> ! {
60 let started = std::time::Instant::now();
61 match blockworx_store::migrate::to_gzip(&blockworx_store::storage::Native::at(container)) {
62 Ok(migrated) => {
63 println!(
64 "{}: {migrated}, in {:.1?}",
65 container.display(),
66 started.elapsed(),
67 );
68 std::process::exit(0);
69 }
70 Err(e) => {
71 eprintln!("{}: {e}", container.display());
72 std::process::exit(1);
73 }
74 }
75}
76
77fn print_log(container: &Path) -> ! {
80 let dumped = match blockworx_store::dump::dump(&blockworx_store::storage::Native::at(container))
81 {
82 Ok(dumped) => dumped,
83 Err(e) => {
84 eprintln!("{}: {e}", container.display());
85 std::process::exit(1);
86 }
87 };
88 for line in &dumped.lines {
89 println!("{line}");
90 }
91 match dumped.broken {
92 None => std::process::exit(0),
93 Some(report) => {
94 eprintln!("{report:?}");
95 std::process::exit(1);
96 }
97 }
98}
99
100fn print_verification(container: &Path) -> ! {
104 let started = std::time::Instant::now();
105 let checked =
106 match blockworx_store::dump::verify(blockworx_store::storage::Native::at(container)) {
107 Ok(checked) => checked,
108 Err(e) => {
109 eprintln!("{}: {e}", container.display());
110 std::process::exit(1);
111 }
112 };
113 for line in &checked.lines {
114 println!("{line}");
115 }
116 println!("in {:.1?}", started.elapsed());
117 match checked.broken {
118 None => std::process::exit(0),
119 Some(report) => {
120 eprintln!("{report:?}");
121 std::process::exit(1);
122 }
123 }
124}
125
126pub(crate) fn init_tracing() {
135 use tracing_subscriber::EnvFilter;
136 let filter =
137 EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("blockworx=warn"));
138 tracing_subscriber::fmt()
139 .with_env_filter(filter)
140 .with_target(false)
141 .with_writer(std::io::stderr)
142 .with_ansi(std::io::IsTerminal::is_terminal(&std::io::stderr()))
143 .init();
144}
145
146#[cfg(test)]
147mod tests {
148 use super::{Cli, Command};
149 use clap::Parser as _;
150
151 #[test]
155 fn every_subcommand_names_a_container() {
156 let dumped = Cli::try_parse_from(["blockworx", "log", "doc.bwx"]).expect("the dump parses");
157 let Command::Log { container } = dumped.command else {
158 panic!("`log` was not read as the subcommand");
159 };
160 assert_eq!(container, std::path::Path::new("doc.bwx"));
161
162 let checked =
163 Cli::try_parse_from(["blockworx", "verify", "doc.bwx"]).expect("so does the fsck");
164 let Command::Verify { container } = checked.command else {
165 panic!("`verify` was not read as the subcommand");
166 };
167 assert_eq!(container, std::path::Path::new("doc.bwx"));
168
169 let carried = Cli::try_parse_from(["blockworx", "migrate", "doc.bwx"])
170 .expect("and so does the carry");
171 let Command::Migrate { container } = carried.command else {
172 panic!("`migrate` was not read as the subcommand");
173 };
174 assert_eq!(container, std::path::Path::new("doc.bwx"));
175
176 assert!(Cli::try_parse_from(["blockworx", "doc.bwx"]).is_err());
177 assert!(Cli::try_parse_from(["blockworx"]).is_err());
178 }
179}