diff --git a/LICENSE b/LICENSE deleted file mode 100644 index c67dad6..0000000 --- a/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2013, Patrick Mezard -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright -notice, this list of conditions and the following disclaimer in the -documentation and/or other materials provided with the distribution. - The names of its contributors may not be used to endorse or promote -products derived from this software without specific prior written -permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS -IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED -TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED -TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/LICENSE-COMMERCIAL.md b/LICENSE-COMMERCIAL.md new file mode 100644 index 0000000..69ffbcc --- /dev/null +++ b/LICENSE-COMMERCIAL.md @@ -0,0 +1,11 @@ +# Coffee Commercial License + +Copyright (c) 2025 Sam Jamshidi Larijani +Permission is hereby granted, upon payment of the required license fee, to any legal entity with a net worth, market cap, or enterprise value of above USD $ 500,000,000 obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. + +This license is subject to the following conditions: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..c783c78 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,31 @@ +# Coffee License + +Copyright (c) 2025 Sam Jamshidi Larijani +Permission is hereby granted, free of charge, to any legal entity with a net worth, market cap, or +enterprise value of below USD $ 500,000,000 obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to +do so. + +Any legal entity with a net worth, market capitalization, or enterprise value of USD $ 500,000,000 or +above must obtain a commercial license (Fee: USD $ 50) prior to use. +Contact : wow@sammakes.art + +This license is subject to the following conditions: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions +and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, this list of +conditions and the following disclaimer in the documentation and/or other materials provided with +the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY +OF SUCH DAMAGE. \ No newline at end of file diff --git a/README.md b/README.md index 348a7cf..c35e3bd 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@ go-difflib ========== -THIS PACKAGE IS NO LONGER MAINTAINED. +THIS PACKAGE IS NO LONGER MAINTAINED BY THE ORIGINAL AUTHOR. At this point, I have no longer the time nor the interest to work on go-difflib. I apologize for the inconvenience. -[![GoDoc](https://godoc.org/github.com/pmezard/go-difflib/difflib?status.svg)](https://godoc.org/github.com/pmezard/go-difflib/difflib) +**UPDATE**: This fork is now minimally maintained as I've started using it in a personal project. Basic functionality and Go compatibility will be preserved. Recent updates add ndiff options, byte-oriented adapters, and intraline hints with junk filters. + +[![GoDoc](https://godoc.org/github.com/codinganovel/go-difflib/difflib?status.svg)](https://godoc.org/github.com/codinganovel/go-difflib/difflib) Go-difflib is a partial port of python 3 difflib package. Its main goal was to make unified and context diff available in pure Go, mostly for @@ -16,13 +18,159 @@ The following class and functions (and related tests) have be ported: * `SequenceMatcher` * `unified_diff()` * `context_diff()` +* `get_close_matches()` (as `GetCloseMatches`) +* `ndiff()` + `restore()` (as `NDiff`, `NDiffWith`, `NDiffBytes`, and `Restore`) +* `Differ` (with intraline "? " hints; honors line/char junk filters) +* Byte adapters for unified/context diffs (`GetUnifiedDiffBytes`, `GetContextDiffBytes`) ## Installation ```bash -$ go get github.com/pmezard/go-difflib/difflib +go get github.com/codinganovel/go-difflib/difflib +``` + +### More Examples + +Close matches (fuzzy suggestions): + +```go +matches := difflib.GetCloseMatches( + "appel", + []string{"ape", "apple", "peach", "puppy"}, + 3, 0.6, +) +// matches => []string{"apple", "ape"} +``` + +Basic ndiff + restore (human-friendly line deltas): + +```go +a := difflib.SplitLines("one\ntwo\nthree\n") +b := difflib.SplitLines("zero\none\nthree\n") +delta := difflib.NDiff(a, b) + +// Reconstruct originals from the delta +a2 := difflib.Restore(delta, 1) // == a +b2 := difflib.Restore(delta, 2) // == b +``` + +Intraline hints with Differ (shows character-level changes using "? "): + +```go +d := &difflib.Differ{} +delta := d.Compare( + difflib.SplitLines("abc\n"), + difflib.SplitLines("axc\n"), +) +// Emits lines prefixed with: +// " " (equal), "- " (delete), "+ " (insert), "? " (intraline guide) +``` + +NDiff with options (line/char junk filters, intraline hints): + +```go +delta := difflib.NDiffWith( + difflib.SplitLines("abc\n"), + difflib.SplitLines("axc\n"), + difflib.NDiffOptions{Intraline: true, CharJunk: difflib.IsCharacterJunk}, +) +``` + +Byte-oriented ndiff (no intraline hints, binary safe): + +```go +out := difflib.NDiffBytes([]byte("one\ntwo\n"), []byte("one\n2\n"), difflib.NDiffOptions{}) +fmt.Print(string(out)) +``` + +Unified/Context diffs (bytes): + +```go +u, _ := difflib.GetUnifiedDiffBytes(difflib.UnifiedDiffBytes{A: a, B: b, FromFile: "A", ToFile: "B", Context: 3}) +c, _ := difflib.GetContextDiffBytes(difflib.ContextDiffBytes{A: a, B: b, FromFile: "A", ToFile: "B", Context: 3, Eol: []byte{'\n'}}) +``` + +### Notes + +- `NDiff` remains a simple, line-only API for compatibility. Use `NDiffWith` for intraline and junk filters. +- `Differ.Compare` honors `LineJunk`/`CharJunk` and emits intraline `"? "` hints. +- Byte adapters avoid decoding and are safe for non‑UTF‑8 data; intraline is disabled in `NDiffBytes`. +- `HtmlDiff` is not yet implemented; see `TO-DO.md` for remaining parity tasks. + +## API + +`NDiff` + +```go +func NDiff(a, b []string) []string ``` +- Emits ndiff-style lines with prefixes: `" "` (equal), `"- "` (delete), `"+ "` (insert). +- Does not include intraline `"? "` guide lines; use `NDiffWith` (with `Intraline: true`) or `Differ` for those. +- Inputs should be `SplitLines(...)` output to preserve end-of-line markers. +- Output is suitable for `Restore` to reconstruct either original sequence. + +`Restore` + +```go +func Restore(delta []string, which int) []string +``` + +- Reconstructs sequence 1 (`which == 1`) or sequence 2 (`which == 2`) from an ndiff delta. +- Ignores lines prefixed with `"? "` if present. +- Returns `nil` for invalid `which` values. + +`Differ` + +```go +type Differ struct { + LineJunk func(string) bool + CharJunk func(rune) bool +} + +func (d *Differ) Compare(a, b []string) []string +``` + +- Produces ndiff-style output, including intraline `"? "` hints for replacements. +- Intraline markers: `^` for replacements, `-` under deletions (aline), `+` under insertions (bline). +- Trailing spaces may appear in `"? "` lines to align markers with characters; examples trim them for readability. +- Current implementation pairs replaced lines greedily; advanced "fancy replace" heuristics are planned. + +`NDiffWith` + +```go +type NDiffOptions struct { + LineJunk func(string) bool + CharJunk func(rune) bool + Intraline bool +} + +func NDiffWith(a, b []string, opts NDiffOptions) []string +``` + +- Like `NDiff`, with optional intraline hints and junk filters applied. +- When `Intraline` is true, `CharJunk` defaults to `IsCharacterJunk` if nil. + +`NDiffBytes` + +```go +func NDiffBytes(a, b []byte, opts NDiffOptions) []byte +``` + +- Byte-safe ndiff; intraline is disabled even if requested. +- Use when inputs are arbitrary bytes or not valid UTF‑8. + +`UnifiedDiffBytes` / `ContextDiffBytes` + +```go +type UnifiedDiffBytes struct { /* ... */ } +func GetUnifiedDiffBytes(diff UnifiedDiffBytes) ([]byte, error) +type ContextDiffBytes = UnifiedDiffBytes +func GetContextDiffBytes(diff ContextDiffBytes) ([]byte, error) +``` + +- Byte-oriented wrappers over unified/context diffs; preserve formatting and headers. + ### Quick Start Diffs are configured with Unified (or ContextDiff) structures, and can @@ -50,4 +198,3 @@ would output: -bar +baz ``` - diff --git a/THIRD-PARTY-LICENSES.md b/THIRD-PARTY-LICENSES.md new file mode 100644 index 0000000..5eab33d --- /dev/null +++ b/THIRD-PARTY-LICENSES.md @@ -0,0 +1,633 @@ +# Third-Party License Information + +This document contains license information for all third-party dependencies used across Go and Python projects in this repository. + +Generated on: 2025-07-29 + +--- + +## Go Dependencies + +### github.com/codinganovel/autocd-go + +**License**: Coffee License +**Repository**: https://github.com/codinganovel/autocd-go +**Description**: A Go library that implements directory inheritance for command-line applications, allowing applications to "inherit" their final directory to the shell when they exit. + +``` +Coffee License + +Copyright (c) 2025 Sam Jamshidi Larijani +Permission is hereby granted, free of charge, to any legal entity with a net worth, market cap, or +enterprise value of below USD $ 500,000,000 obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to +do so. + +Any legal entity with a net worth, market capitalization, or enterprise value of USD $ 500,000,000 or +above must obtain a commercial license (Fee: USD $ 50) prior to use. +Contact : wow@sammakes.art + +This license is subject to the following conditions: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions +and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, this list of +conditions and the following disclaimer in the documentation and/or other materials provided with +the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY +OF SUCH DAMAGE. +``` + + +### github.com/gdamore/tcell/v2 + +**License**: Apache-2.0 +**Repository**: https://github.com/gdamore/tcell +**Versions Used**: v2.7.0, v2.7.4, v2.8.1 +**Description**: A Go package for creating text-based terminal interfaces with pure Go implementation, rich Unicode support, enhanced color handling (24-bit color), and cross-platform compatibility (Linux, Windows, WASM). + +``` +Copyright 2015 The TCell Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +``` + +### github.com/spf13/cobra + +**License**: Apache-2.0 +**Repository**: https://github.com/spf13/cobra +**Versions Used**: v1.8.0, v1.9.1 +**Description**: A library for creating powerful modern CLI applications with subcommand-based interfaces, POSIX-compliant flags, automatic help generation, and shell autocomplete support. Used by Kubernetes, Hugo, and GitHub CLI. + +``` +Copyright 2013-2023 The Cobra Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +``` + +### github.com/sahilm/fuzzy + +**License**: MIT +**Repository**: https://github.com/sahilm/fuzzy +**Version Used**: v0.1.1 +**Description**: A Go library for fuzzy string matching optimized for filenames and code symbols, providing intuitive matching with quality scoring, fast performance, and Unicode awareness. + +``` +MIT License + +Copyright (c) 2017 Sahil Muthoo + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +### github.com/gdamore/encoding + +**License**: Apache-2.0 +**Repository**: https://github.com/gdamore/encoding +**Versions Used**: v1.0.0, v1.0.1 +**Description**: A Go library providing character map encodings missing from the standard Go encoding package, helping handle I/O streams from non-UTF friendly sources. + +``` +Copyright 2015 The Encoding Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +``` + +### github.com/lucasb-eyer/go-colorful + +**License**: MIT +**Repository**: https://github.com/lucasb-eyer/go-colorful +**Version Used**: v1.2.0 +**Description**: A library for playing with colors in Go, supporting multiple color spaces (RGB, HSL, HSV, CIE-XYZ, CIE-L*a*b*), color conversion, blending, and palette generation. + +``` +MIT License + +Copyright (c) 2013 Lucas Beyer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +### github.com/mattn/go-runewidth + +**License**: MIT +**Repository**: https://github.com/mattn/go-runewidth +**Versions Used**: v0.0.15, v0.0.16 +**Description**: A Go package that provides functions to get fixed width of characters or strings, particularly useful for multi-byte character width calculations in terminal applications. + +``` +MIT License + +Copyright (c) 2013 Yasuhiro Matsumoto + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +### github.com/rivo/uniseg + +**License**: MIT +**Repository**: https://github.com/rivo/uniseg +**Versions Used**: v0.4.3, v0.4.7 +**Description**: A Go package for Unicode text processing that handles grapheme cluster segmentation, word and sentence boundary detection, line breaking, and monospace string width calculation according to Unicode standards. + +``` +MIT License + +Copyright (c) 2019 Oliver Kuederle + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +### github.com/inconshreveable/mousetrap + +**License**: Apache-2.0 +**Repository**: https://github.com/inconshreveable/mousetrap +**Version Used**: v1.1.0 +**Description**: A tiny Go library that detects if a Windows process was started by double-clicking an executable in Windows Explorer, helping provide better user experience for CLI tools. + +``` +Copyright 2014 Alan Shreve + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +``` + +### github.com/spf13/pflag + +**License**: BSD-3-Clause +**Repository**: https://github.com/spf13/pflag +**Versions Used**: v1.0.5, v1.0.6 +**Description**: A drop-in replacement for Go's standard flag package, implementing POSIX/GNU-style command-line flags with support for shorthand flags, custom normalization, and flag deprecation. + +``` +Copyright (c) 2012 Alex Ogier. All rights reserved. +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + +### github.com/kylelemons/godebug + +**License**: Apache-2.0 +**Repository**: https://github.com/kylelemons/godebug +**Version Used**: v1.1.0 +**Description**: A Go library for pretty-printing data structures with indentation, useful for debugging and visualizing complex data structures in unit tests and development. + +``` +Copyright 2013 Kyle Lemons + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +``` + +### golang.org/x/sys + +**License**: BSD-3-Clause +**Repository**: https://pkg.go.dev/golang.org/x/sys +**Description**: Supplemental Go packages for low-level interactions with the operating system, providing OS-related functionality like CPU feature detection and system calls. + +``` +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + +### golang.org/x/term + +**License**: BSD-3-Clause +**Repository**: https://pkg.go.dev/golang.org/x/term +**Description**: Package providing support functions for dealing with terminals on UNIX systems, including terminal raw mode, password reading, and terminal state management. + +*Uses the same BSD-3-Clause license as golang.org/x/sys* + +### golang.org/x/text + +**License**: BSD-3-Clause +**Repository**: https://pkg.go.dev/golang.org/x/text +**Description**: Repository of text-related packages for internationalization (i18n) and localization (l10n), including character encodings, text transformations, and locale-specific text handling. + +*Uses the same BSD-3-Clause license as golang.org/x/sys* + +### golang.org/x/crypto + +**License**: BSD-3-Clause +**Repository**: https://pkg.go.dev/golang.org/x/crypto +**Description**: Supplementary Go cryptography packages providing various cryptographic implementations including encryption algorithms, hash functions, key derivation functions, and cryptographic protocols. + +*Uses the same BSD-3-Clause license as golang.org/x/sys* + +--- + +## Python Dependencies + +### textual + +**License**: MIT +**Repository**: https://github.com/Textualize/textual +**Description**: A Python framework for building cross-platform user interfaces that can run in terminals and web browsers, featuring a simple Python API, comprehensive widget library, and flexible layout system. + +``` +MIT License + +Copyright (c) 2021 Will McGugan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +### pyperclip + +**License**: BSD-3-Clause +**Repository**: https://github.com/asweigart/pyperclip +**Description**: A cross-platform Python module for copy and paste clipboard functions that works with Python 2 and 3, supporting Windows, Mac, and Linux for plaintext clipboard operations. + +``` +BSD 3-Clause License + +Copyright (c) 2010, Albert Sweigart +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + +### rapidfuzz + +**License**: MIT +**Repository**: https://github.com/maxbachmann/RapidFuzz +**Description**: A fast string matching library for Python and C++ that improves upon FuzzyWuzzy with better performance, additional string metrics, and MIT licensing. Written primarily in C++ for speed. + +``` +MIT License + +Copyright (c) 2020 Max Bachmann + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +### openai + +**License**: Apache-2.0 +**Repository**: https://github.com/openai/openai-python +**Description**: The OpenAI Python library providing convenient access to the OpenAI REST API for Python 3.8+ applications, with type definitions for all request params and response fields, supporting both synchronous and asynchronous clients. + +``` +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +``` + +### rich + +**License**: MIT +**Repository**: https://github.com/Textualize/rich +**Description**: A Python library for rich text and beautiful formatting in the terminal, providing colorful console output, pretty printing, syntax highlighting, progress bars, tables, and Markdown rendering. + +``` +MIT License + +Copyright (c) 2020 Will McGugan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +### prompt-toolkit + +**License**: BSD-3-Clause +**Repository**: https://github.com/prompt-toolkit/python-prompt-toolkit +**Description**: A Python library for building powerful interactive command line applications with features like syntax highlighting, multi-line input editing, advanced code completion, Emacs and Vi key bindings, and Unicode support. + +``` +BSD 3-Clause License + +Copyright (c) 2014, Jonathan Slenders +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + +--- + +github.com/pmezard/go-difflib +License: BSD-3-Clause +Repository: https://github.com/pmezard/go-difflib +Description: A Go library for computing differences between sequences, similar to Python’s difflib, supporting unified and context diff output formats. +Copyright (c) 2013, Patrick Mezard +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + The names of its contributors may not be used to endorse or promote +products derived from this software without specific prior written +permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--- + +## License Summary + +### License Distribution +- **Apache-2.0**: 6 dependencies (tcell, cobra, encoding, mousetrap, godebug, openai) +- **MIT**: 7 dependencies (fuzzy, go-colorful, go-runewidth, uniseg, textual, rich, rapidfuzz) +- **BSD-3-Clause**: 7 dependencies (pflag, golang.org/x/* packages, pyperclip, prompt-toolkit) +- **Coffee License**: 1 dependency (autocd-go) + +### Important Notes + +1. **Optional Dependencies**: Many Python projects are designed to work with optional dependencies (pyperclip, rapidfuzz, etc.), gracefully degrading functionality when packages aren't available. This means users can run the tools even without installing all dependencies. + +2. **Coffee License Dependency**: The `autocd-go` library uses The Coffee License, which allows free use for entities under $500M valuation and requires a $50 commercial license for larger entities. + +3. **Golang Extended Packages**: All `golang.org/x/*` packages are part of the Go extended standard library and use BSD-3-Clause licensing. + +4. **Commercial Use**: Most dependencies use permissive licenses (Apache-2.0, MIT, BSD-3-Clause) that allow commercial use without restrictions. Only `autocd-go` has valuation-based licensing terms. + +For the most up-to-date license information, always check the official repositories listed above. \ No newline at end of file diff --git a/difflib/difflib.go b/difflib/difflib.go index 003e99f..1506cda 100644 --- a/difflib/difflib.go +++ b/difflib/difflib.go @@ -20,7 +20,9 @@ import ( "bytes" "fmt" "io" + "sort" "strings" + "unicode/utf8" ) func min(a, b int) int { @@ -127,9 +129,6 @@ func (m *SequenceMatcher) SetSeqs(a, b []string) { // // See also SetSeqs() and SetSeq2(). func (m *SequenceMatcher) SetSeq1(a []string) { - if &a == &m.a { - return - } m.a = a m.matchingBlocks = nil m.opCodes = nil @@ -138,9 +137,6 @@ func (m *SequenceMatcher) SetSeq1(a []string) { // Set the second sequence to be compared. The first sequence to be compared is // not changed. func (m *SequenceMatcher) SetSeq2(b []string) { - if &b == &m.b { - return - } m.b = b m.matchingBlocks = nil m.opCodes = nil @@ -199,12 +195,15 @@ func (m *SequenceMatcher) isBJunk(s string) bool { // If IsJunk is not defined: // // Return (i,j,k) such that a[i:i+k] is equal to b[j:j+k], where -// alo <= i <= i+k <= ahi -// blo <= j <= j+k <= bhi +// +// alo <= i <= i+k <= ahi +// blo <= j <= j+k <= bhi +// // and for all (i',j',k') meeting those conditions, -// k >= k' -// i <= i' -// and if i == i', j <= j' +// +// k >= k' +// i <= i' +// and if i == i', j <= j' // // In other words, of all maximal matching blocks, return one that // starts earliest in a, and of all those maximal matching blocks that @@ -635,7 +634,7 @@ func WriteUnifiedDiff(writer io.Writer, diff UnifiedDiff) error { func GetUnifiedDiffString(diff UnifiedDiff) (string, error) { w := &bytes.Buffer{} err := WriteUnifiedDiff(w, diff) - return string(w.Bytes()), err + return w.String(), err } // Convert range to the "ed" format. @@ -760,13 +759,660 @@ func WriteContextDiff(writer io.Writer, diff ContextDiff) error { func GetContextDiffString(diff ContextDiff) (string, error) { w := &bytes.Buffer{} err := WriteContextDiff(w, diff) - return string(w.Bytes()), err + return w.String(), err +} + +// UnifiedDiffBytes mirrors UnifiedDiff but takes raw byte content and Eol as bytes. +type UnifiedDiffBytes struct { + A []byte + FromFile string + FromDate string + B []byte + ToFile string + ToDate string + Eol []byte + Context int +} + +// WriteUnifiedDiffBytes is a byte-oriented adapter that splits inputs on '\n' +// (preserving newlines), delegates to WriteUnifiedDiff, and writes byte output. +func WriteUnifiedDiffBytes(w io.Writer, diff UnifiedDiffBytes) error { + a := SplitLinesBytes(diff.A) + b := SplitLinesBytes(diff.B) + + // Convert to []string without interpreting as UTF-8. + as := make([]string, len(a)) + for i := range a { + as[i] = string(a[i]) + } + bs := make([]string, len(b)) + for i := range b { + bs[i] = string(b[i]) + } + + sd := UnifiedDiff{ + A: as, + FromFile: diff.FromFile, + FromDate: diff.FromDate, + B: bs, + ToFile: diff.ToFile, + ToDate: diff.ToDate, + Eol: string(diff.Eol), + Context: diff.Context, + } + // Use WriteUnifiedDiff into a bytes.Buffer, then write raw bytes to w. + var buf bytes.Buffer + if err := WriteUnifiedDiff(&buf, sd); err != nil { + return err + } + _, err := w.Write(buf.Bytes()) + return err +} + +// GetUnifiedDiffBytes is like WriteUnifiedDiffBytes but returns the diff as bytes. +func GetUnifiedDiffBytes(diff UnifiedDiffBytes) ([]byte, error) { + var buf bytes.Buffer + if err := WriteUnifiedDiffBytes(&buf, diff); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +// ContextDiffBytes mirrors ContextDiff/UnifiedDiff but takes raw bytes. +type ContextDiffBytes UnifiedDiffBytes + +// WriteContextDiffBytes is a byte-oriented adapter for context diffs. +func WriteContextDiffBytes(w io.Writer, diff ContextDiffBytes) error { + a := SplitLinesBytes(diff.A) + b := SplitLinesBytes(diff.B) + + as := make([]string, len(a)) + for i := range a { + as[i] = string(a[i]) + } + bs := make([]string, len(b)) + for i := range b { + bs[i] = string(b[i]) + } + + sd := ContextDiff{ + A: as, + FromFile: diff.FromFile, + FromDate: diff.FromDate, + B: bs, + ToFile: diff.ToFile, + ToDate: diff.ToDate, + Eol: string(diff.Eol), + Context: diff.Context, + } + var buf bytes.Buffer + if err := WriteContextDiff(&buf, sd); err != nil { + return err + } + _, err := w.Write(buf.Bytes()) + return err +} + +// GetContextDiffBytes returns a context diff as bytes. +func GetContextDiffBytes(diff ContextDiffBytes) ([]byte, error) { + var buf bytes.Buffer + if err := WriteContextDiffBytes(&buf, diff); err != nil { + return nil, err + } + return buf.Bytes(), nil } // Split a string on "\n" while preserving them. The output can be used // as input for UnifiedDiff and ContextDiff structures. func SplitLines(s string) []string { lines := strings.SplitAfter(s, "\n") - lines[len(lines)-1] += "\n" + // Ensure the last element always ends with a trailing newline. + if len(lines) > 0 && !strings.HasSuffix(lines[len(lines)-1], "\n") { + lines[len(lines)-1] += "\n" + } return lines } + +// SplitLinesBytes splits on '\n' while preserving them and ensures the last +// element ends with a trailing '\n', mirroring SplitLines semantics. +func SplitLinesBytes(b []byte) [][]byte { + parts := bytes.SplitAfter(b, []byte{'\n'}) + parts[len(parts)-1] = append(parts[len(parts)-1], '\n') + return parts +} + +// IsLineJunk reports whether a line is ignorable: blank or containing only a single '#', +// possibly surrounded by whitespace. Trailing "\n" is ignored for the purpose of this check. +// This mirrors Python difflib's IS_LINE_JUNK default. +func IsLineJunk(line string) bool { + line = strings.TrimSuffix(line, "\n") + t := strings.TrimSpace(line) + return t == "" || t == "#" +} + +// IsCharacterJunk reports whether a rune is ignorable: a space or a tab. +// This mirrors Python difflib's IS_CHARACTER_JUNK default. +func IsCharacterJunk(r rune) bool { return r == ' ' || r == '\t' } + +// GetCloseMatches returns a list (up to n) of the best "good enough" matches +// from possibilities for the given word. Only candidates scoring at least +// cutoff (in [0.0, 1.0]) are returned, ordered by similarity descending and +// then by original order for ties. If n <= 0 or cutoff is outside [0,1], +// it returns an empty slice. +func GetCloseMatches(word string, possibilities []string, n int, cutoff float64) []string { + if n <= 0 || cutoff < 0.0 || cutoff > 1.0 { + return nil + } + + // Helper to split into runes as []string elements for SequenceMatcher. + splitRunes := func(s string) []string { + // Allocate approximately len(s) elements; final may be smaller for multibyte runes. + out := make([]string, 0, len(s)) + for _, r := range s { + out = append(out, string(r)) + } + return out + } + + mb := splitRunes(word) + m := NewMatcher(nil, mb) + + type cand struct { + score float64 + idx int + val string + } + results := make([]cand, 0, len(possibilities)) + for i, p := range possibilities { + ma := splitRunes(p) + m.SetSeq1(ma) + if m.RealQuickRatio() >= cutoff && m.QuickRatio() >= cutoff { + s := m.Ratio() + if s >= cutoff { + results = append(results, cand{score: s, idx: i, val: p}) + } + } + } + + sort.SliceStable(results, func(i, j int) bool { + if results[i].score == results[j].score { + return results[i].idx < results[j].idx // preserve original order on ties + } + return results[i].score > results[j].score + }) + + if len(results) > n { + results = results[:n] + } + out := make([]string, len(results)) + for i, c := range results { + out[i] = c.val + } + return out +} + +// NDiff returns a basic human-readable delta between a and b (lists of lines), +// using prefixes similar to Python's difflib.ndiff: +// +// "- " line unique to sequence a +// "+ " line unique to sequence b +// " " line common to both sequences +// +// This basic version does not include intraline "? " guide lines. +func NDiff(a, b []string) []string { + m := NewMatcher(a, b) + var out []string + for _, op := range m.GetOpCodes() { + switch op.Tag { + case 'e': + for _, line := range a[op.I1:op.I2] { + out = append(out, " "+line) + } + case 'd': + for _, line := range a[op.I1:op.I2] { + out = append(out, "- "+line) + } + case 'i': + for _, line := range b[op.J1:op.J2] { + out = append(out, "+ "+line) + } + case 'r': + for _, line := range a[op.I1:op.I2] { + out = append(out, "- "+line) + } + for _, line := range b[op.J1:op.J2] { + out = append(out, "+ "+line) + } + } + } + return out +} + +// NDiffOptions customizes NDiffWith behavior. +// +// LineJunk: optional filter to treat some lines as ignorable during matching +// (e.g., blank lines). When set, it's passed to the line-level matcher. +// CharJunk: optional filter for intraline comparison to ignore characters +// (e.g., spaces or tabs); defaults to IsCharacterJunk if nil. +// Intraline: when true, include "? " guide lines showing intraline changes. +// If false, output matches basic NDiff-style output without guide lines. +type NDiffOptions struct { + LineJunk func(string) bool + CharJunk func(rune) bool + Intraline bool +} + +// NDiffWith returns a human-readable delta between a and b with options. +// It preserves NDiff formatting and, when Intraline is true, adds "? " guide lines +// similar to Python's difflib.ndiff. +func NDiffWith(a, b []string, opts NDiffOptions) []string { + if opts.Intraline { + d := &Differ{LineJunk: opts.LineJunk, CharJunk: opts.CharJunk} + return d.Compare(a, b) + } + // No intraline: keep basic output, but still honor LineJunk for matching. + m := NewMatcherWithJunk(a, b, true, opts.LineJunk) + var out []string + for _, op := range m.GetOpCodes() { + switch op.Tag { + case 'e': + for _, line := range a[op.I1:op.I2] { + out = append(out, " "+line) + } + case 'd': + for _, line := range a[op.I1:op.I2] { + out = append(out, "- "+line) + } + case 'i': + for _, line := range b[op.J1:op.J2] { + out = append(out, "+ "+line) + } + case 'r': + for _, line := range a[op.I1:op.I2] { + out = append(out, "- "+line) + } + for _, line := range b[op.J1:op.J2] { + out = append(out, "+ "+line) + } + } + } + return out +} + +// NDiffBytes is a thin adapter that produces ndiff-style output for byte +// sequences. It splits inputs on '\n' (preserving newlines) and delegates to +// the string-based NDiffWith. Intraline hints are disabled to avoid rune +// decoding of arbitrary bytes; opts.Intraline is ignored. +func NDiffBytes(a, b []byte, opts NDiffOptions) []byte { + al := SplitLinesBytes(a) + bl := SplitLinesBytes(b) + + // Convert to []string without interpreting as UTF-8 (Go strings are raw bytes). + as := make([]string, len(al)) + for i := range al { + as[i] = string(al[i]) + } + bs := make([]string, len(bl)) + for i := range bl { + bs[i] = string(bl[i]) + } + + // Disable intraline for byte mode to stay byte-safe. + opts.Intraline = false + lines := NDiffWith(as, bs, opts) + // Join into a single byte slice. + var buf bytes.Buffer + for _, s := range lines { + buf.WriteString(s) + } + return buf.Bytes() +} + +// Restore reconstructs one of the original sequences (1 or 2) from an ndiff +// style delta (as produced by NDiff). Lines starting with "? " are ignored if +// present. Returns nil if 'which' is not 1 or 2. +func Restore(delta []string, which int) []string { + if which != 1 && which != 2 { + return nil + } + keepPrefix := " " + wantPrefix := "- " + if which == 2 { + wantPrefix = "+ " + } + var out []string + for _, d := range delta { + if len(d) < 2 { + continue + } + prefix := d[:2] + if prefix == keepPrefix || prefix == wantPrefix { + out = append(out, d[2:]) + } + // ignore other prefixes (e.g., "+ " when which==1, "- " when which==2, and "? ") + } + return out +} + +// Differ produces human-readable deltas from sequences of lines of text. +// It can emit intraline "? " guide lines highlighting character-level changes. +type Differ struct { + // LineJunk filters ignorable lines. Currently unused in Compare scaffolding. + LineJunk func(string) bool + // CharJunk filters ignorable characters when computing intraline hints. + // Currently unused; placeholder for parity with Python API. + CharJunk func(rune) bool + // Fancy enables smarter splitting of 'replace' blocks. Default false to + // preserve historical greedy behavior unless opted in. + Fancy bool + // PreserveWS maps intraline guide spaces back to original whitespace + // (spaces/tabs) for tab-aware alignment. Default false. + PreserveWS bool + // TabSize reserved for future _qformat parity; not used yet. + TabSize int +} + +// Compare returns an ndiff-style delta including intraline hints for replacements. +// Prefixes: +// +// "- " line unique to sequence a +// "+ " line unique to sequence b +// " " line common to both sequences +// "? " intraline difference guides (only for replacements) +func (d *Differ) Compare(a, b []string) []string { + // Honor LineJunk by passing it to the matcher when provided. + var m *SequenceMatcher + if d.LineJunk != nil { + m = NewMatcherWithJunk(a, b, true, d.LineJunk) + } else { + m = NewMatcher(a, b) + } + var out []string + for _, op := range m.GetOpCodes() { + switch op.Tag { + case 'e': + for _, line := range a[op.I1:op.I2] { + out = append(out, " "+line) + } + case 'd': + for _, line := range a[op.I1:op.I2] { + out = append(out, "- "+line) + } + case 'i': + for _, line := range b[op.J1:op.J2] { + out = append(out, "+ "+line) + } + case 'r': + if d.Fancy { + // Use a fancier replacement to find good sync pairs inside the block. + out = append(out, d.fancyReplace(a[op.I1:op.I2], b[op.J1:op.J2])...) + break + } + // Greedy 1:1 pairing (original behavior) + as := a[op.I1:op.I2] + bs := b[op.J1:op.J2] + n := min(len(as), len(bs)) + for i := 0; i < n; i++ { + aline := as[i] + bline := bs[i] + atags, btags := buildIntralineTagsWith(aline, bline, d.CharJunk) + if d.PreserveWS { + sa := strings.TrimSuffix(aline, "\n") + sb := strings.TrimSuffix(bline, "\n") + atags = keepOriginalWhitespace(atags, sa) + btags = keepOriginalWhitespace(btags, sb) + } + // Ensure guides align to full line width like Python _qformat output + atags = padTag(atags, aline) + btags = padTag(btags, bline) + out = append(out, "- "+aline) + if atags != "" { + out = append(out, "? "+atags+"\n") + } + out = append(out, "+ "+bline) + if btags != "" { + out = append(out, "? "+btags+"\n") + } + } + for _, line := range as[n:] { + out = append(out, "- "+line) + } + for _, line := range bs[n:] { + out = append(out, "+ "+line) + } + } + } + return out +} + +// fancyReplace tries to split a replacement block into intuitive chunks by +// pairing lines with the highest character-level similarity (using the same +// junk filters as intraline tagging). Falls back to greedy pairing when no +// sufficiently good sync point is found. +func (d *Differ) fancyReplace(as, bs []string) []string { + // Fast paths + if len(as) == 0 { + var out []string + for _, line := range bs { + out = append(out, "+ "+line) + } + return out + } + if len(bs) == 0 { + var out []string + for _, line := range as { + out = append(out, "- "+line) + } + return out + } + if len(as) == 1 && len(bs) == 1 { + aline, bline := as[0], bs[0] + atags, btags := buildIntralineTagsWith(aline, bline, d.CharJunk) + if d.PreserveWS { + sa := strings.TrimSuffix(aline, "\n") + sb := strings.TrimSuffix(bline, "\n") + atags = keepOriginalWhitespace(atags, sa) + btags = keepOriginalWhitespace(btags, sb) + } + // Ensure guides align to full line width like Python _qformat output + atags = padTag(atags, aline) + btags = padTag(btags, bline) + out := []string{"- " + aline} + if atags != "" { + out = append(out, "? "+atags+"\n") + } + out = append(out, "+ "+bline) + if btags != "" { + out = append(out, "? "+btags+"\n") + } + return out + } + + // Find best matching pair across as x bs using character-level similarity. + // Threshold chosen to mirror Python's difflib behavior approximately. + const minRatio = 0.74 + bestRatio := 0.0 + bestI, bestJ := -1, -1 + + charRatio := func(x, y string) float64 { + // Split into runes as []string for SequenceMatcher. + split := func(s string) []string { + out := make([]string, 0, len(s)) + for _, r := range s { + out = append(out, string(r)) + } + return out + } + ma := split(x) + mb := split(y) + // Use provided CharJunk if any. + var cj = d.CharJunk + if cj == nil { + cj = IsCharacterJunk + } + isJunk := func(s string) bool { + r, _ := utf8.DecodeRuneInString(s) + return cj(r) + } + sm := NewMatcherWithJunk(ma, mb, true, isJunk) + return sm.Ratio() + } + + for i := range as { + for j := range bs { + r := charRatio(as[i], bs[j]) + if r > bestRatio { + bestRatio = r + bestI, bestJ = i, j + if r == 1.0 { + break + } + } + } + } + + // If no good anchor, fall back to greedy 1:1 pairing like before. + if bestRatio < minRatio || bestI < 0 || bestJ < 0 { + var out []string + n := min(len(as), len(bs)) + for i := 0; i < n; i++ { + aline := as[i] + bline := bs[i] + atags, btags := buildIntralineTagsWith(aline, bline, d.CharJunk) + if d.PreserveWS { + sa := strings.TrimSuffix(aline, "\n") + sb := strings.TrimSuffix(bline, "\n") + atags = keepOriginalWhitespace(atags, sa) + btags = keepOriginalWhitespace(btags, sb) + } + // Ensure guides align to full line width like Python _qformat output + atags = padTag(atags, aline) + btags = padTag(btags, bline) + out = append(out, "- "+aline) + if atags != "" { + out = append(out, "? "+atags+"\n") + } + out = append(out, "+ "+bline) + if btags != "" { + out = append(out, "? "+btags+"\n") + } + } + for _, line := range as[n:] { + out = append(out, "- "+line) + } + for _, line := range bs[n:] { + out = append(out, "+ "+line) + } + return out + } + + // Recurse around the best sync point. + var out []string + out = append(out, d.fancyReplace(as[:bestI], bs[:bestJ])...) + out = append(out, d.fancyReplace(as[bestI:bestI+1], bs[bestJ:bestJ+1])...) + out = append(out, d.fancyReplace(as[bestI+1:], bs[bestJ+1:])...) + return out +} + +// buildIntralineTags returns tag strings (without trailing newline) for aline and bline, +// using '^' for replacements, '-' for deletions (aline only) and '+' for insertions (bline only). +func buildIntralineTagsWith(aline, bline string, charJunk func(rune) bool) (string, string) { + // Strip trailing newlines for alignment purposes + aline = strings.TrimSuffix(aline, "\n") + bline = strings.TrimSuffix(bline, "\n") + + split := func(s string) []string { + out := make([]string, 0, len(s)) + for _, r := range s { + out = append(out, string(r)) + } + return out + } + ma := split(aline) + mb := split(bline) + // Default to IsCharacterJunk if not provided. + var cj = charJunk + if cj == nil { + cj = IsCharacterJunk + } + isJunk := func(s string) bool { + r, _ := utf8.DecodeRuneInString(s) + return cj(r) + } + sm := NewMatcherWithJunk(ma, mb, true, isJunk) + var atags, btags strings.Builder + for _, oc := range sm.GetOpCodes() { + switch oc.Tag { + case 'e': + for i := 0; i < oc.I2-oc.I1; i++ { + atags.WriteByte(' ') + } + for j := 0; j < oc.J2-oc.J1; j++ { + btags.WriteByte(' ') + } + case 'r': + for i := 0; i < oc.I2-oc.I1; i++ { + atags.WriteByte('^') + } + for j := 0; j < oc.J2-oc.J1; j++ { + btags.WriteByte('^') + } + case 'd': + for i := 0; i < oc.I2-oc.I1; i++ { + atags.WriteByte('-') + } + case 'i': + for j := 0; j < oc.J2-oc.J1; j++ { + btags.WriteByte('+') + } + } + } + aTag := atags.String() + bTag := btags.String() + if strings.TrimSpace(aTag) == "" { + aTag = "" + } + if strings.TrimSpace(bTag) == "" { + bTag = "" + } + return aTag, bTag +} + +// keepOriginalWhitespace maps spaces in the tag string back to the original +// whitespace characters (space or tab) from the corresponding line, so that +// the intraline guide aligns across tabs without expanding the original text. +func keepOriginalWhitespace(tag, line string) string { + if tag == "" { + return tag + } + // Work on copies without trailing newlines (already stripped by caller). + // Iterate runes so tag length (built per-rune) matches positions. + // If tag has a blank at position i and the original rune is space or tab, + // copy the original whitespace rune into the tag at that position. + out := []rune(tag) + lrunes := []rune(line) + n := min(len(out), len(lrunes)) + for i := 0; i < n; i++ { + if out[i] == ' ' { + if lrunes[i] == ' ' || lrunes[i] == '\t' { + out[i] = lrunes[i] + } + } + } + return string(out) +} + +// padTag extends tag with spaces to match the rune length of line (without trailing '\n'). +func padTag(tag, line string) string { + lt := []rune(tag) + lr := []rune(strings.TrimSuffix(line, "\n")) + if len(lt) >= len(lr) { + return tag + } + var b strings.Builder + b.WriteString(tag) + for i := len(lt); i < len(lr); i++ { + b.WriteByte(' ') + } + return b.String() +} diff --git a/difflib/difflib_test.go b/difflib/difflib_test.go index d725119..69f593d 100644 --- a/difflib/difflib_test.go +++ b/difflib/difflib_test.go @@ -46,7 +46,7 @@ func TestGetOptCodes(t *testing.T) { fmt.Fprintf(w, "%s a[%d:%d], (%s) b[%d:%d] (%s)\n", string(op.Tag), op.I1, op.I2, a[op.I1:op.I2], op.J1, op.J2, b[op.J1:op.J2]) } - result := string(w.Bytes()) + result := w.String() expected := `d a[0:1], (q) b[0:0] () e a[1:3], (ab) b[0:2] (ab) r a[3:4], (x) b[2:3] (y) @@ -81,7 +81,7 @@ func TestGroupedOpCodes(t *testing.T) { op.I1, op.I2, op.J1, op.J2) } } - result := string(w.Bytes()) + result := w.String() expected := `group e, 5, 8, 5, 8 i, 8, 8, 8, 9 @@ -102,7 +102,7 @@ group } } -func ExampleGetUnifiedDiffCode() { +func ExampleWriteUnifiedDiff() { a := `one two three @@ -135,7 +135,7 @@ four` // -fmt.Printf("%s,%T",a,b) } -func ExampleGetContextDiffCode() { +func ExampleWriteContextDiff() { a := `one two three @@ -207,6 +207,97 @@ four` // four } +func ExampleGetUnifiedDiffBytes() { + a := []byte("one\ntwo\nthree\nfour\nfmt.Printf(\"%s,%T\",a,b)") + b := []byte("zero\none\nthree\nfour") + diff := UnifiedDiffBytes{ + A: a, + B: b, + FromFile: "Original", + FromDate: "2005-01-26 23:30:50", + ToFile: "Current", + ToDate: "2010-04-02 10:20:52", + Context: 3, + } + out, _ := GetUnifiedDiffBytes(diff) + // Print as string for example purposes (raw bytes are fine here). + fmt.Println(strings.Replace(string(out), "\t", " ", -1)) + // Output: + // --- Original 2005-01-26 23:30:50 + // +++ Current 2010-04-02 10:20:52 + // @@ -1,5 +1,4 @@ + // +zero + // one + // -two + // three + // four + // -fmt.Printf("%s,%T",a,b) +} + +func TestUnifiedDiffBytes_EqualsStringUnified_ASCII(t *testing.T) { + a := []byte("one\ntwo\nthree\nfour\n") + b := []byte("zero\none\nthree\nfour\n") + by, err := GetUnifiedDiffBytes(UnifiedDiffBytes{A: a, B: b, FromFile: "A", ToFile: "B", Context: 3}) + if err != nil { + t.Fatalf("bytes unified diff error: %v", err) + } + st, err := GetUnifiedDiffString(UnifiedDiff{A: SplitLines(string(a)), B: SplitLines(string(b)), FromFile: "A", ToFile: "B", Context: 3}) + if err != nil { + t.Fatalf("string unified diff error: %v", err) + } + if string(by) != st { + t.Fatalf("unified bytes != string\nGot:\n%s\nWant:\n%s", string(by), st) + } +} + +func TestContextDiffBytes_EqualsStringContext_ASCII(t *testing.T) { + a := []byte("one\ntwo\nthree\nfour\n") + b := []byte("zero\none\nthree\nfour\n") + by, err := GetContextDiffBytes(ContextDiffBytes{A: a, B: b, FromFile: "A", ToFile: "B", Context: 3}) + if err != nil { + t.Fatalf("bytes context diff error: %v", err) + } + st, err := GetContextDiffString(ContextDiff{A: SplitLines(string(a)), B: SplitLines(string(b)), FromFile: "A", ToFile: "B", Context: 3, Eol: "\n"}) + if err != nil { + t.Fatalf("string context diff error: %v", err) + } + if string(by) != st { + t.Fatalf("context bytes != string\nGot:\n%s\nWant:\n%s", string(by), st) + } +} + +func TestUnifiedDiffBytes_BinarySafety(t *testing.T) { + // Contains NUL and 0xFF bytes + a := []byte{'a', 0x00, 'b', '\n', 0xFF, 'x', '\n'} + b := []byte{'a', 0x00, 'c', '\n', 0xFF, 'x', '\n'} + _, err := GetUnifiedDiffBytes(UnifiedDiffBytes{A: a, B: b, Context: 1}) + if err != nil { + t.Fatalf("unexpected error on binary unified diff: %v", err) + } +} + +func TestContextDiffBytes_BinarySafety(t *testing.T) { + a := []byte{'a', 0x00, 'b', '\n'} + b := []byte{'a', 0x00, 'c', '\n'} + _, err := GetContextDiffBytes(ContextDiffBytes{A: a, B: b, Context: 1, Eol: []byte{'\n'}}) + if err != nil { + t.Fatalf("unexpected error on binary context diff: %v", err) + } +} + +func TestNDiffBytes_BinarySafety(t *testing.T) { + a := []byte{'x', 0x00, 'y', '\n'} + b := []byte{'x', 0x00, 'z', '\n'} + out := NDiffBytes(a, b, NDiffOptions{}) + if len(out) == 0 { + t.Fatalf("unexpected empty result for binary ndiff") + } + // Should not contain '? ' intraline guides in bytes mode + if strings.Contains(string(out), "? ") { + t.Fatalf("bytes ndiff should not emit intraline guides; got:\n%s", string(out)) + } +} + func rep(s string, count int) string { return strings.Repeat(s, count) } @@ -424,3 +515,276 @@ func BenchmarkSplitLines100(b *testing.B) { func BenchmarkSplitLines10000(b *testing.B) { benchmarkSplitLines(b, 10000) } + +func ExampleGetCloseMatches() { + matches := GetCloseMatches("appel", []string{"ape", "apple", "peach", "puppy"}, 2, 0.6) + fmt.Println(matches) + // Output: + // [apple ape] +} + +func ExampleNDiff() { + a := SplitLines("one\ntwo\nthree") + b := SplitLines("zero\none\nthree") + delta := NDiff(a, b) + fmt.Print(strings.Join(delta, "")) + // Output: + // + zero + // one + // - two + // three +} + +func ExampleNDiffBytes() { + a := []byte("one\ntwo\nthree") + b := []byte("zero\none\nthree") + delta := NDiffBytes(a, b, NDiffOptions{}) + fmt.Print(string(delta)) + // Output: + // + zero + // one + // - two + // three +} + +func ExampleNDiffWith() { + a := SplitLines("abc") + b := SplitLines("axc") + delta := NDiffWith(a, b, NDiffOptions{Intraline: true}) + // Trim trailing spaces on intraline lines for stable example output. + for i, s := range delta { + if strings.HasPrefix(s, "? ") { + s = strings.TrimRight(s, " \n") + "\n" + delta[i] = s + } + } + fmt.Print(strings.Join(delta, "")) + // Output: + // - abc + // ? ^ + // + axc + // ? ^ +} + +func TestNDiffWith_NoIntraline_EqualsNDiff(t *testing.T) { + a := SplitLines("one\ntwo\nthree") + b := SplitLines("zero\none\nthree") + got := NDiffWith(a, b, NDiffOptions{Intraline: false}) + want := NDiff(a, b) + if strings.Join(got, "") != strings.Join(want, "") { + t.Fatalf("NDiffWith (no intraline) != NDiff\nGot:\n%s\nWant:\n%s", strings.Join(got, ""), strings.Join(want, "")) + } +} + +func TestNDiffBytes_EqualsStringNDiff_ASCII(t *testing.T) { + a := []byte("one\ntwo\nthree") + b := []byte("zero\none\nthree") + got := string(NDiffBytes(a, b, NDiffOptions{})) + want := strings.Join(NDiff(SplitLines(string(a)), SplitLines(string(b))), "") + if got != want { + t.Fatalf("NDiffBytes != NDiff (ASCII)\nGot:\n%s\nWant:\n%s", got, want) + } +} + +func TestNDiffBytes_IgnoresIntralineOption(t *testing.T) { + a := []byte("abc") + b := []byte("axc") + // Even if Intraline is requested, bytes mode should not emit '? ' lines. + out := string(NDiffBytes(a, b, NDiffOptions{Intraline: true})) + if strings.Contains(out, "? ") { + t.Fatalf("bytes adapter should not emit intraline guides; got:\n%s", out) + } +} + +func ExampleDiffer_Compare() { + d := &Differ{} + delta := d.Compare(SplitLines("abc"), SplitLines("axc")) + // Trim trailing spaces on intraline lines for stable example output. + for i, s := range delta { + if strings.HasPrefix(s, "? ") { + s = strings.TrimRight(s, " \n") + "\n" + delta[i] = s + } + } + fmt.Print(strings.Join(delta, "")) + // Output: + // - abc + // ? ^ + // + axc + // ? ^ +} + +func TestIsLineJunk(t *testing.T) { + // Blank + assertEqual(t, IsLineJunk("\n"), true) + assertEqual(t, IsLineJunk(" \n"), true) + // Only '#' + assertEqual(t, IsLineJunk(" # \n"), true) + // Non-junk + assertEqual(t, IsLineJunk("hello\n"), false) + assertEqual(t, IsLineJunk("#notjunk\n"), false) +} + +func TestIsCharacterJunk(t *testing.T) { + assertEqual(t, IsCharacterJunk(' '), true) + assertEqual(t, IsCharacterJunk('\t'), true) + assertEqual(t, IsCharacterJunk('\n'), false) + assertEqual(t, IsCharacterJunk('x'), false) +} + +func TestGetCloseMatches(t *testing.T) { + poss := []string{"ape", "apple", "peach", "puppy"} + got := GetCloseMatches("appel", poss, 3, 0.6) + // Expect order by score desc with ties by original order; Python example yields ["apple", "ape"]. + assertEqual(t, got[:2], []string{"apple", "ape"}) + + // Edge cases + assertEqual(t, GetCloseMatches("", poss, 0, 0.6) == nil, true) + assertEqual(t, GetCloseMatches("abc", poss, 3, -0.1) == nil, true) + assertEqual(t, GetCloseMatches("abc", poss, 3, 1.1) == nil, true) + + // High cutoff yields empty + assertEqual(t, len(GetCloseMatches("appel", poss, 3, 0.95)) == 0, true) +} + +func TestNDiffRestoreRoundTrip(t *testing.T) { + a := SplitLines(" 1. Beautiful is better than ugly.\n 2. Explicit is better than implicit.\n 3. Simple is better than complex.\n 4. Complex is better than complicated.\n") + b := SplitLines(" 1. Beautiful is better than ugly.\n 3. Simple is better than complex.\n 4. Complicated is better than complex.\n 5. Flat is better than nested.\n") + + delta := NDiff(a, b) + a2 := Restore(delta, 1) + b2 := Restore(delta, 2) + + assertEqual(t, a2, a) + assertEqual(t, b2, b) + + // Ensure no '? ' lines are present in the basic ndiff output + for _, line := range delta { + if len(line) >= 2 { + p := line[:2] + if p != " " && p != "+ " && p != "- " { + t.Fatalf("unexpected ndiff prefix: %q in line %q", p, line) + } + } + } +} + +func TestDifferRestoreRoundTrip(t *testing.T) { + a := SplitLines("one two three\nalpha beta\n") + b := SplitLines("one tree three\nalpha bet\n") + d := &Differ{} + delta := d.Compare(a, b) + a2 := Restore(delta, 1) + b2 := Restore(delta, 2) + assertEqual(t, a2, a) + assertEqual(t, b2, b) +} + +func TestDifferIntralineSimple(t *testing.T) { + a := SplitLines("abc\n") + b := SplitLines("axc\n") + d := &Differ{} + delta := d.Compare(a, b) + // Allow an optional final equal newline entry (since SplitLines adds a terminal "\n"). + if !(len(delta) == 4 || len(delta) == 5) { + t.Fatalf("unexpected delta length: %d (%v)", len(delta), delta) + } + // First four entries should be the replacement pair with intraline guides. + assertEqual(t, delta[0], "- abc\n") + if !(strings.HasPrefix(delta[1], "? ") && strings.Contains(delta[1], " ^ ") && strings.HasSuffix(delta[1], "\n")) { + t.Fatalf("unexpected intraline for a: %q", delta[1]) + } + assertEqual(t, delta[2], "+ axc\n") + if !(strings.HasPrefix(delta[3], "? ") && strings.Contains(delta[3], " ^ ") && strings.HasSuffix(delta[3], "\n")) { + t.Fatalf("unexpected intraline for b: %q", delta[3]) + } + if len(delta) == 5 { + assertEqual(t, delta[4], " \n") + } +} + +func TestDifferFancyReplace_CrossAlignExact(t *testing.T) { + // Case where the best alignment is cross-wise: one line moves positions. + // Greedy pairing would compare unrelated lines first; fancyReplace should + // find the exact match and split around it. + a := SplitLines("abcd\nwxyz\n") + b := SplitLines("wxyz\nabcq\n") + d := &Differ{Fancy: true} + delta := d.Compare(a, b) + // Expect the exact-match line to be recognized as equal and the others as + // delete/insert; trailing blank line equal (from SplitLines). + want := []string{ + "- abcd\n", + " wxyz\n", + "+ abcq\n", + " \n", + } + assertEqual(t, delta, want) +} + +func TestDifferFancyReplace_MultiLineWithIntraline(t *testing.T) { + // No exact equal lines inside the replacement block; fancyReplace should + // align most-similar pairs and emit intraline guides for character diffs. + a := SplitLines("spam and eggs\nfoo bar baz\nalpha beta") + b := SplitLines("spam n eggs\nalpha bet\nfoo buz baz") + d := &Differ{Fancy: true} + delta := d.Compare(a, b) + + // Helper to confirm order: "- aline" appears before "+ bline" somewhere. + hasOrderedPair := func(aline, bline string) bool { + aidx, bidx := -1, -1 + for i := 0; i < len(delta); i++ { + if delta[i] == "- "+aline+"\n" && aidx < 0 { + aidx = i + } + if delta[i] == "+ "+bline+"\n" && bidx < 0 { + bidx = i + } + } + return aidx >= 0 && bidx >= 0 && aidx < bidx + } + + if !hasOrderedPair("spam and eggs", "spam n eggs") { + t.Fatalf("expected pair for spam line not found; delta:\n%s", strings.Join(delta, "")) + } + if !hasOrderedPair("foo bar baz", "foo buz baz") { + t.Fatalf("expected pair for foo line not found; delta:\n%s", strings.Join(delta, "")) + } + if !hasOrderedPair("alpha beta", "alpha bet") { + t.Fatalf("expected pair for alpha line not found; delta:\n%s", strings.Join(delta, "")) + } + + // Ensure at least one intraline guide was emitted somewhere. + sawGuide := false + for _, s := range delta { + if strings.HasPrefix(s, "? ") { + sawGuide = true + break + } + } + if !sawGuide { + t.Fatalf("expected at least one intraline '? ' guide; delta:\n%s", strings.Join(delta, "")) + } +} + +func TestDifferIntralineTabsPreserved(t *testing.T) { + // Ensure intraline guide preserves tabs in the tag line to match visual columns. + // Example: change after a tab should keep the tab in '? ' line. + a := SplitLines("col1\tvalue\n") + b := SplitLines("col1\tvalux\n") + d := &Differ{Fancy: true, PreserveWS: true} + delta := d.Compare(a, b) + // Expect a replacement with intraline; find '? ' lines and ensure they include a tab. + sawTabInGuide := false + for _, s := range delta { + if strings.HasPrefix(s, "? ") { + if strings.ContainsRune(s, '\t') { + sawTabInGuide = true + break + } + } + } + if !sawTabInGuide { + t.Fatalf("expected intraline guide to preserve tabs; delta:\n%s", strings.Join(delta, "")) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b2df5ed --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/codinganovel/go-difflib + +go 1.16