{"id":19596256,"url":"https://github.com/sirthias/spliff","last_synced_at":"2026-03-16T01:34:42.666Z","repository":{"id":39851308,"uuid":"360440981","full_name":"sirthias/spliff","owner":"sirthias","description":"Efficient diffing in Scala","archived":false,"fork":false,"pushed_at":"2025-11-04T14:14:50.000Z","size":113,"stargazers_count":61,"open_issues_count":1,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-11-22T21:08:38.693Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sirthias.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG","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":"2021-04-22T08:08:09.000Z","updated_at":"2025-11-04T14:14:54.000Z","dependencies_parsed_at":"2023-02-15T13:31:19.407Z","dependency_job_id":"359c8865-c44f-4b79-91db-7e10eeab013d","html_url":"https://github.com/sirthias/spliff","commit_stats":{"total_commits":66,"total_committers":2,"mean_commits":33.0,"dds":0.3484848484848485,"last_synced_commit":"d646385109a4043aa2d6838b1b08f05afdd04c51"},"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/sirthias/spliff","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sirthias%2Fspliff","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sirthias%2Fspliff/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sirthias%2Fspliff/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sirthias%2Fspliff/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sirthias","download_url":"https://codeload.github.com/sirthias/spliff/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sirthias%2Fspliff/sbom","scorecard":{"id":827868,"data":{"date":"2025-08-11","repo":{"name":"github.com/sirthias/spliff","commit":"9e6f471fd1a6fcc60aa6b95efef28be2fb129b95"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.3,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Code-Review","score":2,"reason":"Found 6/24 approved changesets -- score normalized to 2","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Mozilla Public License 2.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 12 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-23T16:58:16.193Z","repository_id":39851308,"created_at":"2025-08-23T16:58:16.193Z","updated_at":"2025-08-23T16:58:16.193Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30558089,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-15T23:30:23.986Z","status":"ssl_error","status_checked_at":"2026-03-15T23:28:43.564Z","response_time":61,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":[],"created_at":"2024-11-11T08:52:34.328Z","updated_at":"2026-03-16T01:34:42.656Z","avatar_url":"https://github.com/sirthias.png","language":"Scala","readme":"spliff\n======\n\n_spliff_ is a small, zero-dependency [Scala] library providing efficient implementations of the diff algorithm and\nsupporting logic presented by _Eugene W. Myers_ in [his 1986 paper \"An O(ND) Difference Algorithm and Its Variations\".][1]\n\nMyers' algorithm is the default diffing logic for many popular tools (like `git diff`) because it performs well\n(time- and memory-wise) and tends to produce diffing results that humans would consider \"good\".\n\nMany improvements and refinements have been proposed over Myers' original algorithm since 1986.    \nThe implementations provided by _spliff_ are based on the [work by Robert Elder][5].\n\n_spliff_ sports these features:\n\n- no dependencies except for the Scala standard library\n- fast (minimal support structures, light on allocations, cheap integer-logic)\n- optional detection of moves and replaces\n- stacksafe (i.e. heap-based rather than stack-based recursion)\n- operates on any data type (not only `String`)\n- customizable, type class based equality logic\n\n_spliff_ is available for [Scala] 2.13, [Scala] 3 and [Scala.js].\n\n\nInstallation\n------------\n\nThe _spliff_ artifacts live on Maven Central and can be tied into your [SBT] project like this:\n\n```scala\nlibraryDependencies ++= Seq(\n  \"io.bullet\" %% \"spliff\" % \"0.8.1\"\n)\n```\n\n\nUsage\n-----\n\nSince information about the differences between two sequences of arbitrary objects are useful in a very broad range of\napplication contexts _spliff_ provides several distinct \"views\" onto the diff data.  \nSimply pick the one(s) that are most suited to the task you are trying to solve:\n\n\n### 1. Basic Operations\n\nOn the most basic level _spliff_ uses Myers' algorithm to describe the difference between two sequences `base` and\n`target` as a number of `Diff.Op.Delete` and `Diff.Op.Insert` operations. Instances of these types can be regarded\nas mere \"decorators\" on the underlying `base` and `target` sequences as they don't hold any data elements themselves.\nThey merely describe in terms of index ranges, which data elements must be deleted or inserted in order to transform\n`base` into `target`.  \nThe least common super type of `Diff.Op.Delete` and `Diff.Op.Insert` is the `Diff.Op.DelIns` trait.\n\nExample:\n\n```scala\nimport io.bullet.spliff.Diff\n\n// create a 'diff' between two `IndexedSeq[T]`\nval diff = Diff(\n  \"the base sequence\",\n  \"the target sequence\"\n)\n\n// all delete operations required to get from `base` to `target`\nval deletes: Seq[Diff.Op.Delete] = diff.deletes\ndeletes ==\u003e ArraySeq(\n  Delete(4, 1),\n  Delete(6, 1)\n)\n\n// all insert operations required to get from `base` to `target`\nval inserts: Seq[Diff.Op.Insert] = diff.inserts\ninserts ==\u003e ArraySeq(\n  Insert(5, 4, 1),\n  Insert(7, 6, 2),\n  Insert(8, 9, 1)\n)\n\n// all deletes and inserts combined\nval delIns: Seq[Diff.Op.DelIns] = diff.delInsOps\nval delInsSorted: Seq[Diff.Op.DelIns] = diff.delInsOpsSorted // same but sorted by index\n```\n\n\n### 2. Higher-Level Operations\n\nOn the next higher-level _spliff_ can refine the basic `Diff.Op.DelIns` operations by identifying `Diff.Op.Move` and,\noptionally, `Diff.Op.Replace` operations. While this raises the semantic level it doesn't change the fundamental\n\"decorator-only\" character of the diff result.\nWithout access to both underlying sequences (`base` and `target`) this representation of the diff is likely of limited\nvalue only.\n\nExample:\n\n```scala\nimport io.bullet.spliff.Diff\n\n// create a 'diff' between two `IndexedSeq[T]`\nval diff = Diff(\n  \"the base sequence\",\n  \"the sequence base !\"\n)\n\n// the diff result as a list of 'delete', 'insert' and 'move' operations\nval delInsMov: Seq[Diff.Op.DelInsMov] = diff.delInsMovOps\nval delInsMovSorted: Seq[Diff.Op.DelInsMov] = diff.delInsMovOpsSorted // same but sorted by index\n\ndelInsMov ==\u003e ArraySeq(\n  Move(2, 16, 5),\n  Insert(17, 17, 2)\n)\n\n// the diff result as a list of 'delete', 'insert', 'move' and 'replace' operations\nval allOps: Seq[Diff.Op] = diff.allOps // already sorted by index\n```\n\n### 3. Patch\n\nBuilding upon the `Diff.Op.DelInsMov` operations _spliff_ can also represent the diff as a `Diff.Patch[T]`.\nA patch holds a compact representation of all data required to reconstruct the target sequence, given only the `base`.\nIn addition to the information about which data are to be deleted from the `base` and/or moved to other positions\na patch must therefore contain the actual data elements that are to be _inserted_. \n\nExample:\n\n```scala\nimport io.bullet.spliff.Diff\n\n// create a 'diff' between two `IndexedSeq[T]`\nval diff = Diff(\n  \"the base sequence\",\n  \"the target sequence\"\n)\n\n// create a batch\nval patch: Diff.Patch[Char] = diff.patch\n\npatch ==\u003e Patch(\n  baseSize = 17,\n  targetSize = 19,\n  steps = ArraySeq(\n    Delete(4,1),\n    Delete(6,1),\n    Insert(5, ArraySeq('t')),\n    Insert(7, ArraySeq('r', 'g')),\n    Insert(8, ArraySeq('t'))\n  )\n)\n\n// apply the patch to the base sequence\nval newTarget = patch.apply(\"the base sequence\")\n\n// yields the original target sequence\nnewTarget ==\u003e Right(\"the target sequence\")\n```\n\n\n### 4. Chunks\n\nIn addition to the above _spliff_ can represent the diff as a sequence of `Diff.Chunk[T]` instances, which partitions\nthe `base` and `target` into a list of segments, each of which holds the respective data elements as well as meta data\nabout where the chunk comes from (only the `base`, only the `target`, both sequences or distinct to each sequence).\nFor some use cases this diff representation is more suitable and directly usable than the more basic operations or\npatches.\n\nExample:\n\n```scala\nimport io.bullet.spliff.Diff\n\n// create a 'diff' between two `IndexedSeq[T]`\nval diff = Diff(\n  \"the base sequence\",\n  \"the target sequence\"\n)\n\n// the diff represented as a sequence of \"chunks\"\nval chunks: Seq[Diff.Chunk[Char]] = diff.chunks\n\nchunks ==\u003e Seq(\n  Diff.Chunk.InBoth(\"the \"),\n  Diff.Chunk.Distinct(\"base\", \"target\"),\n  Diff.Chunk.InBoth(\" sequence\")\n)\n```\n\n\n### 5. Bimap\n\nSometimes you need a (bidirectional) mapping between the indices of `base` and the indices of `target`.\n_spliff_ makes this readily available.\n\nExample:\n\n```scala\nimport io.bullet.spliff.Diff\n\n// create a 'diff' between two `IndexedSeq[T]`\nval diff = Diff(\n  \"the base sequence\",\n  \"the target sequence\"\n)\n\n// a bidirectional mapping between each individual base and target index, where possible\nval bimap: Diff.Bimap = diff.bimap\n\nbimap.baseToTargetIndex(10) ==\u003e Some(12)\nbimap.targetToBaseIndex(12) ==\u003e Some(10)\n```\n\n\n### 6. Longest Common Subsequence and Min Edit Distance\n\nFinally _spliff_ offers these two supporting functions:\n\n```scala\nimport io.bullet.spliff.Diff\n\nval base = \"the base sequence\"\nval target = \"the target sequence\"\n\n// determines the longest subsequence of elements that is present in both sequences\nval lcs: Seq[Char] = Diff.longestCommonSubsequence(base, target)\nlcs.mkString ==\u003e \"the ae sequence\"\n\n// determines the minimum number of edits required to transform `base` into `target`,\n// whereby one \"edit\" corresponds to deleting or inserting one single element\nval distance: Int = Diff.minEditDistance(base, target)\ndistance ==\u003e 6\n```\n\nFor further information and more detailed API documentation just look at the [source code], which is hopefully not\nthat hard to read. (Even though the algorithmic core parts themselves are certainly quite dense.)\n\n\nWhy \"spliff\"?\n-------------\n\nThe name _spliff_ is a [portmanteau] of the words \"split\" and \"difference\" alluding to the core principle of Myers'\nalgorithm, which divides the problem of finding a suitable diff into two parts, that are then solved separately\nand recursively.\n\nThere is, of course, no relationship with other, potentially overloaded meanings of the word \"spliff\".\n\n\nLicense\n-------\n\n_spliff_ is released under the [MPL 2.0][2], which is a simple and modern weak [copyleft][3] license.\n\nHere is the gist of the terms that are likely most important to you (disclaimer: the following points are not legally\nbinding, only the license text itself is):\n\nIf you'd like to use _spliff_ as a library in your own applications:\n\n- **_spliff_ is safe for use in closed-source applications.**\n  The MPL share-alike terms do not apply to applications built on top of or with the help of _spliff_.\n   \n- **You do not need a commercial license.**\n  The MPL applies to _spliff's_ own source code, not your applications.\n\nIf you'd like to contribute to _spliff_:\n\n- You do not have to transfer any copyright.\n\n- You do not have to sign a CLA.\n\n- You can be sure that your contribution will always remain available in open-source form and\n  will not *become* a closed-source commercial product (even though it might be *used* by such products!)\n\nFor more background info on the license please also see the [official MPL 2.0 FAQ][4].\n\n  [Scala]: https://www.scala-lang.org/\n  [SBT]: https://www.scala-sbt.org/\n  [scalafmt]: https://scalameta.org/scalafmt/\n  [Scala.js]: https://www.scala-js.org/\n  [source code]: https://github.com/sirthias/spliff/blob/master/src/main/scala/io/bullet/spliff/Diff.scala\n  [portmanteau]: https://en.wikipedia.org/wiki/Portmanteau\n  [1]: http://www.xmailserver.org/diff2.pdf\n  [2]: https://www.mozilla.org/en-US/MPL/2.0/\n  [3]: http://en.wikipedia.org/wiki/Copyleft\n  [4]: https://www.mozilla.org/en-US/MPL/2.0/FAQ/\n  [5]: https://blog.robertelder.org/diff-algorithm/","funding_links":[],"categories":["Table of Contents"],"sub_categories":["Misc"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsirthias%2Fspliff","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsirthias%2Fspliff","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsirthias%2Fspliff/lists"}