aboutsummaryrefslogblamecommitdiff
path: root/src/main.rs
blob: a2de0e101d0ae779c55596bb42eda40fc925fb21 (plain) (tree)

































































































































































                                                                                           
use std::path::PathBuf;
use std::ops::Deref;

use anyhow::Result;

use rustyline::error::ReadlineError;
use rustyline::Editor;

use structopt::StructOpt;

use sled::IVec;

#[derive(StructOpt, Debug)]
#[structopt(name = "sledcli")]
struct Opt {
	/// Path to Sled database
	#[structopt(name="path")]
	path: PathBuf,
}

enum DisplayMode {
	TryString,
	HexDump,
	Mixed,
}

struct State {
	db: sled::Db,
	tree: sled::Tree,
	displaymode: DisplayMode,
}

fn main() {
	let opt = Opt::from_args();
	let db = sled::Config::default()
		.path(&opt.path)
		.open()
		.expect("Unable to open database");

	let tree: sled::Tree = db.deref().clone();
	let mut state = State {
		db,
		tree,
		displaymode: DisplayMode::Mixed,
	};

	let mut readline = Editor::<()>::new();
    loop {
        let lineread = readline.readline(&format!("{}> ", try_string(&state.tree.name())));
        match lineread {
            Ok(line) => {
                readline.add_history_entry(line.as_str());
				if let Err(e) = do_command(&line, &mut state) {
					println!("Error: {}", e);
				}
            },
            Err(ReadlineError::Interrupted) => {
				println!("^C");
				continue;
            },
            Err(ReadlineError::Eof) => {
                break
            },
            Err(err) => {
                println!("Readline error: {:?}", err);
                break
            }
        }
    }
}

fn try_string(input: &sled::IVec) -> String {
    let mut string = String::new();
	utf8::LossyDecoder::new(|s| string.push_str(s)).feed(input);
	string
}

fn do_command(line: &str, state: &mut State) -> Result<()> {
	let parts = line.split(' ').filter(|part| part.len() > 0).collect::<Vec<_>>();
	if parts.is_empty() {
		return Ok(());
	}

	match &parts[..] {
		["ls"] => {
			let mut names = state.db.tree_names();
			names.sort();
			for name in names {
				println!("{}", try_string(&name));
			}
		}
		["cd", treename] => {
			if state.db.tree_names().iter().any(|t| t == treename.as_bytes()) {
				state.tree = state.db.open_tree(treename.as_bytes())?;
			} else {
				println!("Tree {} does not exist", treename);
			}
		}
		["hex"] => {
			state.displaymode = DisplayMode::HexDump;
		}
		["str"] => {
			state.displaymode = DisplayMode::TryString;
		}
		["mix"] => {
			state.displaymode = DisplayMode::Mixed;
		}
		["keys"] => {
			for (i, pair) in state.tree.iter().enumerate() {
				if i >= 20 {
					println!("...");
					break;
				}
				let (k, _v) = pair?;
				state.displaymode.print_key(&k);
			}
		}
		["pairs"] => {
			for (i, pair) in state.tree.iter().enumerate() {
				if i >= 20 {
					println!("...");
					break;
				}
				let (k, v) = pair?;
				state.displaymode.print_pair(&k, &v);
			}
		}
		bad_cmd => println!("Unrecognized command: {:?}", bad_cmd),
	}
	Ok(())
}

impl DisplayMode {
	fn print_key(&self, k: &IVec) {
		match *self {
			DisplayMode::HexDump => {
				hexdump::hexdump(k);
			}
			_ => {
				println!("{}", try_string(k));
			}
		}
	}

	fn print_pair(&self, k: &IVec, v: &IVec) {
		match *self {
			DisplayMode::HexDump => {
				hexdump::hexdump(k);
				hexdump::hexdump(v);
				println!();
			}
			DisplayMode::Mixed => {
				println!("{}", try_string(k));
				hexdump::hexdump(v);
				println!();
			}
			DisplayMode::TryString => {
				println!("{}\t{}", try_string(k), try_string(v));
			}
		}
	}
}