use ansi_term::Colour::{Green, Red, Yellow};
use clap::{ArgEnum, Parser};
use std::io::Read;
mod display;
mod filter;
mod parse;
use display::{dotted, escape_string, show_as, spaced, ShowAs};
use filter::{is_selected, SelectQuery};
use parse::{try_parse_entries, EntryValue, ParseConfig};
#[derive(Parser)]
#[clap(author, about, version, long_about = None)]
struct Args {
#[clap(arg_enum, short, long, default_value = "dot")]
indent: IndentStyle,
#[clap(long)]
no_fixed: bool,
#[clap(long)]
no_fixed64: bool,
#[clap(long)]
no_fixed32: bool,
#[clap(long)]
full: bool,
#[clap()]
select: Option<String>,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ArgEnum)]
enum IndentStyle {
Space,
Dot,
Path,
}
#[derive(Clone)]
struct Config {
pub indent: IndentStyle,
pub select: SelectQuery,
pub full: bool,
pub parse_config: ParseConfig,
}
fn main() {
let args = Args::parse();
let mut input = Vec::<u8>::new();
for byte in std::io::stdin().bytes() {
input.push(byte.unwrap());
}
let config = Config {
indent: args.indent,
select: SelectQuery::parse(&args.select.unwrap_or_default()).unwrap(),
full: args.full,
parse_config: ParseConfig {
no_fixed64: args.no_fixed || args.no_fixed64,
no_fixed32: args.no_fixed || args.no_fixed32,
},
};
decode(&input, &config);
}
fn decode(bytes: &[u8], config: &Config) {
if let Some(entries) = try_parse_entries(bytes, config.parse_config) {
for entry in entries
.into_iter()
.filter(|e| is_selected(e, &config.select))
{
let stripped_path = entry.path[config.select.len()..].to_vec();
let path = print_path(&stripped_path, config);
match entry.value {
EntryValue::Fixed64(v) => println!("{}: (64 bit) {}", path, print_fixed64(v)),
EntryValue::Fixed32(v) => println!("{}: (32 bit) {}", path, print_fixed32(v)),
EntryValue::Varint(i) => println!("{}: {}", path, print_int(i)),
EntryValue::Bytes(v) => {
print!(
"{}: ({} bytes) {}\n",
path,
v.len(),
print_bytes(&v, config.full)
)
}
EntryValue::OpenNested => {
if !path.is_empty() {
print!("{} {{\n", path);
}
}
EntryValue::CloseNested => {
if !path.is_empty() {
print!("{}}}\n", dotted((path.chars().count() - 1) / 2));
}
}
}
}
} else {
panic!("Input bytes is not a valid protobuf serialization");
}
}
fn yellow(s: impl ToString) -> String {
Yellow.paint(s.to_string()).to_string()
}
fn print_fixed64(v: [u8; 8]) -> String {
let as_unsigned = u64::from_le_bytes(v);
let as_signed = i64::from_le_bytes(v);
let as_float = f64::from_le_bytes(v);
let mut values = Vec::<String>::new();
values.push(yellow(as_unsigned));
if as_signed < 0 {
values.push(yellow(as_signed));
}
values.push(yellow(as_float));
values.join(" / ")
}
fn print_fixed32(v: [u8; 4]) -> String {
let as_unsigned = u32::from_le_bytes(v);
let as_signed = i32::from_le_bytes(v);
let as_float = f32::from_le_bytes(v);
let mut values = Vec::<String>::new();
values.push(yellow(as_unsigned));
if as_signed < 0 {
values.push(yellow(as_signed));
}
values.push(yellow(as_float));
values.join(" / ")
}
fn print_int(i: impl Into<u128>) -> String {
Red.paint(i.into().to_string()).to_string()
}
fn print_bytes(bytes: &[u8], full: bool) -> String {
let text = match show_as(bytes) {
ShowAs::String(s) => escape_string(s),
ShowAs::Bytes(bytes) => {
const MAX_BYTES: usize = 256;
if full || bytes.len() <= MAX_BYTES {
hex::encode(bytes)
} else {
let mut truncated = hex::encode(&bytes[0..MAX_BYTES]);
truncated.push('…');
truncated
}
}
};
Green.paint(text).to_string()
}
fn print_path(path: &[u64], config: &Config) -> String {
match config.indent {
IndentStyle::Dot => {
let mut out = dotted(path.len().saturating_sub(1));
if let Some(last) = path.last() {
out.push_str(&format!("{}", last));
}
out
}
IndentStyle::Space => {
let mut out = spaced(path.len().saturating_sub(1));
if let Some(last) = path.last() {
out.push_str(&format!("{}", last));
}
out
}
IndentStyle::Path => {
let formated_path: String = path.iter().map(|number| format!(".{}", number)).collect();
formated_path
}
}
}