mod commands;
mod tui;
use anyhow::Result;
use clap::{Parser, Subcommand};
use shiki_config::Config;
use shiki_core::NotebookStore;
#[derive(Parser)]
#[command(
name = "shiki",
version,
about = "shiki (私記) — personal notes in the terminal"
)]
struct Cli {
#[command(subcommand)]
command: Option<Commands>,
}
#[derive(Subcommand)]
enum Commands {
New {
title: String,
#[arg(short, long)]
notebook: Option<String>,
},
List {
#[arg(short = 'n', long)]
notebook: Option<String>,
},
Edit {
note: String,
#[arg(short, long)]
notebook: Option<String>,
},
Show {
note: String,
#[arg(short, long)]
notebook: Option<String>,
},
Search {
query: String,
#[arg(short, long)]
notebook: Option<String>,
},
Daily {
#[arg(short, long)]
notebook: Option<String>,
},
Sync {
#[arg(short = 'n', long)]
notebook: Option<String>,
},
Config,
Doctor,
Notebook {
#[command(subcommand)]
action: NotebookAction,
},
Theme {
#[command(subcommand)]
action: ThemeAction,
},
}
#[derive(Subcommand)]
enum NotebookAction {
Create { name: String },
List,
Rename { old: String, new: String },
}
#[derive(Subcommand)]
enum ThemeAction {
List,
Set { name: String },
}
struct Context {
config: Config,
store: NotebookStore,
}
impl Context {
fn load() -> Result<Self> {
let config_path = Config::default_path()?;
let config = Config::load_or_init(&config_path)?;
let templates_dir = Config::default_templates_dir()?;
shiki_core::templates::ensure_defaults(&templates_dir)?;
let data_dir = Config::default_data_dir()?;
let store = NotebookStore::new(data_dir);
Ok(Self { config, store })
}
fn notebook_name(&self, override_name: Option<String>) -> String {
override_name.unwrap_or_else(|| self.config.general.default_notebook.clone())
}
}
fn main() -> Result<()> {
tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.init();
let cli = Cli::parse();
if matches!(cli.command, Some(Commands::Doctor)) {
return commands::doctor::run();
}
let mut ctx = Context::load()?;
match cli.command {
None => tui::launch(ctx.config, ctx.store),
Some(Commands::New { title, notebook }) => {
let notebook = ctx.notebook_name(notebook);
commands::new::run(&ctx.store, ¬ebook, &title, &ctx.config.general.editor)
}
Some(Commands::List { notebook }) => {
let notebook = ctx.notebook_name(notebook);
commands::list::run(&ctx.store, ¬ebook)
}
Some(Commands::Edit { note, notebook }) => {
let notebook = ctx.notebook_name(notebook);
commands::edit::run(&ctx.store, ¬ebook, ¬e, &ctx.config.general.editor)
}
Some(Commands::Show { note, notebook }) => {
let notebook = ctx.notebook_name(notebook);
commands::show::run(&ctx.store, ¬ebook, ¬e)
}
Some(Commands::Search { query, notebook }) => {
let notebook = ctx.notebook_name(notebook);
commands::search::run(&ctx.store, ¬ebook, &query)
}
Some(Commands::Daily { notebook }) => {
let notebook = ctx.notebook_name(notebook);
let templates_dir = Config::default_templates_dir()?;
commands::daily::run(
&ctx.store,
¬ebook,
&templates_dir,
&ctx.config.general.editor,
)
}
Some(Commands::Sync { notebook }) => {
let notebook = ctx.notebook_name(notebook);
commands::sync::run(&ctx.store, ¬ebook, &ctx.config.git)
}
Some(Commands::Config) => commands::config::run(),
Some(Commands::Doctor) => unreachable!("handled before Context::load() above"),
Some(Commands::Notebook { action }) => match action {
NotebookAction::Create { name } => commands::notebook::create(&ctx.store, &name),
NotebookAction::List => commands::notebook::list(&ctx.store),
NotebookAction::Rename { old, new } => {
commands::notebook::rename(&ctx.store, &old, &new)
}
},
Some(Commands::Theme { action }) => match action {
ThemeAction::List => commands::theme::list(&ctx.config),
ThemeAction::Set { name } => commands::theme::set(&mut ctx.config, &name),
},
}
}