Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The compare subcommand #1

Draft
wants to merge 3 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions src/blocks/compare/kinds.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use crate::blocks::intermediary::data::ModernBlockData;

#[derive(Debug)]
pub enum PropertyComparison {
/// The base is equal to the target property -> compatible
Equal,
/// The base has less elements than the target property -> compatible
MissingEnd,
/// The base has more elements than the target property -> incompatible
LongerEnd,
/// The order of the base elements is different from the target property -> compatible
DifferentOrdering,
/// The values (and maybe ordering) of the base elements is different from the target property -> incompatible
DifferentValues,
}

impl PropertyComparison {
pub fn compare<'raw>(base: &Vec<&'raw str>, target: &Vec<&'raw str>) -> Self {
if base == target {
Self::Equal
} else {
let mut last_index = 0;
for (i, value) in base.iter().enumerate() {
if matches!(target.get(i), Some(x) if x == value) {
if last_index != i {
break;
}
last_index += 1;
}
}
if last_index == base.len() || last_index == target.len() {
return if target.len() > base.len() {
Self::MissingEnd
} else {
Self::LongerEnd
}
}
for value in base {
if !target.contains(value) {
return Self::DifferentValues
}
}
Self::DifferentOrdering
}
}
}

#[derive(Debug)]
pub enum BlockComparison {
/// The base properties equal to the target -> compatible
Equal,
/// The base has one or more different types from the target -> incompatible
DifferentType,
/// The base has a different enum type from the target -> maybe compatible
DifferentEnum,
/// The base has a different range from the target -> maybe compatible
DifferentRange,
/// The base has more properties than the target -> compatible
MoreProperties,
/// The base has less properties than the target -> incompatible
LessProperties,
/// If all else fails, this usually means the properties would be in a different order,
/// sometimes compatible, sometimes incompatible
DifferentOrder,
}

impl BlockComparison {
pub fn compare<'raw>(base: &ModernBlockData<'raw>, target: &ModernBlockData<'raw>) -> Self {
if base.properties() == target.properties() {
Self::Equal
} else {
if let Some(properties) = base.properties() {
let target = if let Some(target) = target.properties() {
target
} else {
return Self::MoreProperties;
};
for (name, kind) in properties {
if let Some(target) = target.get(name) {
if kind != target {
return if kind.is_enum() && target.is_enum() {
Self::DifferentEnum
} else if kind.is_range() && target.is_range() {
Self::DifferentRange
} else {
Self::DifferentType
}
}
}
}
if properties.len() < target.len() {
Self::LessProperties
} else if properties.len() > target.len() {
Self::MoreProperties
} else {
Self::DifferentOrder
}
} else {
Self::LessProperties
}
}
}
}
74 changes: 74 additions & 0 deletions src/blocks/compare/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use anyhow::Context;
use clap::Args;
use crate::blocks::compare::kinds::{BlockComparison, PropertyComparison};
use crate::blocks::intermediary::data::ModernBlockList;
use crate::util::file::InputFile;

pub mod kinds;

#[derive(Args, Debug)]
/// Compare two data versions one-directional
///
/// Currently only compares block data
pub struct CompareCommand {
/// The intermediary data file to start from
base: InputFile,
/// The target intermediary data file to find compatibility with
target: InputFile,
/// Don't list missing entries
#[clap(long = "print-missing")]
print_missing: bool,
}

impl CompareCommand {
pub fn compare<'raw>(&self) -> anyhow::Result<()> {
let base_version: ModernBlockList = self.base.data().with_context(|| format!("Error deserializing {:?}", self.base.name()))?;
let target_version: ModernBlockList = self.target.data().with_context(|| format!("Error deserializing {:?}", self.target.name()))?;

eprintln!("Checking property compatibility...\n=============");
let mut missing_props = 0;
for (property, base) in base_version.properties() {
if let Some(target) = target_version.properties().get(property) {
match PropertyComparison::compare(base, target) {
PropertyComparison::Equal => {}
cmp => println!("\"{}\" has {:?}", property, cmp),
}
} else {
if self.print_missing {
println!("Missing property: \"{}\"!", property);
}
missing_props += 1;
}
}
eprintln!("=============\nChecking block compatibility...\n=============");
let mut missing_count = 0;
let mut same_base = 0;
let mut perfect_count = 0;
for (name, data) in base_version.blocks() {
if let Some(target) = target_version.blocks().get(name) {
match BlockComparison::compare(data, target) {
BlockComparison::Equal => {
if data.base_id() == target.base_id() {
perfect_count += 1;
}
}
cmp => eprintln!("\"{}\" has \"{:?}\"", name, cmp),
}
if data.base_id() == target.base_id() {
same_base += 1;
}
} else {
if self.print_missing {
println!("Missing blocks: \"{}\"", name);
}
missing_count += 1;
}
}
println!("=============\nMissing {} properties", missing_props);
println!("Missing {} blocks", missing_count);
println!("{} out of {} have the same base id", same_base, base_version.blocks().len() - missing_count);
println!("{} perfect blocks", perfect_count);

Ok(())
}
}
55 changes: 55 additions & 0 deletions src/blocks/intermediary/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ impl<'raw> ModernBlockList<'raw> {
blocks,
}
}

pub fn properties(&self) -> &LinkedHashMap<&'raw str, Vec<&'raw str>> {
&self.properties
}

pub fn blocks(&self) -> &LinkedHashMap<Identifier<'raw>, ModernBlockData<'raw>, RandomState> {
&self.blocks
}
}

#[derive(Debug, Serialize, Deserialize)]
Expand All @@ -41,6 +49,14 @@ impl<'raw> ModernBlockData<'raw> {
default_id
}
}

pub fn properties(&self) -> Option<&LinkedHashMap<&'raw str, TextOrRange<'raw>, RandomState>> {
self.kinds.as_ref()
}

pub fn base_id(&self) -> i32 {
self.base_id
}
}

#[derive(Debug, Serialize, Deserialize)]
Expand All @@ -59,4 +75,43 @@ impl<'raw> TextOrRange<'raw> {
pub fn range(start: i32, end: i32) -> Self {
Self::Range([start, end])
}

pub fn is_bool(&self) -> bool {
matches!(self, TextOrRange::Text(val) if val == &"bool")
}

pub fn is_enum(&self) -> bool {
matches!(self, TextOrRange::Text(val) if val != &"bool")
}

pub fn is_range(&self) -> bool {
matches!(self, TextOrRange::Range(_))
}

pub fn get_enum(&self) -> Option<&'raw str> {
match self {
TextOrRange::Text(val) if val != &"bool" => {
Some(val)
}
_ => None
}
}
}

impl<'raw> PartialEq for TextOrRange<'raw> {
fn eq(&self, other: &Self) -> bool {
match self {
TextOrRange::Text(val) => {
if let Self::Text(other) = other {
return val == other;
}
}
TextOrRange::Range(range) => {
if let Self::Range(other) = other {
return range == other;
}
}
}
false
}
}
1 change: 1 addition & 0 deletions src/blocks/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod compare;
pub mod intermediary;
pub mod raw;
5 changes: 5 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use anyhow::Context;
use clap::{Parser, Subcommand};
use blocks::intermediary::IntermediaryCommand;
use blocks::compare::CompareCommand;

mod blocks;
mod util;
Expand All @@ -14,6 +15,7 @@ pub struct Cli {

#[derive(Subcommand, Debug)]
pub enum SubCommands {
Compare(CompareCommand),
Intermediary(IntermediaryCommand),
}

Expand All @@ -24,5 +26,8 @@ fn main() -> anyhow::Result<()> {
SubCommands::Intermediary(cmd) => {
cmd.generate_intermediate().with_context(|| "Error while generating data")
}
SubCommands::Compare(cmd) => {
cmd.compare().with_context(|| "Error while comparing data")
}
}
}