aboutsummaryrefslogtreecommitdiff
path: root/src/main.rs
diff options
context:
space:
mode:
authorAlex Auvolat <alex@adnab.me>2021-11-03 23:26:37 +0100
committerAlex Auvolat <alex@adnab.me>2021-11-03 23:26:37 +0100
commit5160a7637b60dc45cc93836b4749a8b929b183de (patch)
tree0b1613d359334cae5f8de40b295b50331dd69892 /src/main.rs
downloadsledcli-5160a7637b60dc45cc93836b4749a8b929b183de.tar.gz
sledcli-5160a7637b60dc45cc93836b4749a8b929b183de.zip
First commit
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs162
1 files changed, 162 insertions, 0 deletions
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..a2de0e1
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,162 @@
+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));
+ }
+ }
+ }
+}