1pub mod bx;
8pub mod cluster_gender;
9pub mod goodreads;
10pub mod index_names;
11pub mod openlib;
12pub mod scan_marc;
13
14use anyhow::Result;
15use clap::{Parser, Subcommand};
16use cpu_time::ProcessTime;
17use enum_dispatch::enum_dispatch;
18use happylog::clap::LogOpts;
19use log::*;
20use paste::paste;
21
22use crate::util::process;
23use crate::util::Timer;
24
25macro_rules! wrap_subcommands {
30 ($name:ty) => {
31 paste! {
32 #[derive(clap::Args, Debug)]
33 pub struct [<$name Wrapper>] {
34 #[command(subcommand)]
35 command: $name
36 }
37
38 impl Command for [<$name Wrapper>] {
39 fn exec(&self) -> Result<()> {
40 self.command.exec()
41 }
42 }
43 }
44 };
45}
46
47#[enum_dispatch]
49pub trait Command {
50 fn exec(&self) -> Result<()>;
52}
53
54#[enum_dispatch(Command)]
56#[derive(Subcommand, Debug)]
57pub enum RootCommand {
58 ScanMARC(scan_marc::ScanMARC),
59 IndexNames(index_names::IndexNames),
60 Openlib(openlib::OpenLib),
62 BX(BXCommandWrapper),
64 Goodreads(goodreads::Goodreads),
66 ClusterGender(cluster_gender::AuthorGender),
68}
69
70wrap_subcommands!(BXCommand);
71
72#[enum_dispatch(Command)]
73#[derive(Subcommand, Debug)]
74enum BXCommand {
75 Extract(bx::Extract),
77}
78
79#[derive(Parser, Debug)]
83#[command(name = "bookdata")]
84pub struct CLI {
85 #[command(flatten)]
86 logging: LogOpts,
87
88 #[command(subcommand)]
89 command: RootCommand,
90}
91
92impl CLI {
93 pub fn exec(self) -> Result<()> {
94 self.logging.init()?;
95
96 process::maybe_exit_early()?;
97
98 let timer = Timer::new();
99
100 let res = self.command.exec();
101 if let Err(e) = &res {
102 error!("command failed: {}", e);
103 return res;
104 }
105
106 info!("work completed in {}", timer.human_elapsed());
107 match ProcessTime::try_now() {
108 Ok(pt) => info!("used {} CPU time", friendly::duration(pt.as_duration())),
109 Err(e) => error!("error fetching CPU time: {}", e),
110 };
111
112 process::log_process_stats();
113 res
114 }
115}