base: Improve the state store example so it can run non-interactively

master
Damir Jelić 2021-01-02 12:58:52 +01:00
parent b995492457
commit 16f94ecc1d
4 changed files with 118 additions and 66 deletions

View File

@ -53,6 +53,7 @@ tempfile = "3.1.0"
mockito = "0.27.0" mockito = "0.27.0"
rustyline = "7.0.0" rustyline = "7.0.0"
rustyline-derive = "0.4.0" rustyline-derive = "0.4.0"
atty = "0.2"
clap = "2.33.3" clap = "2.33.3"
syntect = "4.4.0" syntect = "4.4.0"

View File

@ -1,7 +1,10 @@
use std::{convert::TryFrom, env, io, process::exit, sync::Arc}; use std::{convert::TryFrom, fmt::Debug, io, sync::Arc};
use clap::{App as Argparse, AppSettings as ArgParseSettings, Arg, ArgMatches, SubCommand};
use futures::{executor::block_on, StreamExt}; use futures::{executor::block_on, StreamExt};
use serde::Serialize;
use atty::Stream;
use clap::{App as Argparse, AppSettings as ArgParseSettings, Arg, ArgMatches, SubCommand};
use rustyline::{ use rustyline::{
completion::{Completer, Pair}, completion::{Completer, Pair},
error::ReadlineError, error::ReadlineError,
@ -13,6 +16,7 @@ use rustyline::{
use rustyline_derive::Helper; use rustyline_derive::Helper;
use syntect::{ use syntect::{
dumps::from_binary,
easy::HighlightLines, easy::HighlightLines,
highlighting::{Style, ThemeSet}, highlighting::{Style, ThemeSet},
parsing::SyntaxSet, parsing::SyntaxSet,
@ -190,37 +194,60 @@ impl Validator for InspectorHelper {}
struct Printer { struct Printer {
ps: Arc<SyntaxSet>, ps: Arc<SyntaxSet>,
ts: Arc<ThemeSet>, ts: Arc<ThemeSet>,
json: bool,
color: bool,
} }
impl Printer { impl Printer {
fn new() -> Self { fn new(json: bool, color: bool) -> Self {
let syntax_set: SyntaxSet = from_binary(include_bytes!("./syntaxes.bin"));
let themes: ThemeSet = from_binary(include_bytes!("./themes.bin"));
Self { Self {
ps: SyntaxSet::load_defaults_newlines().into(), ps: syntax_set.into(),
ts: ThemeSet::load_from_folder("/home/poljar/local/cfg/bat/themes") ts: themes.into(),
.expect("Couldn't load themes") json,
.into(), color,
} }
} }
fn pretty_print_struct(&self, data: &impl std::fmt::Debug) { fn pretty_print_struct<T: Debug + Serialize>(&self, data: &T) {
let data = format!("{:#?}", data); let data = if self.json {
serde_json::to_string_pretty(data).expect("Can't serialize struct")
} else {
format!("{:#?}", data)
};
let syntax = self.ps.find_syntax_by_extension("rs").unwrap(); let syntax = if self.json {
let mut h = HighlightLines::new(syntax, &self.ts.themes["Forest Night"]); self.ps
.find_syntax_by_extension("rs")
.expect("Can't find rust syntax extension")
} else {
self.ps
.find_syntax_by_extension("json")
.expect("Can't find json syntax extension")
};
for line in LinesWithEndings::from(&data) { if self.color {
let ranges: Vec<(Style, &str)> = h.highlight(line, &self.ps); let mut h = HighlightLines::new(syntax, &self.ts.themes["Forest Night"]);
let escaped = as_24_bit_terminal_escaped(&ranges[..], false);
print!("{}", escaped); for line in LinesWithEndings::from(&data) {
let ranges: Vec<(Style, &str)> = h.highlight(line, &self.ps);
let escaped = as_24_bit_terminal_escaped(&ranges[..], false);
print!("{}", escaped);
}
// Clear the formatting
println!("\x1b[0m");
} else {
println!("{}", data);
} }
// Clear the formatting
println!("\x1b[0m");
} }
} }
impl Inspector { impl Inspector {
fn new(database_path: &str) -> Self { fn new(database_path: &str, json: bool, color: bool) -> Self {
let printer = Printer::new(); let printer = Printer::new(json, color);
let store = Store::open_default(database_path); let store = Store::open_default(database_path);
Self { store, printer } Self { store, printer }
@ -290,6 +317,37 @@ impl Inspector {
.pretty_print_struct(&self.store.get_state_event(&room_id, event_type, "").await); .pretty_print_struct(&self.store.get_state_event(&room_id, event_type, "").await);
} }
fn subcommands() -> Vec<Argparse<'static, 'static>> {
vec![
SubCommand::with_name("list-rooms"),
SubCommand::with_name("get-members").arg(
Arg::with_name("room-id").required(true).validator(|r| {
RoomId::try_from(r)
.map(|_| ())
.map_err(|_| "Invalid room id given".to_owned())
}),
),
SubCommand::with_name("get-profiles").arg(
Arg::with_name("room-id").required(true).validator(|r| {
RoomId::try_from(r)
.map(|_| ())
.map_err(|_| "Invalid room id given".to_owned())
}),
),
SubCommand::with_name("get-state")
.arg(Arg::with_name("room-id").required(true).validator(|r| {
RoomId::try_from(r)
.map(|_| ())
.map_err(|_| "Invalid room id given".to_owned())
}))
.arg(Arg::with_name("event-type").required(true).validator(|e| {
EventType::try_from(e)
.map(|_| ())
.map_err(|_| "Invalid event type".to_string())
})),
]
}
async fn parse_and_run(&self, input: &str) { async fn parse_and_run(&self, input: &str) {
let argparse = Argparse::new("state-inspector") let argparse = Argparse::new("state-inspector")
.global_setting(ArgParseSettings::DisableHelpFlags) .global_setting(ArgParseSettings::DisableHelpFlags)
@ -297,34 +355,7 @@ impl Inspector {
.global_setting(ArgParseSettings::VersionlessSubcommands) .global_setting(ArgParseSettings::VersionlessSubcommands)
.global_setting(ArgParseSettings::NoBinaryName) .global_setting(ArgParseSettings::NoBinaryName)
.setting(ArgParseSettings::SubcommandRequiredElseHelp) .setting(ArgParseSettings::SubcommandRequiredElseHelp)
.subcommand(SubCommand::with_name("list-rooms")) .subcommands(Inspector::subcommands());
.subcommand(SubCommand::with_name("get-members").arg(
Arg::with_name("room-id").required(true).validator(|r| {
RoomId::try_from(r)
.map(|_| ())
.map_err(|_| "Invalid room id given".to_owned())
}),
))
.subcommand(SubCommand::with_name("get-profiles").arg(
Arg::with_name("room-id").required(true).validator(|r| {
RoomId::try_from(r)
.map(|_| ())
.map_err(|_| "Invalid room id given".to_owned())
}),
))
.subcommand(
SubCommand::with_name("get-state")
.arg(Arg::with_name("room-id").required(true).validator(|r| {
RoomId::try_from(r)
.map(|_| ())
.map_err(|_| "Invalid room id given".to_owned())
}))
.arg(Arg::with_name("event-type").required(true).validator(|e| {
EventType::try_from(e)
.map(|_| ())
.map_err(|_| "Invalid event type".to_string())
})),
);
match argparse.get_matches_from_safe(input.split_ascii_whitespace()) { match argparse.get_matches_from_safe(input.split_ascii_whitespace()) {
Ok(m) => { Ok(m) => {
@ -338,31 +369,51 @@ impl Inspector {
} }
fn main() -> io::Result<()> { fn main() -> io::Result<()> {
let database_path = match env::args().nth(1) { let argparse = Argparse::new("state-inspector")
Some(a) => a, .global_setting(ArgParseSettings::DisableVersion)
_ => { .global_setting(ArgParseSettings::VersionlessSubcommands)
eprintln!("Usage: {} <database_path>", env::args().next().unwrap()); .arg(Arg::with_name("database").required(true))
exit(1) .arg(
} Arg::with_name("json")
.long("json")
.help("set the output to raw json instead of Rust structs")
.global(true)
.takes_value(false),
)
.subcommands(Inspector::subcommands());
let matches = argparse.get_matches();
let database_path = matches.args.get("database").expect("No database path");
let json = matches.is_present("json");
let color = if atty::is(Stream::Stdout) {
true
} else {
false
}; };
let inspector = Inspector::new(&database_path); let inspector = Inspector::new(&database_path.vals[0].to_string_lossy(), json, color);
let config = Config::builder() if matches.subcommand.is_none() {
.history_ignore_space(true) let config = Config::builder()
.completion_type(CompletionType::List) .history_ignore_space(true)
.edit_mode(EditMode::Emacs) .completion_type(CompletionType::List)
.output_stream(OutputStreamType::Stdout) .edit_mode(EditMode::Emacs)
.build(); .output_stream(OutputStreamType::Stdout)
.build();
let helper = InspectorHelper::new(inspector.store.clone()); let helper = InspectorHelper::new(inspector.store.clone());
let mut rl = Editor::<InspectorHelper>::with_config(config); let mut rl = Editor::<InspectorHelper>::with_config(config);
rl.set_helper(Some(helper)); rl.set_helper(Some(helper));
while let Ok(input) = rl.readline(">> ") { while let Ok(input) = rl.readline(">> ") {
rl.add_history_entry(input.as_str()); rl.add_history_entry(input.as_str());
block_on(inspector.parse_and_run(input.as_str())); block_on(inspector.parse_and_run(input.as_str()));
}
} else {
block_on(inspector.run(matches));
} }
Ok(()) Ok(())

Binary file not shown.

Binary file not shown.