214 lines
4.3 KiB
Rust
214 lines
4.3 KiB
Rust
use std::ops::Deref;
|
|
use std::path::PathBuf;
|
|
|
|
use anyhow::Result;
|
|
|
|
use humansize::{file_size_opts, FileSize};
|
|
|
|
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 prefix = match state.displaymode {
|
|
DisplayMode::HexDump => "hex",
|
|
DisplayMode::TryString => "str",
|
|
DisplayMode::Mixed => "mix",
|
|
};
|
|
let prompt = format!("[{}] {}> ", prefix, try_string(&state.tree.name()));
|
|
let lineread = readline.readline(&prompt);
|
|
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));
|
|
}
|
|
}
|
|
["ll"] => {
|
|
let mut names = state.db.tree_names();
|
|
names.sort();
|
|
let mut total = 0;
|
|
for name in names {
|
|
let nent = state.db.open_tree(&name)?.len();
|
|
total += nent;
|
|
println!("{:8} {}", nent, try_string(&name));
|
|
}
|
|
println!("{:8} TOTAL", total);
|
|
}
|
|
["lu"] => {
|
|
let mut names = state.db.tree_names();
|
|
names.sort();
|
|
let mut total_nent = 0;
|
|
let mut total_size = 0;
|
|
for name in names {
|
|
let tree = state.db.open_tree(&name)?;
|
|
let nent = tree.len();
|
|
let mut size = 0;
|
|
for ent in tree.iter() {
|
|
let (k, v) = ent?;
|
|
size += k.len() + v.len();
|
|
}
|
|
total_nent += nent;
|
|
total_size += size;
|
|
println!(
|
|
"{:8} {:>12} {}",
|
|
nent,
|
|
size.file_size(file_size_opts::CONVENTIONAL).unwrap(),
|
|
try_string(&name)
|
|
);
|
|
}
|
|
println!("{:8} {:>12} TOTAL",
|
|
total_nent,
|
|
total_size.file_size(file_size_opts::CONVENTIONAL).unwrap());
|
|
}
|
|
["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));
|
|
}
|
|
}
|
|
}
|
|
}
|