mirror of https://github.com/encounter/objdiff.git
Support for progress categories & linked stats
This commit is contained in:
parent
3bd8aaee41
commit
195379968c
|
@ -11,7 +11,8 @@ use argp::FromArgs;
|
||||||
use objdiff_core::{
|
use objdiff_core::{
|
||||||
bindings::report::{
|
bindings::report::{
|
||||||
ChangeItem, ChangeItemInfo, ChangeUnit, Changes, ChangesInput, Measures, Report,
|
ChangeItem, ChangeItemInfo, ChangeUnit, Changes, ChangesInput, Measures, Report,
|
||||||
ReportItem, ReportItemMetadata, ReportUnit, ReportUnitMetadata,
|
ReportCategory, ReportItem, ReportItemMetadata, ReportUnit, ReportUnitMetadata,
|
||||||
|
REPORT_VERSION,
|
||||||
},
|
},
|
||||||
config::ProjectObject,
|
config::ProjectObject,
|
||||||
diff, obj,
|
diff, obj,
|
||||||
|
@ -129,7 +130,17 @@ fn generate(args: GenerateArgs) -> Result<()> {
|
||||||
units = vec.into_iter().flatten().collect();
|
units = vec.into_iter().flatten().collect();
|
||||||
}
|
}
|
||||||
let measures = units.iter().flat_map(|u| u.measures.into_iter()).collect();
|
let measures = units.iter().flat_map(|u| u.measures.into_iter()).collect();
|
||||||
let report = Report { measures: Some(measures), units };
|
let mut categories = Vec::new();
|
||||||
|
for category in &project.progress_categories {
|
||||||
|
categories.push(ReportCategory {
|
||||||
|
id: category.id.clone(),
|
||||||
|
name: category.name.clone(),
|
||||||
|
measures: Some(Default::default()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let mut report =
|
||||||
|
Report { measures: Some(measures), units, version: REPORT_VERSION, categories };
|
||||||
|
report.calculate_progress_categories();
|
||||||
let duration = start.elapsed();
|
let duration = start.elapsed();
|
||||||
info!("Report generated in {}.{:03}s", duration.as_secs(), duration.subsec_millis());
|
info!("Report generated in {}.{:03}s", duration.as_secs(), duration.subsec_millis());
|
||||||
write_output(&report, args.output.as_deref(), output_format)?;
|
write_output(&report, args.output.as_deref(), output_format)?;
|
||||||
|
@ -145,7 +156,7 @@ fn report_object(
|
||||||
) -> Result<Option<ReportUnit>> {
|
) -> Result<Option<ReportUnit>> {
|
||||||
object.resolve_paths(project_dir, target_dir, base_dir);
|
object.resolve_paths(project_dir, target_dir, base_dir);
|
||||||
match (&object.target_path, &object.base_path) {
|
match (&object.target_path, &object.base_path) {
|
||||||
(None, Some(_)) if object.complete != Some(true) => {
|
(None, Some(_)) if !object.complete().unwrap_or(false) => {
|
||||||
warn!("Skipping object without target: {}", object.name());
|
warn!("Skipping object without target: {}", object.name());
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
@ -173,13 +184,19 @@ fn report_object(
|
||||||
let result = diff::diff_objs(&config, target.as_ref(), base.as_ref(), None)?;
|
let result = diff::diff_objs(&config, target.as_ref(), base.as_ref(), None)?;
|
||||||
|
|
||||||
let metadata = ReportUnitMetadata {
|
let metadata = ReportUnitMetadata {
|
||||||
complete: object.complete,
|
complete: object.complete(),
|
||||||
module_name: target
|
module_name: target
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|o| o.split_meta.as_ref())
|
.and_then(|o| o.split_meta.as_ref())
|
||||||
.and_then(|m| m.module_name.clone()),
|
.and_then(|m| m.module_name.clone()),
|
||||||
module_id: target.as_ref().and_then(|o| o.split_meta.as_ref()).and_then(|m| m.module_id),
|
module_id: target.as_ref().and_then(|o| o.split_meta.as_ref()).and_then(|m| m.module_id),
|
||||||
source_path: None, // TODO
|
source_path: object.metadata.as_ref().and_then(|m| m.source_path.clone()),
|
||||||
|
progress_categories: object
|
||||||
|
.metadata
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|m| m.progress_categories.clone())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
auto_generated: object.metadata.as_ref().and_then(|m| m.auto_generated),
|
||||||
};
|
};
|
||||||
let mut measures = Measures::default();
|
let mut measures = Measures::default();
|
||||||
let mut sections = vec![];
|
let mut sections = vec![];
|
||||||
|
@ -191,7 +208,7 @@ fn report_object(
|
||||||
let section_match_percent = section_diff.match_percent.unwrap_or_else(|| {
|
let section_match_percent = section_diff.match_percent.unwrap_or_else(|| {
|
||||||
// Support cases where we don't have a target object,
|
// Support cases where we don't have a target object,
|
||||||
// assume complete means 100% match
|
// assume complete means 100% match
|
||||||
if object.complete == Some(true) {
|
if object.complete().unwrap_or(false) {
|
||||||
100.0
|
100.0
|
||||||
} else {
|
} else {
|
||||||
0.0
|
0.0
|
||||||
|
@ -233,7 +250,7 @@ fn report_object(
|
||||||
let match_percent = symbol_diff.match_percent.unwrap_or_else(|| {
|
let match_percent = symbol_diff.match_percent.unwrap_or_else(|| {
|
||||||
// Support cases where we don't have a target object,
|
// Support cases where we don't have a target object,
|
||||||
// assume complete means 100% match
|
// assume complete means 100% match
|
||||||
if object.complete == Some(true) {
|
if object.complete().unwrap_or(false) {
|
||||||
100.0
|
100.0
|
||||||
} else {
|
} else {
|
||||||
0.0
|
0.0
|
||||||
|
@ -259,6 +276,10 @@ fn report_object(
|
||||||
measures.total_functions += 1;
|
measures.total_functions += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if metadata.complete.unwrap_or(false) {
|
||||||
|
measures.complete_code = measures.total_code;
|
||||||
|
measures.complete_data = measures.total_data;
|
||||||
|
}
|
||||||
measures.calc_fuzzy_match_percent();
|
measures.calc_fuzzy_match_percent();
|
||||||
measures.calc_matched_percent();
|
measures.calc_matched_percent();
|
||||||
Ok(Some(ReportUnit {
|
Ok(Some(ReportUnit {
|
||||||
|
|
|
@ -15,7 +15,7 @@ A local diffing tool for decompilation projects.
|
||||||
crate-type = ["cdylib", "rlib"]
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
all = ["config", "dwarf", "mips", "ppc", "x86", "arm"]
|
all = ["config", "dwarf", "mips", "ppc", "x86", "arm", "bindings"]
|
||||||
any-arch = [] # Implicit, used to check if any arch is enabled
|
any-arch = [] # Implicit, used to check if any arch is enabled
|
||||||
config = ["globset", "semver", "serde_json", "serde_yaml"]
|
config = ["globset", "semver", "serde_json", "serde_yaml"]
|
||||||
dwarf = ["gimli"]
|
dwarf = ["gimli"]
|
||||||
|
@ -23,7 +23,8 @@ mips = ["any-arch", "rabbitizer"]
|
||||||
ppc = ["any-arch", "cwdemangle", "cwextab", "ppc750cl"]
|
ppc = ["any-arch", "cwdemangle", "cwextab", "ppc750cl"]
|
||||||
x86 = ["any-arch", "cpp_demangle", "iced-x86", "msvc-demangler"]
|
x86 = ["any-arch", "cpp_demangle", "iced-x86", "msvc-demangler"]
|
||||||
arm = ["any-arch", "cpp_demangle", "unarm", "arm-attr"]
|
arm = ["any-arch", "cpp_demangle", "unarm", "arm-attr"]
|
||||||
wasm = ["serde_json", "console_error_panic_hook", "console_log"]
|
bindings = ["serde_json", "prost", "pbjson"]
|
||||||
|
wasm = ["bindings", "console_error_panic_hook", "console_log"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.82"
|
anyhow = "1.0.82"
|
||||||
|
@ -34,8 +35,8 @@ log = "0.4.21"
|
||||||
memmap2 = "0.9.4"
|
memmap2 = "0.9.4"
|
||||||
num-traits = "0.2.18"
|
num-traits = "0.2.18"
|
||||||
object = { version = "0.35.0", features = ["read_core", "std", "elf", "pe"], default-features = false }
|
object = { version = "0.35.0", features = ["read_core", "std", "elf", "pe"], default-features = false }
|
||||||
pbjson = "0.7.0"
|
pbjson = { version = "0.7.0", optional = true }
|
||||||
prost = "0.13.1"
|
prost = { version = "0.13.1", optional = true }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
similar = { version = "2.5.0", default-features = false }
|
similar = { version = "2.5.0", default-features = false }
|
||||||
strum = { version = "0.26.2", features = ["derive"] }
|
strum = { version = "0.26.2", features = ["derive"] }
|
||||||
|
|
Binary file not shown.
|
@ -24,6 +24,14 @@ message Measures {
|
||||||
uint32 matched_functions = 9;
|
uint32 matched_functions = 9;
|
||||||
// Fully matched functions percent
|
// Fully matched functions percent
|
||||||
float matched_functions_percent = 10;
|
float matched_functions_percent = 10;
|
||||||
|
// Completed (or "linked") code size in bytes
|
||||||
|
uint64 complete_code = 11;
|
||||||
|
// Completed (or "linked") code percent
|
||||||
|
float complete_code_percent = 12;
|
||||||
|
// Completed (or "linked") data size in bytes
|
||||||
|
uint64 complete_data = 13;
|
||||||
|
// Completed (or "linked") data percent
|
||||||
|
float complete_data_percent = 14;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Project progress report
|
// Project progress report
|
||||||
|
@ -32,6 +40,19 @@ message Report {
|
||||||
Measures measures = 1;
|
Measures measures = 1;
|
||||||
// Units within this report
|
// Units within this report
|
||||||
repeated ReportUnit units = 2;
|
repeated ReportUnit units = 2;
|
||||||
|
// Report version
|
||||||
|
uint32 version = 3;
|
||||||
|
// Progress categories
|
||||||
|
repeated ReportCategory categories = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ReportCategory {
|
||||||
|
// The ID of the category
|
||||||
|
string id = 1;
|
||||||
|
// The name of the category
|
||||||
|
string name = 2;
|
||||||
|
// Progress info for this category
|
||||||
|
Measures measures = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A unit of the report (usually a translation unit)
|
// A unit of the report (usually a translation unit)
|
||||||
|
@ -58,6 +79,10 @@ message ReportUnitMetadata {
|
||||||
optional uint32 module_id = 3;
|
optional uint32 module_id = 3;
|
||||||
// The path to the source file of this unit
|
// The path to the source file of this unit
|
||||||
optional string source_path = 4;
|
optional string source_path = 4;
|
||||||
|
// Progress categories for this unit
|
||||||
|
repeated string progress_categories = 5;
|
||||||
|
// Whether this unit is automatically generated (not user-provided)
|
||||||
|
optional bool auto_generated = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A section or function within a unit
|
// A section or function within a unit
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#[cfg(feature = "any-arch")]
|
||||||
pub mod diff;
|
pub mod diff;
|
||||||
pub mod report;
|
pub mod report;
|
||||||
#[cfg(feature = "wasm")]
|
#[cfg(feature = "wasm")]
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::ops::AddAssign;
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use prost::Message;
|
use prost::Message;
|
||||||
use serde_json::error::Category;
|
use serde_json::error::Category;
|
||||||
|
@ -6,18 +8,21 @@ use serde_json::error::Category;
|
||||||
include!(concat!(env!("OUT_DIR"), "/objdiff.report.rs"));
|
include!(concat!(env!("OUT_DIR"), "/objdiff.report.rs"));
|
||||||
include!(concat!(env!("OUT_DIR"), "/objdiff.report.serde.rs"));
|
include!(concat!(env!("OUT_DIR"), "/objdiff.report.serde.rs"));
|
||||||
|
|
||||||
|
pub const REPORT_VERSION: u32 = 1;
|
||||||
|
|
||||||
impl Report {
|
impl Report {
|
||||||
pub fn parse(data: &[u8]) -> Result<Self> {
|
pub fn parse(data: &[u8]) -> Result<Self> {
|
||||||
if data.is_empty() {
|
if data.is_empty() {
|
||||||
bail!(std::io::Error::from(std::io::ErrorKind::UnexpectedEof));
|
bail!(std::io::Error::from(std::io::ErrorKind::UnexpectedEof));
|
||||||
}
|
}
|
||||||
if data[0] == b'{' {
|
let report = if data[0] == b'{' {
|
||||||
// Load as JSON
|
// Load as JSON
|
||||||
Self::from_json(data).map_err(anyhow::Error::new)
|
Self::from_json(data)?
|
||||||
} else {
|
} else {
|
||||||
// Load as binary protobuf
|
// Load as binary protobuf
|
||||||
Self::decode(data).map_err(anyhow::Error::new)
|
Self::decode(data)?
|
||||||
}
|
};
|
||||||
|
Ok(report)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_json(bytes: &[u8]) -> Result<Self, serde_json::Error> {
|
fn from_json(bytes: &[u8]) -> Result<Self, serde_json::Error> {
|
||||||
|
@ -37,6 +42,81 @@ impl Report {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn migrate(&mut self) -> Result<()> {
|
||||||
|
if self.version == 0 {
|
||||||
|
self.migrate_v0()?;
|
||||||
|
}
|
||||||
|
if self.version != REPORT_VERSION {
|
||||||
|
bail!("Unsupported report version: {}", self.version);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn migrate_v0(&mut self) -> Result<()> {
|
||||||
|
let Some(measures) = &mut self.measures else {
|
||||||
|
bail!("Missing measures in report");
|
||||||
|
};
|
||||||
|
for unit in &mut self.units {
|
||||||
|
let Some(unit_measures) = &mut unit.measures else {
|
||||||
|
bail!("Missing measures in report unit");
|
||||||
|
};
|
||||||
|
let Some(metadata) = &mut unit.metadata else {
|
||||||
|
bail!("Missing metadata in report unit");
|
||||||
|
};
|
||||||
|
if metadata.module_name.is_some() || metadata.module_id.is_some() {
|
||||||
|
metadata.progress_categories = vec!["modules".to_string()];
|
||||||
|
} else {
|
||||||
|
metadata.progress_categories = vec!["dol".to_string()];
|
||||||
|
}
|
||||||
|
if metadata.complete.unwrap_or(false) {
|
||||||
|
unit_measures.complete_code = unit_measures.total_code;
|
||||||
|
unit_measures.complete_data = unit_measures.total_data;
|
||||||
|
unit_measures.complete_code_percent = 100.0;
|
||||||
|
unit_measures.complete_data_percent = 100.0;
|
||||||
|
} else {
|
||||||
|
unit_measures.complete_code = 0;
|
||||||
|
unit_measures.complete_data = 0;
|
||||||
|
unit_measures.complete_code_percent = 0.0;
|
||||||
|
unit_measures.complete_data_percent = 0.0;
|
||||||
|
}
|
||||||
|
measures.complete_code += unit_measures.complete_code;
|
||||||
|
measures.complete_data += unit_measures.complete_data;
|
||||||
|
}
|
||||||
|
measures.calc_matched_percent();
|
||||||
|
self.version = 1;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculate_progress_categories(&mut self) {
|
||||||
|
for unit in &self.units {
|
||||||
|
let Some(metadata) = unit.metadata.as_ref() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Some(measures) = unit.measures.as_ref() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
for category_id in &metadata.progress_categories {
|
||||||
|
let category = match self.categories.iter_mut().find(|c| &c.id == category_id) {
|
||||||
|
Some(category) => category,
|
||||||
|
None => {
|
||||||
|
self.categories.push(ReportCategory {
|
||||||
|
id: category_id.clone(),
|
||||||
|
name: String::new(),
|
||||||
|
measures: Some(Default::default()),
|
||||||
|
});
|
||||||
|
self.categories.last_mut().unwrap()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
*category.measures.get_or_insert_with(Default::default) += *measures;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for category in &mut self.categories {
|
||||||
|
let measures = category.measures.get_or_insert_with(Default::default);
|
||||||
|
measures.calc_fuzzy_match_percent();
|
||||||
|
measures.calc_matched_percent();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Measures {
|
impl Measures {
|
||||||
|
@ -66,6 +146,16 @@ impl Measures {
|
||||||
} else {
|
} else {
|
||||||
self.matched_functions as f32 / self.total_functions as f32 * 100.0
|
self.matched_functions as f32 / self.total_functions as f32 * 100.0
|
||||||
};
|
};
|
||||||
|
self.complete_code_percent = if self.total_code == 0 {
|
||||||
|
100.0
|
||||||
|
} else {
|
||||||
|
self.complete_code as f32 / self.total_code as f32 * 100.0
|
||||||
|
};
|
||||||
|
self.complete_data_percent = if self.total_data == 0 {
|
||||||
|
100.0
|
||||||
|
} else {
|
||||||
|
self.complete_data as f32 / self.total_data as f32 * 100.0
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,19 +165,27 @@ impl From<&ReportItem> for ChangeItemInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AddAssign for Measures {
|
||||||
|
fn add_assign(&mut self, other: Self) {
|
||||||
|
self.fuzzy_match_percent += other.fuzzy_match_percent * other.total_code as f32;
|
||||||
|
self.total_code += other.total_code;
|
||||||
|
self.matched_code += other.matched_code;
|
||||||
|
self.total_data += other.total_data;
|
||||||
|
self.matched_data += other.matched_data;
|
||||||
|
self.total_functions += other.total_functions;
|
||||||
|
self.matched_functions += other.matched_functions;
|
||||||
|
self.complete_code += other.complete_code;
|
||||||
|
self.complete_data += other.complete_data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Allows [collect](Iterator::collect) to be used on an iterator of [Measures].
|
/// Allows [collect](Iterator::collect) to be used on an iterator of [Measures].
|
||||||
impl FromIterator<Measures> for Measures {
|
impl FromIterator<Measures> for Measures {
|
||||||
fn from_iter<T>(iter: T) -> Self
|
fn from_iter<T>(iter: T) -> Self
|
||||||
where T: IntoIterator<Item = Measures> {
|
where T: IntoIterator<Item = Measures> {
|
||||||
let mut measures = Measures::default();
|
let mut measures = Measures::default();
|
||||||
for other in iter {
|
for other in iter {
|
||||||
measures.fuzzy_match_percent += other.fuzzy_match_percent * other.total_code as f32;
|
measures += other;
|
||||||
measures.total_code += other.total_code;
|
|
||||||
measures.matched_code += other.matched_code;
|
|
||||||
measures.total_data += other.total_data;
|
|
||||||
measures.matched_data += other.matched_data;
|
|
||||||
measures.total_functions += other.total_functions;
|
|
||||||
measures.matched_functions += other.matched_functions;
|
|
||||||
}
|
}
|
||||||
measures.calc_fuzzy_match_percent();
|
measures.calc_fuzzy_match_percent();
|
||||||
measures.calc_matched_percent();
|
measures.calc_matched_percent();
|
||||||
|
@ -125,8 +223,10 @@ impl From<LegacyReport> for Report {
|
||||||
total_functions: value.total_functions,
|
total_functions: value.total_functions,
|
||||||
matched_functions: value.matched_functions,
|
matched_functions: value.matched_functions,
|
||||||
matched_functions_percent: value.matched_functions_percent,
|
matched_functions_percent: value.matched_functions_percent,
|
||||||
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
units: value.units.into_iter().map(ReportUnit::from).collect(),
|
units: value.units.into_iter().map(ReportUnit::from).collect::<Vec<_>>(),
|
||||||
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,8 @@ pub struct ProjectConfig {
|
||||||
pub watch_patterns: Option<Vec<Glob>>,
|
pub watch_patterns: Option<Vec<Glob>>,
|
||||||
#[serde(default, alias = "units")]
|
#[serde(default, alias = "units")]
|
||||||
pub objects: Vec<ProjectObject>,
|
pub objects: Vec<ProjectObject>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub progress_categories: Vec<ProjectProgressCategory>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, serde::Deserialize)]
|
#[derive(Default, Clone, serde::Deserialize)]
|
||||||
|
@ -44,11 +46,37 @@ pub struct ProjectObject {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub base_path: Option<PathBuf>,
|
pub base_path: Option<PathBuf>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
#[deprecated(note = "Use metadata.reverse_fn_order")]
|
||||||
pub reverse_fn_order: Option<bool>,
|
pub reverse_fn_order: Option<bool>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
#[deprecated(note = "Use metadata.complete")]
|
||||||
pub complete: Option<bool>,
|
pub complete: Option<bool>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub scratch: Option<ScratchConfig>,
|
pub scratch: Option<ScratchConfig>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub metadata: Option<ProjectObjectMetadata>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone, serde::Deserialize)]
|
||||||
|
pub struct ProjectObjectMetadata {
|
||||||
|
#[serde(default)]
|
||||||
|
pub complete: Option<bool>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub reverse_fn_order: Option<bool>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub source_path: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub progress_categories: Option<Vec<String>>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub auto_generated: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone, serde::Deserialize)]
|
||||||
|
pub struct ProjectProgressCategory {
|
||||||
|
#[serde(default)]
|
||||||
|
pub id: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProjectObject {
|
impl ProjectObject {
|
||||||
|
@ -82,6 +110,16 @@ impl ProjectObject {
|
||||||
self.base_path = Some(project_dir.join(path));
|
self.base_path = Some(project_dir.join(path));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn complete(&self) -> Option<bool> {
|
||||||
|
#[allow(deprecated)]
|
||||||
|
self.metadata.as_ref().and_then(|m| m.complete).or(self.complete)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reverse_fn_order(&self) -> Option<bool> {
|
||||||
|
#[allow(deprecated)]
|
||||||
|
self.metadata.as_ref().and_then(|m| m.reverse_fn_order).or(self.reverse_fn_order)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
#[derive(Default, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
|
#[cfg(feature = "any-arch")]
|
||||||
pub mod arch;
|
pub mod arch;
|
||||||
|
#[cfg(feature = "bindings")]
|
||||||
pub mod bindings;
|
pub mod bindings;
|
||||||
#[cfg(feature = "config")]
|
#[cfg(feature = "config")]
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
#[cfg(feature = "any-arch")]
|
||||||
pub mod diff;
|
pub mod diff;
|
||||||
|
#[cfg(feature = "any-arch")]
|
||||||
pub mod obj;
|
pub mod obj;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|
||||||
#[cfg(not(feature = "any-arch"))]
|
|
||||||
compile_error!("At least one architecture feature must be enabled.");
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ use num_traits::PrimInt;
|
||||||
use object::{Endian, Object};
|
use object::{Endian, Object};
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/44711012/how-do-i-format-a-signed-integer-to-a-sign-aware-hexadecimal-representation
|
// https://stackoverflow.com/questions/44711012/how-do-i-format-a-signed-integer-to-a-sign-aware-hexadecimal-representation
|
||||||
pub(crate) struct ReallySigned<N: PrimInt>(pub(crate) N);
|
pub struct ReallySigned<N: PrimInt>(pub(crate) N);
|
||||||
|
|
||||||
impl<N: PrimInt> LowerHex for ReallySigned<N> {
|
impl<N: PrimInt> LowerHex for ReallySigned<N> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
|
|
@ -365,7 +365,7 @@ fn display_object(
|
||||||
let selected = matches!(selected_obj, Some(obj) if obj.name == object_name);
|
let selected = matches!(selected_obj, Some(obj) if obj.name == object_name);
|
||||||
let color = if selected {
|
let color = if selected {
|
||||||
appearance.emphasized_text_color
|
appearance.emphasized_text_color
|
||||||
} else if let Some(complete) = object.complete {
|
} else if let Some(complete) = object.complete() {
|
||||||
if complete {
|
if complete {
|
||||||
appearance.insert_color
|
appearance.insert_color
|
||||||
} else {
|
} else {
|
||||||
|
@ -392,8 +392,8 @@ fn display_object(
|
||||||
name: object_name.to_string(),
|
name: object_name.to_string(),
|
||||||
target_path: object.target_path.clone(),
|
target_path: object.target_path.clone(),
|
||||||
base_path: object.base_path.clone(),
|
base_path: object.base_path.clone(),
|
||||||
reverse_fn_order: object.reverse_fn_order,
|
reverse_fn_order: object.reverse_fn_order(),
|
||||||
complete: object.complete,
|
complete: object.complete(),
|
||||||
scratch: object.scratch.clone(),
|
scratch: object.scratch.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -470,7 +470,7 @@ fn filter_node(
|
||||||
if (search.is_empty() || name.to_ascii_lowercase().contains(search))
|
if (search.is_empty() || name.to_ascii_lowercase().contains(search))
|
||||||
&& (!filter_diffable
|
&& (!filter_diffable
|
||||||
|| (object.base_path.is_some() && object.target_path.is_some()))
|
|| (object.base_path.is_some() && object.target_path.is_some()))
|
||||||
&& (!filter_incomplete || matches!(object.complete, None | Some(false)))
|
&& (!filter_incomplete || matches!(object.complete(), None | Some(false)))
|
||||||
{
|
{
|
||||||
Some(node.clone())
|
Some(node.clone())
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in New Issue