mirror of
https://github.com/widberg/bff
synced 2026-08-23 14:26:03 -04:00
Add diff subcommand
This commit is contained in:
parent
23e91ae641
commit
4d288c662a
2 changed files with 188 additions and 0 deletions
173
bff-cli/src/diff.rs
Normal file
173
bff-cli/src/diff.rs
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
use std::collections::BTreeMap;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use bff::BufReader;
|
||||
use bff::bigfile::BigFile;
|
||||
use bff::bigfile::resource::Resource;
|
||||
use bff::names::NameContext;
|
||||
|
||||
use crate::error::BffCliResult;
|
||||
use crate::extract::{read_bigfile, read_bigfile_names};
|
||||
|
||||
struct ResolvedResource<'a> {
|
||||
link_name: Option<String>,
|
||||
resource: &'a Resource,
|
||||
}
|
||||
|
||||
fn read_name_file(name_path: &Path, name_context: &NameContext) -> BffCliResult<()> {
|
||||
let f = File::open(name_path)?;
|
||||
let mut reader = BufReader::new(f);
|
||||
name_context.read(&mut reader)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_bigfile(
|
||||
bigfile_path: &Path,
|
||||
name_path: &Option<PathBuf>,
|
||||
) -> BffCliResult<(BigFile, NameContext)> {
|
||||
let name_context = NameContext::default();
|
||||
read_bigfile_names(bigfile_path, &name_context)?;
|
||||
if let Some(name_path) = name_path {
|
||||
read_name_file(name_path, &name_context)?;
|
||||
}
|
||||
let bigfile = read_bigfile(bigfile_path, &None, &None, &name_context)?;
|
||||
Ok((bigfile, name_context))
|
||||
}
|
||||
|
||||
fn resolve_resources<'a>(
|
||||
bigfile: &'a BigFile,
|
||||
name_context: &NameContext,
|
||||
side: &'static str,
|
||||
) -> BffCliResult<BTreeMap<String, ResolvedResource<'a>>> {
|
||||
let mut resources = BTreeMap::new();
|
||||
|
||||
for resource in bigfile.resources.values() {
|
||||
let resource_name = resource.name.with_context(name_context).to_string();
|
||||
let class_name = resource.class_name.with_context(name_context).to_string();
|
||||
let full_name = format!("{resource_name}.{class_name}");
|
||||
let resolved_resource = ResolvedResource {
|
||||
link_name: resource
|
||||
.link_name
|
||||
.as_ref()
|
||||
.map(|name| name.with_context(name_context).to_string()),
|
||||
resource,
|
||||
};
|
||||
|
||||
assert!(
|
||||
resources
|
||||
.insert(full_name.clone(), resolved_resource)
|
||||
.is_none(),
|
||||
"duplicate resolved resource name {full_name} in {side}"
|
||||
);
|
||||
}
|
||||
|
||||
Ok(resources)
|
||||
}
|
||||
|
||||
fn display_link_name(link_name: &Option<String>) -> &str {
|
||||
link_name.as_deref().unwrap_or("<none>")
|
||||
}
|
||||
|
||||
fn describe_data_change(old_resource: &Resource, new_resource: &Resource) -> String {
|
||||
let old_size = old_resource.size();
|
||||
let new_size = new_resource.size();
|
||||
|
||||
if old_size == new_size {
|
||||
format!("data changed ({old_size} bytes)")
|
||||
} else {
|
||||
format!("data: {old_size} -> {new_size} bytes")
|
||||
}
|
||||
}
|
||||
|
||||
fn describe_changes(old_resource: &ResolvedResource<'_>, new_resource: &ResolvedResource<'_>) -> Vec<String> {
|
||||
let mut changes = Vec::new();
|
||||
|
||||
if old_resource.link_name != new_resource.link_name {
|
||||
changes.push(format!(
|
||||
"link: {} -> {}",
|
||||
display_link_name(&old_resource.link_name),
|
||||
display_link_name(&new_resource.link_name)
|
||||
));
|
||||
}
|
||||
|
||||
if old_resource.resource.data != new_resource.resource.data {
|
||||
changes.push(describe_data_change(
|
||||
old_resource.resource,
|
||||
new_resource.resource,
|
||||
));
|
||||
}
|
||||
|
||||
changes
|
||||
}
|
||||
|
||||
pub fn diff(
|
||||
old_bigfile_path: &Path,
|
||||
new_bigfile_path: &Path,
|
||||
old_name_path: &Option<PathBuf>,
|
||||
new_name_path: &Option<PathBuf>,
|
||||
) -> BffCliResult<()> {
|
||||
let (old_bigfile, old_name_context) = load_bigfile(old_bigfile_path, old_name_path)?;
|
||||
let (new_bigfile, new_name_context) = load_bigfile(new_bigfile_path, new_name_path)?;
|
||||
|
||||
let old_resources = resolve_resources(&old_bigfile, &old_name_context, "old BigFile")?;
|
||||
let new_resources = resolve_resources(&new_bigfile, &new_name_context, "new BigFile")?;
|
||||
|
||||
let added = new_resources
|
||||
.iter()
|
||||
.filter(|(name, _)| !old_resources.contains_key(*name))
|
||||
.collect::<Vec<_>>();
|
||||
let removed = old_resources
|
||||
.iter()
|
||||
.filter(|(name, _)| !new_resources.contains_key(*name))
|
||||
.collect::<Vec<_>>();
|
||||
let changed = old_resources
|
||||
.iter()
|
||||
.filter_map(|(name, old_resource)| {
|
||||
let new_resource = new_resources.get(name)?;
|
||||
let changes = describe_changes(old_resource, new_resource);
|
||||
(!changes.is_empty()).then_some((name, old_resource, new_resource, changes))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut stdout = std::io::stdout().lock();
|
||||
|
||||
if added.is_empty() && removed.is_empty() && changed.is_empty() {
|
||||
writeln!(stdout, "No differences found.")?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut wrote_section = false;
|
||||
|
||||
if !added.is_empty() {
|
||||
writeln!(stdout, "Added:")?;
|
||||
for (name, _) in &added {
|
||||
writeln!(stdout, " {name}")?;
|
||||
}
|
||||
wrote_section = true;
|
||||
}
|
||||
|
||||
if !removed.is_empty() {
|
||||
if wrote_section {
|
||||
writeln!(stdout)?;
|
||||
}
|
||||
writeln!(stdout, "Removed:")?;
|
||||
for (name, _) in &removed {
|
||||
writeln!(stdout, " {name}")?;
|
||||
}
|
||||
wrote_section = true;
|
||||
}
|
||||
|
||||
if !changed.is_empty() {
|
||||
if wrote_section {
|
||||
writeln!(stdout)?;
|
||||
}
|
||||
writeln!(stdout, "Changed:")?;
|
||||
for (name, _, _, changes) in &changed {
|
||||
writeln!(stdout, " {name} ({})", changes.join(", "))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ mod crc;
|
|||
mod create;
|
||||
mod create_resource;
|
||||
mod csc;
|
||||
mod diff;
|
||||
mod dump_json_schema;
|
||||
mod error;
|
||||
mod extract;
|
||||
|
|
@ -100,6 +101,14 @@ enum Commands {
|
|||
#[arg(long)]
|
||||
out_reference_graph: Option<PathBuf>,
|
||||
},
|
||||
Diff {
|
||||
old_bigfile: PathBuf,
|
||||
new_bigfile: PathBuf,
|
||||
#[arg(long)]
|
||||
old_names: Option<PathBuf>,
|
||||
#[arg(long)]
|
||||
new_names: Option<PathBuf>,
|
||||
},
|
||||
Names {
|
||||
bigfile: Option<PathBuf>,
|
||||
#[clap(value_enum)]
|
||||
|
|
@ -293,6 +302,12 @@ fn main() -> BffCliResult<()> {
|
|||
in_names,
|
||||
out_reference_graph: out_dependencies,
|
||||
} => info::info(bigfile, in_names, out_dependencies, &name_context),
|
||||
Commands::Diff {
|
||||
old_bigfile,
|
||||
new_bigfile,
|
||||
old_names,
|
||||
new_names,
|
||||
} => diff::diff(old_bigfile, new_bigfile, old_names, new_names),
|
||||
Commands::Names {
|
||||
bigfile,
|
||||
wordlist,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue