use std::str::FromStr;
use anyhow::{Context, Result};
use clap::{Arg, ArgGroup, ArgMatches, Command};
use itertools::Itertools;
use prettytable::color::Color;
use prettytable::format;
use prettytable::{color, Attr, Cell, Row, Table};
use daml::lf::{DamlLfPackage, DarFile};
use crate::DarnCommand;
pub struct CommandIntern {}
impl DarnCommand for CommandIntern {
fn name(&self) -> &str {
"intern"
}
fn args<'a>(&self) -> Command<'a> {
Command::new("intern")
.about("Show interned strings and dotted names in a dar")
.arg(Arg::new("dar").help("Sets the input dar file to use").required(true).index(1))
.arg(Arg::new("string").short('s').long("string").help("Show interned strings"))
.arg(Arg::new("dotted").short('d').long("dotted").help("Show interned dotted names"))
.arg(
Arg::new("index")
.short('i')
.long("index")
.multiple_occurrences(true)
.use_value_delimiter(true)
.takes_value(true)
.required(false)
.help("the intern indices"),
)
.arg(Arg::new("show-mangled").short('f').long("show-mangled").required(false).help("show mangled names"))
.arg(Arg::new("order-by-index").required(false).long("order-by-index").help("order by index"))
.arg(Arg::new("order-by-name").required(false).long("order-by-name").help("order by name"))
.group(ArgGroup::new("mode").required(true).arg("string").arg("dotted"))
.group(ArgGroup::new("order").required(false).arg("order-by-index").arg("order-by-name"))
}
fn execute(&self, matches: &ArgMatches) -> Result<()> {
let dar_path = matches.value_of("dar").unwrap();
let filter: Vec<usize> = matches
.values_of("index")
.unwrap_or_default()
.map(|i| usize::from_str(i).context(format!("parsing index from '{}'", i)))
.collect::<Result<Vec<_>>>()?;
let show_mangled = matches.is_present("show-mangled");
let sort = match (matches.is_present("order-by-index"), matches.is_present("order-by-name")) {
(true, false) => SortOrder::ByIndex,
_ => SortOrder::ByName,
};
if matches.is_present("dotted") {
intern_dotted(dar_path, show_mangled, &sort, filter.as_slice())
} else if matches.is_present("string") {
intern_string(dar_path, show_mangled, &sort, filter.as_slice())
} else {
unreachable!()
}
}
}
enum SortOrder {
ByIndex,
ByName,
}
fn intern_string(dar_path: &str, show_mangled: bool, sort_order: &SortOrder, filter: &[usize]) -> Result<()> {
let dar = DarFile::from_file(dar_path)?;
match dar.main.payload.package {
DamlLfPackage::V1(package) => {
let mut res: Vec<_> = package
.interned_strings
.iter()
.enumerate()
.filter_map(|(idx, rendered)| {
if (filter.is_empty() || filter.contains(&idx)) && (show_mangled || !rendered.contains('$')) {
Some((idx, rendered))
} else {
None
}
})
.collect();
if res.is_empty() {
println!("no interned strings matched indices {}", filter.iter().join(", "));
return Ok(());
}
if let SortOrder::ByName = sort_order {
res.sort_by(|(_, rendered_a), (_, rendered_b)| rendered_a.cmp(rendered_b));
} else {
res.sort_by(|(index_a, _), (index_b, _)| index_a.cmp(index_b));
}
let mut table = Table::new();
table.set_titles(Row::new(vec!["index", "rendered"].into_iter().map(Cell::new).collect()));
table.set_format(*format::consts::FORMAT_NO_LINESEP_WITH_TITLE);
for (idx, rendered) in &res {
table.add_row(string_row(idx.to_string().as_str(), rendered, pick_color(rendered)));
}
table.printstd();
},
}
Ok(())
}
fn intern_dotted(dar_path: &str, show_mangled: bool, sort_order: &SortOrder, filter: &[usize]) -> Result<()> {
let dar = DarFile::from_file(dar_path)?;
match dar.main.payload.package {
DamlLfPackage::V1(package) => {
let mut res: Vec<_> = package
.interned_dotted_names
.iter()
.enumerate()
.filter_map(|(idx, dt)| {
if filter.is_empty() || filter.contains(&idx) {
let segments = dt
.segments_interned_str
.iter()
.map(|&i| format!("{}({})", package.interned_strings[i as usize], i))
.join(".");
let rendered =
dt.segments_interned_str.iter().map(|&i| &package.interned_strings[i as usize]).join(".");
if show_mangled || !rendered.contains('$') {
Some((idx, rendered, segments))
} else {
None
}
} else {
None
}
})
.collect();
if res.is_empty() {
println!("no interned dotted names matched indices {}", filter.iter().join(", "));
return Ok(());
}
if let SortOrder::ByName = sort_order {
res.sort_by(|(_, rendered_a, _), (_, rendered_b, _)| rendered_a.cmp(rendered_b));
} else {
res.sort_by(|(index_a, ..), (index_b, ..)| index_a.cmp(index_b));
}
let mut table = Table::new();
table.set_titles(Row::new(vec!["index", "rendered", "segments"].into_iter().map(Cell::new).collect()));
table.set_format(*format::consts::FORMAT_NO_LINESEP_WITH_TITLE);
for (idx, rendered, segments) in &res {
table.add_row(dotted_row(idx.to_string().as_str(), rendered, segments, pick_color(rendered)));
}
table.printstd();
},
}
Ok(())
}
fn string_row(idx: &str, rendered: &str, color: color::Color) -> Row {
Row::new(vec![cell(idx, color), cell(rendered, color)])
}
fn dotted_row(idx: &str, rendered: &str, segments: &str, color: color::Color) -> Row {
Row::new(vec![cell(idx, color), cell(rendered, color), cell(segments, color)])
}
fn cell(data: &str, color: color::Color) -> Cell {
Cell::new(data).with_style(Attr::Bold).with_style(Attr::ForegroundColor(color))
}
fn pick_color(data: &str) -> Color {
if data.contains('$') {
color::BLUE
} else {
color::WHITE
}
}