bookdata/cli/
mod.rs

1//! Command line interface for book data.
2//!
3//! The book data tools are implemented as a single monolithic executable (to reduce
4//! compilation time and disk space in common configurations, with different tools
5//! implemented as subcommands.  Each subcommand implements the [Command] trait, which
6//! exposes the command line arguments and invocation.
7pub 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
25/// Macro to generate wrappers for subcommand enums.
26///
27/// This is for subcommands that only exist to contain further subcommands,
28/// to make it easier to implement their wrapper classes.
29macro_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/// Trait implemented by book data commands.
48#[enum_dispatch]
49pub trait Command {
50    /// Run the command with options
51    fn exec(&self) -> Result<()>;
52}
53
54/// Enum to collect and dispatch CLI commands.
55#[enum_dispatch(Command)]
56#[derive(Subcommand, Debug)]
57pub enum RootCommand {
58    ScanMARC(scan_marc::ScanMARC),
59    IndexNames(index_names::IndexNames),
60    /// Commands for processing OpenLibrary data.
61    Openlib(openlib::OpenLib),
62    /// Commands for processing BookCrossing data.
63    BX(BXCommandWrapper),
64    /// Commands for processing GoodReads data.
65    Goodreads(goodreads::Goodreads),
66    /// Commands for working with clusters.
67    ClusterGender(cluster_gender::AuthorGender),
68}
69
70wrap_subcommands!(BXCommand);
71
72#[enum_dispatch(Command)]
73#[derive(Subcommand, Debug)]
74enum BXCommand {
75    /// Extract BX from source data and clean.
76    Extract(bx::Extract),
77}
78
79/// Entry point for the Book Data Tools.
80///
81/// This program runs the various book data tools, exposed as subcommands.
82#[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}