Skip to main content

semora_cli/
lib.rs

1//! The `semora` CLI: publish Markdown and HTML to a Semora server and
2//! wait for feedback from the page.
3//!
4//! The library exists for the binary and its tests; it is not a stable API.
5
6pub mod args;
7pub mod bundle;
8pub mod client;
9pub mod commands;
10pub mod config;
11pub mod credentials;
12pub mod docref;
13pub mod exit;
14pub mod output;
15pub mod remember;
16
17use clap::Parser;
18
19use crate::args::{Cli, Cmd};
20use crate::config::{Flags, Settings};
21
22/// Everything a command needs.
23pub struct Ctx {
24    pub settings: Settings,
25    pub json: bool,
26    pub store: credentials::Store,
27}
28
29impl Ctx {
30    pub fn client(&self) -> anyhow::Result<client::Client> {
31        client::Client::new(&self.settings)
32    }
33}
34
35/// Parses arguments, runs the command and returns the process exit code.
36pub async fn main_entry() -> i32 {
37    config::load_dotenv();
38    let cli = Cli::parse();
39    let env = |k: &str| std::env::var(k).ok();
40    let result = async {
41        let ctx = build_ctx(&cli, &env)?;
42        run(ctx, cli.cmd).await
43    }
44    .await;
45    match result {
46        Ok(code) => code,
47        Err(e) => {
48            match e.downcast_ref::<exit::Exit>() {
49                Some(x) => eprintln!("error: {}", x.message),
50                None => eprintln!("error: {e:#}"),
51            }
52            exit::code_of(&e)
53        }
54    }
55}
56
57fn build_ctx(cli: &Cli, env: &dyn Fn(&str) -> Option<String>) -> anyhow::Result<Ctx> {
58    let dir = config::config_dir(env);
59    let file = config::read_file_config(&dir)?;
60    let no_keyring = env("SEMORA_NO_KEYRING").is_some_and(|v| !v.is_empty() && v != "0");
61    let store = credentials::Store::new(&dir, !no_keyring);
62    let flags = Flags {
63        server: cli.server.clone(),
64        api_key: cli.api_key.clone(),
65    };
66    let settings = config::resolve(&flags, env, &file, store.default_server(), &|u| {
67        store.get(&config::origin(u))
68    })?;
69    Ok(Ctx {
70        settings,
71        json: cli.json,
72        store,
73    })
74}
75
76async fn run(ctx: Ctx, cmd: Cmd) -> anyhow::Result<i32> {
77    use commands::*;
78    match cmd {
79        Cmd::Push(a) => push::run(&ctx, a).await,
80        Cmd::Watch(a) => watch::run(&ctx, a).await,
81        Cmd::Wait(a) => wait::run(&ctx, a).await,
82        Cmd::Say {
83            doc,
84            text,
85            reply_to,
86        } => events::say(&ctx, &doc, &text, reply_to).await,
87        Cmd::Ls {
88            query,
89            tag,
90            kind,
91            limit,
92        } => docs::ls(&ctx, query, tag, kind, limit).await,
93        Cmd::Show { doc } => docs::show(&ctx, &doc).await,
94        Cmd::Get {
95            doc,
96            path,
97            rev,
98            output,
99        } => docs::get(&ctx, &doc, path, rev, output).await,
100        Cmd::Tag { doc, changes } => docs::tag(&ctx, &doc, &changes).await,
101        Cmd::Rm { doc } => docs::rm(&ctx, &doc).await,
102        Cmd::Open { doc } => docs::open(&ctx, &doc).await,
103        Cmd::Whoami => account::whoami(&ctx).await,
104        Cmd::Login { no_browser, name } => login::login(&ctx, no_browser, name).await,
105        Cmd::Logout => login::logout(&ctx).await,
106        Cmd::Keys(k) => account::keys(&ctx, k).await,
107        Cmd::Skill => skill::run(&ctx),
108    }
109}