#![feature(let_else)]
use std::path::PathBuf;
use anyhow::{bail, Context};
use clap::{Parser, Subcommand};
use otkeep::AppContext;
#[derive(Parser)]
#[clap(about, version)]
struct Args {
#[clap(subcommand)]
subcommand: Option<Sub>,
}
#[derive(Subcommand)]
enum Sub {
Add {
name: String,
script: String,
#[clap(short = 'i', long = "inline")]
inline: bool,
},
Mod {
name: String,
desc: Option<String>,
},
Remove {
name: String,
},
Establish,
Unestablish,
ListTrees,
Checkout {
name: String,
},
Cat {
name: String,
},
Update {
name: String,
script: String,
#[clap(short = 'i', long = "inline")]
inline: bool,
},
Rename {
current: String,
new: String,
},
Save {
path: String,
},
Restore {
path: Option<String>,
},
Clone {
tree: PathBuf,
},
}
fn main() -> anyhow::Result<()> {
let db = otkeep::load_db()?;
let opt_root = otkeep::find_root(&db)?;
let Some(subcommand) = Args::parse().subcommand else {
match opt_root {
Some(root) => {
let ctx = &AppContext {
db,
root_id: root.0,
};
otkeep::list_scripts(ctx)?;
println!();
otkeep::list_files(ctx)?;
help_msg();
return Ok(());
}
None => {
eprintln!("The following trees are available:");
cmd::list_trees(&db)?;
help_msg();
return Ok(());
}
}
};
match subcommand {
Sub::ListTrees => {
cmd::list_trees(&db)?;
return Ok(());
}
Sub::Establish => {
cmd::establish(&db).context("Failed to establish OtKeep root")?;
eprintln!("Established {}", std::env::current_dir()?.display());
return Ok(());
}
_ => {}
}
let (root_id, root_path) = match opt_root {
Some(root) => root,
None => {
otkeep::print_established_trees(&db)?;
bail!("No OtKeep tree root was found. To establish one, use okeep establish");
}
};
let mut app = AppContext { db, root_id };
match subcommand {
Sub::Add {
name,
script,
inline,
} => cmd::add(&mut app, &name, &script, inline).context("Failed to add script")?,
Sub::Mod { name, desc } => {
cmd::mod_(&mut app, &name, desc.as_deref()).context("Mod failed")?
}
Sub::Remove { name } => cmd::remove(&mut app, &name).context("Failed to remove script")?,
Sub::Establish => unreachable!(),
Sub::Unestablish => {
if std::env::current_dir()? != root_path {
eprintln!("The current directory is not the root.");
eprintln!("Go to {}", root_path.display());
eprintln!("Then run this command again if you really want to unestablish");
return Ok(());
}
cmd::unestablish(&mut app).context("Failed to unestablish current directory")?;
eprintln!("Unestablished {}", root_path.display());
}
Sub::ListTrees => unreachable!(),
Sub::Checkout { name } => cmd::checkout(&mut app, &name).context("Checkout failed")?,
Sub::Cat { name } => cmd::cat(&mut app, &name).context("Cat failed")?,
Sub::Update {
name,
script,
inline,
} => cmd::update(&mut app, &name, &script, inline).context("Update failed")?,
Sub::Rename { current, new } => {
cmd::rename(&mut app, ¤t, &new).context("Failed to rename script")?
}
Sub::Save { path } => cmd::save(&mut app, &path).context("File save failed")?,
Sub::Restore { path } => {
cmd::restore(&mut app, path.as_deref()).context("File restore failed")?
}
Sub::Clone { tree } => cmd::clone(&mut app, &tree)?,
}
Ok(())
}
fn help_msg() {
eprintln!("\nType okeep --help for help.");
}
mod cmd {
use std::path::Path;
use anyhow::{bail, Context};
use otkeep::{database::Database, AppContext};
pub(crate) fn add(
ctx: &mut AppContext,
name: &str,
script: &str,
inline: bool,
) -> anyhow::Result<()> {
let curr_dir = std::env::current_dir()?;
let script_body = if inline {
script.as_bytes().to_vec()
} else {
let absolute_path = std::fs::canonicalize(curr_dir.join(script))?;
std::fs::read(&absolute_path)?
};
ctx.db.add_script(ctx.root_id, name, script_body)?;
Ok(())
}
pub fn establish(db: &Database) -> anyhow::Result<()> {
let current_dir = std::env::current_dir()?;
match db.query_tree(¤t_dir)? {
None => db.add_new_tree(¤t_dir)?,
Some(_) => bail!("There is already a OtKeep tree root here."),
}
Ok(())
}
pub fn unestablish(ctx: &mut AppContext) -> anyhow::Result<()> {
ctx.db.remove_tree(ctx.root_id)
}
pub fn mod_(ctx: &mut AppContext, name: &str, desc: Option<&str>) -> anyhow::Result<()> {
let mut modded = false;
if let Some(description) = desc {
ctx.db
.add_script_description(ctx.root_id, name, description)?;
eprintln!("{} => {}", name, description);
modded = true;
}
if !modded {
eprintln!("No modification option given, did nothing.");
}
Ok(())
}
pub fn remove(ctx: &mut AppContext, name: &str) -> anyhow::Result<()> {
if ctx.db.remove_script(ctx.root_id, name)? {
eprintln!("Removed script '{}'", name);
} else {
eprintln!("Didn't remove anything. '{}' probably doesn't exist.", name);
}
Ok(())
}
pub fn list_trees(db: &Database) -> anyhow::Result<()> {
let mut any = false;
for root_path in db.get_tree_roots()? {
eprintln!("{}", root_path.display());
any = true;
}
if !any {
eprintln!("Looks like no trees have been added yet.");
eprintln!("Find a tree you'd like to add and type `okeep establish`.");
}
Ok(())
}
pub fn checkout(ctx: &mut AppContext, name: &str) -> anyhow::Result<()> {
otkeep::checkout(name, ctx)?;
Ok(())
}
pub fn cat(ctx: &mut AppContext, name: &str) -> anyhow::Result<()> {
otkeep::cat(name, ctx)?;
Ok(())
}
pub fn update(
ctx: &mut AppContext,
name: &str,
script: &str,
inline: bool,
) -> anyhow::Result<()> {
let curr_dir = std::env::current_dir()?;
let script_body = if inline {
script.as_bytes().to_vec()
} else {
let absolute_path = std::fs::canonicalize(curr_dir.join(script))?;
std::fs::read(&absolute_path)?
};
ctx.db.update_script(ctx.root_id, name, script_body)?;
Ok(())
}
pub(crate) fn rename(ctx: &mut AppContext, current: &str, new: &str) -> anyhow::Result<()> {
otkeep::rename_script(current, new, ctx)?;
Ok(())
}
pub(crate) fn save(app: &mut AppContext, path: &str) -> anyhow::Result<()> {
let bytes = std::fs::read(path)?;
otkeep::add_file(app, path, bytes)?;
Ok(())
}
pub(crate) fn restore(app: &mut AppContext, path: Option<&str>) -> anyhow::Result<()> {
let path = match path {
Some(path) => path,
None => {
otkeep::list_files(app)?;
return Ok(());
}
};
let bytes = otkeep::get_file(app, path)?;
std::fs::write(path, bytes)?;
Ok(())
}
pub(crate) fn clone(app: &mut AppContext, tree: &Path) -> anyhow::Result<()> {
let dst = app.root_id;
let src = app.db.query_tree(tree)?.context("Missing tree")?;
app.db.clone_tree(src, dst)?;
Ok(())
}
}