{"id":23077981,"url":"https://github.com/volumegraphics/havocompare","last_synced_at":"2025-08-15T19:33:03.952Z","repository":{"id":61651365,"uuid":"553411189","full_name":"VolumeGraphics/havocompare","owner":"VolumeGraphics","description":"A multi-platform file-configurable folder comparison tool with html-reporting written in rust","archived":false,"fork":false,"pushed_at":"2025-07-28T09:19:29.000Z","size":2197,"stargazers_count":12,"open_issues_count":0,"forks_count":2,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-08-07T00:38:02.844Z","etag":null,"topics":["command-line","command-line-tool","compare-files","compare-images","comparison","comparison-tool","html-reporting","rust"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/VolumeGraphics.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-10-18T07:10:12.000Z","updated_at":"2025-07-28T08:21:59.000Z","dependencies_parsed_at":"2023-11-06T09:44:38.217Z","dependency_job_id":"4aaff166-c3cd-43dc-84f0-3a628d75b324","html_url":"https://github.com/VolumeGraphics/havocompare","commit_stats":{"total_commits":146,"total_committers":6,"mean_commits":"24.333333333333332","dds":0.3904109589041096,"last_synced_commit":"54ba160033d17e1f42b494d6394be638f6a32726"},"previous_names":[],"tags_count":31,"template":false,"template_full_name":null,"purl":"pkg:github/VolumeGraphics/havocompare","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VolumeGraphics%2Fhavocompare","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VolumeGraphics%2Fhavocompare/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VolumeGraphics%2Fhavocompare/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VolumeGraphics%2Fhavocompare/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/VolumeGraphics","download_url":"https://codeload.github.com/VolumeGraphics/havocompare/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VolumeGraphics%2Fhavocompare/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270620779,"owners_count":24617722,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-15T02:00:12.559Z","response_time":110,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["command-line","command-line-tool","compare-files","compare-images","comparison","comparison-tool","html-reporting","rust"],"created_at":"2024-12-16T10:45:45.681Z","updated_at":"2025-08-15T19:33:03.939Z","avatar_url":"https://github.com/VolumeGraphics.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Havocompare - a folder comparison utility\n\n[![Crates.io](https://img.shields.io/crates/d/havocompare?style=flat)](https://crates.io/crates/havocompare)\n[![Documentation](https://docs.rs/havocompare/badge.svg)](https://docs.rs/havocompare)\n![CI](https://github.com/VolumeGraphics/havocompare/actions/workflows/rust.yml/badge.svg?branch=main \"CI\")\n[![Coverage Status](https://coveralls.io/repos/github/VolumeGraphics/havocompare/badge.svg?branch=main)](https://coveralls.io/github/VolumeGraphics/havocompare?branch=main)\n[![License](https://img.shields.io/badge/license-MIT-blue?style=flat)](LICENSE)\n\n## Contributors:\n\n\u003ca href=\"https://github.com/VolumeGraphics/havocompare/graphs/contributors\"\u003e\n  \u003cimg src=\"https://contrib.rocks/image?repo=VolumeGraphics/havocompare\"  alt=\"Contributors\"/\u003e\n\u003c/a\u003e\n\n## Quickstart\n\n### 0. Install havocompare\n\nYou have rust? cool! try:\n`cargo install havocompare`\n\nYou just want a binary:\nCheck our binary downloads on github-pages\n\n### 1. Create a config file\n\nHavocompare was developed with a few design goals in mind. We wanted a human-readable and easily composable\nconfiguration file format.\nAfter a few tries we ended up with the current format, which is a list of rules inside a yaml file.\nSee the following example `config.yaml`:\n\n```yaml\nrules:\n  - name: \"Numerical results csv\"\n    # you can have multiple includes and excludes\n    pattern_include:\n      - \"**/export_*.csv\"\n    # excludes are optional\n    pattern_exclude:\n      - \"**/export_1337.csv\"\n    CSV:\n      comparison_modes:\n        - Relative: 0.1\n        - Absolute: 1.0\n```\n\nIt creates a new rule named rule including all files matching \"export_*.csv\" in all sub-folders but exclude \"\nexport_1337.csv\".\nString cells will be checked for perfect identity, numbers (including numbers with units) will be checked for a relative\ndeviation smaller than `0.1`\nAND absolute deviation smaller than `1.0`.\n\n__Comparison rules__\n\n- Relative means validity is checked like: `|nominal - actual| / |nominal| \u003c tolerance`\n- Absolute means validity is checked like: `|nominal - actual| \u003c tolerance`\n- \"nan\" and \"nan\" is equal\n- `0` difference with `0` nominal value is valid for any relative difference\n\n### 2. Run the compare\n\nRunning the comparison is super easy, just supply nominal, actual and the config:\n`./havocompare compare nominal_dir actual_dir config.yaml`\nThe report of the comparison will be written inside the `./report` folder. Differences will also be printed to the\nterminal.\nFurthermore, if differences are found, the return code will be `1`, if no differences are found, it will be `0` making\nintegration of\nhavocompare into a CI system rather easy.\n\n## Details on the config\n\n### Validation Scheme\n\nWriting a valid configuration file can be error-prone without auto-completion. We suggest using json schema to validate\nyour yaml\nand even enable auto-completion in IDEs like pycharm. To generate the schema you can call:\n`./havocompare schema \u003e config_scheme.json` and import the resulting scheme into your IDE.\n\n### Comparison options\n\n#### CSV\n\nThe `comparison_modes` option is required and of type 'list'. It can comprise either a relative numerical ('Relative')\nmaximum deviation or a maximum\ndeviation ('Absolute').\nYou can specify the decimal separator and the field separator. If you don't specify, havocompare will try to guess it\nfrom each csv file.\nNote: If delimiters are not specified, even different delimiters between nominal and actual are accepted as long as all\ndeviations are in bounds.\nTo ignore specific cells, you can specify an exclusion regex.\n\nThe preprocessing steps are done after the file is parsed using the given delimiters (or guessing) but before anything\nelse. Processing order is as written in the list.\nIn the below example, headers will be extracted from the csv-input file, then a column with the title \"Column to delete\"\nwill be deleted.\nIf any of the preprocessing steps fail, havocompare will exit with an error immediately so use them carefully.\n\nSee the following example with all optional parameters set:\n\n```yaml\nrules:\n  - name: \"CSV - Demo all options\"\n    # what files to include - use as many as make sense to reduce duplication in your rules\n    pattern_include:\n      - \"**/*.csv\"\n    # optional: of all included files, remove the ones matching any exclude pattern\n    pattern_exclude:\n      - \"**/ignored.csv\"\n    CSV:\n      # delimiters are optional, if not given, they will be auto-detected.\n      # auto-detection allows different delimiters for nominal and actual\n      decimal_separator: '.'\n      field_delimiter: ';'\n      # can have Absolute or Relative or both\n      comparison_modes:\n        - Absolute: 1.0\n        - Relative: 0.1\n      # optional: exclude fields matching the regex from comparison\n      exclude_field_regex: \"Excluded\"\n      # optional: preprocessing of the csv files\n      preprocessing:\n        # extracts the headers to the header-fields, makes reports more legible and allows for further processing \"ByName\".\n        # While it may fail, there's no penalty for it, as long as you don't rely on it.\n        - ExtractHeaders\n        # Sort the table by column 0, beware that the column must only contain numbers / quantities\n        - SortByColumnNumber: 0\n        # Delete a column by name, needs `ExtractHeaders` first - delete sets all values to 'DELETED'\n        - DeleteColumnByName: \"Vertex_Position_Y\"\n        - DeleteColumnByNumber: 1\n        # Sorts are stable, so a second sort will keep the first sort as sub-order.\n        - SortByColumnName: \"Vertex_Position_X\"\n        # Deletes the first row by setting all values to 'DELETED' - meaning that numbering stays constant \n        - DeleteRowByNumber: 0\n        # Deletes rows having any element matching the given regex (may delete different lines in nom / act)!\n        - DeleteRowByRegex: \"Vertex_Count\"\n        # Deletes the cell (column, row) by setting the value to 'DELETED'\n        - DeleteCellByNumber:\n            column: 0\n            row: 0\n        # Deletes the cell (column name, row) by setting the value to 'DELETED'. This needs `ExtractHeaders`\n        - DeleteCellByName:\n            column: \"Column to delete\"\n            row: 0\n```\n\n#### Image comparison\n\nImage comparison is done using the `image compare` crate.\nSpecify loads of options here and then filter on threshold.\n\n```yaml\nrules:\n  - name: \"JPG comparison\"\n    pattern_include:\n      - \"**/*.jpg\"\n    # exclude can of course also be specified!\n    Image:\n      # Compare images in RGBA-mode, can also be RGB and Gray\n      # Comparison mode set to Hybrid means we want MSSIM on the Y channel and 2 dim vec diff on UV for color information\n      RGBA: Hybrid\n      threshold: 0.9\n```\n\n#### Plain text comparison\n\nFor plain text comparison the file is read and compared line by line. For each line the normalized Damerau-Levenshtein\ndistance from the `strsim`\ncrate is used. You can ignore single lines which you know are different by specifying an arbitrary number of ignored\nlines:\n\n```yaml\nrules:\n  - name: \"HTML-Compare strict\"\n    pattern_exclude:\n      - \"**/*_changed.html\"\n    pattern_include:\n      - \"**/*.html\"\n    PlainText:\n      # Normalized Damerau-Levenshtein distance\n      threshold: 1.0\n      # All lines matching any regex below will be ignored\n      ignore_lines:\n        - \"stylesheet\"\n        - \"next_ignore\"\n        - \"[A-Z]*[0-9]\"\n```\n\n#### PDF text comparison\n\nFor PDF text comparison the text will be extracted and written to temporary files. The files will then be compared using\nthe Plain text comparison:\n\n```yaml\nrules:\n  - name: \"PDF-Text-Compare\"\n    pattern_exclude:\n      - \"**/*_changed.pdf\"\n    pattern_include:\n      - \"**/*.pdf\"\n    PDFText:\n      # Normalized Damerau-Levenshtein distance\n      threshold: 1.0\n      # All lines matching any regex below will be ignored\n      ignore_lines:\n        - \"stylesheet\"\n        - \"next_ignore\"\n        - \"[A-Z]*[0-9]\"\n```\n\n#### Hash comparison\n\nFor binary files which cannot otherwise be checked we can also do a simple hash comparison.\nCurrently, we only support SHA-256 but more checks can be added easily.\n\n```yaml\nrules:\n  - name: \"Hash comparison strict\"\n    pattern_exclude:\n      - \"**/*.bin\"\n    Hash:\n      # Currently we only have Sha256\n      function: Sha256\n```\n\n#### File metadata comparison\n\nFor the cases where the pure existence or some metadata are already enough.\n\n```yaml\nrules:\n  - name: \"Metadata comparison\"\n    pattern_exclude:\n      - \"**/*.bin\"\n    FileProperties:\n      # nom/act file paths must not contain whitespace\n      forbid_name_regex: \"[\\\\s]\"\n      # files must have their modification timestamp within 3600 seconds\n      modification_date_tolerance_secs: 3600\n      # files sizes must be within 1 kb \n      file_size_tolerance_bytes: 1024\n```\n\n#### Run external comparison tool\n\nIn case you want to run an external comparison tool, you can use this option\n\n```yaml\nrules:\n  - name: \"External checker\"\n    pattern_include:\n      - \"*.pdf\"\n    External:\n      # this config will call `/usr/bin/pdf-diff --only-images nominal.pdf actual.pdf`\n      # return code will decide on comparison result\n      executable: \"/usr/bin/pdf-diff\"\n      # optional: add as many extra params as \n      extra_params:\n        - \"--only-images\"\n```\n\n#### JSON comparison\n\nCompares JSON files for different keys in both files and mismatches in values.\n`ignore_keys` is a list of regexes that are matched against the individual key names, the key value pair is excluded\nfrom the comparison if a regex matches.\nThe values are not affected by this.\n\n```yaml\nrules:\n  - name: \"Compare JSON files\"\n    pattern_include:\n      - \"**/*.json\"\n    Json:\n      ignore_keys:\n        # drop \"ignore_this_key\" and \"ignore_this_keys\" with this regex :)\n        - \"ignore_this_key(s?)\"\n```\n\n#### File Exists / Directory Comparison\n\nCompares directory structure and file existence in both paths.\n\n```yaml\nrules:\n  - name: \"Directory Checker\"\n    # to check directory structure only (ignoring the files) use \"**/*/\" and remove pattern_exclude\n    # to check all files and directory use \"**/*\" and remove pattern_exclude\n    # to check files only uses \"**/*.*\" but this works only in windows. Or use \"**/*\" and pattern_exclude \"**/*/\"\n    pattern_include:\n      - \"**/*\"\n    pattern_exclude:\n      - \"**/*/\"\n    Directory:\n      # Mode Identical to check whether both paths are really the same: whether entry is missing in actual, and/or if entry exists in actual but not in nominal\n      # Mode MissingOnly to check only if entry is missing in actual, ignoring entries that exist in actual but not in nominal\n      mode: Identical\n```\n\n### Use HavoCompare in your unit-tests\n\n1. Add havocompare to your dev-dependencies:\n    ```toml\n    [dev-dependencies]\n    havocompare = \"0.5\"\n    ```\n2. Use it in a unit-test like\n    ```rust\n    #[test]\n    // works in 0.5 series\n    fn integ_test_dirs() {\n      let result_dir = process_whatever_test_data();\n      // just write the usual yaml file\n      let result = havocompare::compare_folders(\"../tests/data/nominal/integ_test/case\", \u0026result_dir, \"../tests/data/config.yaml\", \"../tests/out_report\").unwrap;\n      assert!(result);\n    }\n    #[test]\n    // works starting with 0.5.3 only\n    fn integ_test_file() {\n      let result_file = process_generate_image();\n      // see docs for all options\n      let compare_mode = ComparisonMode::Image(ImageCompareConfig{threshold: 0.97});\n      let result = havocompare::compare_files(\"../tests/data/nominal.png\", \u0026result_file, \u0026compare_mode).unwrap;\n      assert!(result);\n    }\n    ```\n\n## Changelog\n\n### 0.8.0\n\n- Report will always be generated even though compare is failing.\n- Small additions: Add error message when files count don't match, use tracing::error instead of println\n- return error when:\n    - folder can't be read in Directory/FileExist Checker\n    - nominal and/or actual is not a folder instead of returning Ok(false)\n\n### 0.7.0\n\n- add file exist checker\n- fix file property checker report that \"Creation date\" of \"nominal\" and \"actual\" columns are switched\n- update thiserror, itertools and pdf-extract crates\n\n### 0.6.1\n\n- Add new version of json-diff, featuring a better API and better filtering options\n\n### 0.6.0\n\n- Add new options for image compare module (a lot of options!)\n- Bump json-compare to new version fixing bugs in regex field excludes and sorting\n\n### 0.5.4\n\n- Add option to run single file mode from CLI\n\n### 0.5.3\n\n- Add option to sort json arrays (including deep sorting)\n- Make single file comparison function to public api\n- Update dependencies, fix broken pdf import regarding whitespaces\n\n### 0.5.2\n\n- Preserve white spaces in CSV and PDF report instead of being collapsed by HTML\n- Add + and - symbols to the report index\n- Display combined file names in report index if the compared file names are different\n\n### 0.5.1\n\n- Fix potential crash in JSON checking\n\n### 0.5.0\n\n- Add JSON checking\n\n### 0.4.0\n\n- Separate reporting logic from comparison logic\n- Implement a machine-readable JSON reporting\n\n### 0.3.2\n\n- Allow direct opening of reports after comparison with `--open`\n- Parsing failures when running `compare` are now propagated to terminal\n\n### 0.3.1\n\n- Fix swapped actual and nominal for hash and text-compares\n\n### 0.3.0\n\n- Allow RGBA image comparison\n- Add file metadata comparison\n- Add external checking\n- Add and adjust reports for file metadata comparison and external checking\n- Add csv header comparison. Previously, the report only marks the differences but not returning any error.\n- Added config yaml validation command to check whether file can be loaded or not\n\n### 0.2.4\n\n- add check for row lines of both compared csv files, and throw error if they are unequal\n- Add deletion by cell\n- Simplify report sub-folders creation: sub-folders are now created temporarily in the temp folder instead of in the\n  current working folder\n- Change report row numbering to always start with 0, so row deletion is more understandable\n- fix floating point value comparison of non-displayable diff values\n\n### 0.2.3\n\n- bump pdf-extract crate to 0.6.4 to fix \"'attempted to leave\n  type `linked_hash_map::Node\u003calloc::vec::Vec\u003cu8\u003e, object::Object\u003e` uninitialized'\"\n\n### 0.2.2\n\n- Include files which has error and can't be compared to the report\n- Fixed a bug which caused the program exited early out of rules-loop, and not processing all\n\n### 0.2.0\n\n- Deletion of columns will no longer really delete them but replace every value with \"DELETED\"\n- Expose config struct to library API\n- Fixed a bug regarding wrong handling of multiple empty lines\n- Reworked CSV reporting to have an interleaved and more compact view\n    - Display the relative path of compared files instead of file name in the report index.html\n    - Made header-extraction fallible but uncritical - can now always be enabled\n- Wrote a completely new csv parser:\n    - Respects escaping with '\\'\n    - Allows string-literals containing unescaped field separators (field1, \"field2, but as literal\", field3)\n    - Allows multi-line string literals with quotes\n- CSVs with non-rectangular format will now fail\n\n### 0.1.4\n\n- Add multiple includes and excludes - warning, this will break rules-files from 0.1.3 and earlier\n- Remove all `unwrap` and `expect` in the library code in favor of correct error propagation\n- Add preprocessing options for CSV files\n- Refined readme.md\n- fix unique key creation in the report generation\n- Add PDF-Text compare\n\n### 0.1.3:\n\n- Add optional cli argument to configure the folder to store the report\n\n### 0.1.2:\n\n- Add SHA-256 comparison mode\n- Fix BOM on windows for CSV comparison\n\n### 0.1.1:\n\n- Better error message on folder not found\n- Better test coverage\n- Fix colors on Windows terminal\n- Extend CI to Windows and mac\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvolumegraphics%2Fhavocompare","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvolumegraphics%2Fhavocompare","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvolumegraphics%2Fhavocompare/lists"}