1use std::path::PathBuf;
3
4use clap::Parser;
5use color_eyre::eyre::eyre;
6use config::AppConfig;
7use kxio::{fs::FileSystem, kxeprintln as e, kxprintln as p, net::Net, print::Printer};
8use tokio::time::Instant;
9
10use crate::{nextcloud::client::DeckClient, trello::client::TrelloClient};
11
12use execute::Execute;
13
14mod api_result;
15mod check;
16mod config;
17mod conversion;
18mod execute;
19mod import;
20mod init;
21pub mod macros;
22mod nextcloud;
23mod rate_limit;
24mod template;
25mod trello;
26
27#[cfg(test)]
28mod tests;
29
30const NAME: &str = "trello-to-deck";
31
32#[derive(Parser, Debug)]
33#[clap(version = clap::crate_version!(), author = clap::crate_authors!(), about = clap::crate_description!())]
34pub struct Commands {
35 #[clap(long, action = clap::ArgAction::SetTrue)]
36 pub log: bool,
37 #[clap(long, action = clap::ArgAction::SetTrue)]
38 pub tokio_console: bool,
39 #[clap(subcommand)]
40 pub command: Command,
41}
42#[derive(Parser, Debug)]
43pub enum Command {
44 #[command(about = "Initialize configuration")]
46 Init,
47
48 #[command(about = "Check configuration and connection")]
50 Check,
51
52 #[command(about = "Import boards from Trello to Nextcloud Deck")]
54 Import,
55
56 #[command(about = "Trello-specific commands")]
58 #[clap(subcommand)]
59 Trello(trello::TrelloCommand),
60
61 #[command(about = "Nextcloud-specific commands")]
63 #[clap(subcommand)]
64 Nextcloud(nextcloud::NextcloudCommand),
65}
66
67#[derive(Clone)]
68pub struct Ctx {
69 pub fs: FileSystem,
70 pub net: Net,
71 pub prt: Printer,
72}
73impl From<PathBuf> for Ctx {
74 fn from(base: PathBuf) -> Self {
75 Self {
76 fs: kxio::fs::new(base),
77 net: kxio::net::new(),
78 prt: kxio::print::standard(),
79 }
80 }
81}
82
83#[derive(Clone)]
84pub(crate) struct FullCtx {
85 pub fs: FileSystem,
86 pub net: Net,
87 pub prt: Printer,
88 pub cfg: AppConfig,
89}
90
91impl FullCtx {
92 pub(crate) fn now(&self) -> Instant {
93 Instant::now()
94 }
95}
96
97impl FullCtx {
98 pub(crate) const fn deck_client(&self) -> DeckClient {
99 DeckClient::new(self)
100 }
101
102 pub(crate) const fn trello_client(&self) -> TrelloClient {
103 TrelloClient::new(self)
104 }
105}
106
107#[cfg_attr(test, mutants::skip)]
108pub async fn run(ctx: &Ctx, commands: &Commands) -> color_eyre::Result<()> {
109 if commands.tokio_console {
110 use tracing_subscriber::prelude::*;
111 let console_layer = console_subscriber::spawn();
112 tracing_subscriber::registry()
113 .with(console_layer)
114 .with(tracing_subscriber::fmt::layer())
115 .init();
116 } else if commands.log {
117 tracing::subscriber::set_global_default(
118 tracing_subscriber::FmtSubscriber::builder()
119 .with_max_level(tracing::Level::TRACE)
120 .with_env_filter("trace,hyper_util=info,kxio=info")
121 .with_file(true)
122 .with_line_number(true)
123 .finish(),
124 )?;
125 }
126 tracing::info!("ready");
127
128 let cfg = AppConfig::load(ctx);
129 match cfg {
130 Err(err) => {
131 if matches!(commands.command, Command::Init) {
132 init::run(ctx)
133 } else {
134 Err(eyre!("Missing or invalid config: {err}"))
135 }
136 }
137 Ok(cfg) => {
138 commands
139 .command
140 .execute(&FullCtx {
141 fs: ctx.fs.clone(),
142 net: ctx.net.clone(),
143 prt: ctx.prt.clone(),
144 cfg,
145 })
146 .await
147 }
148 }
149}