├── .github ├── pull_request_template.md └── workflows │ └── release.yml ├── .gitignore ├── .goreleaser.yml ├── LICENCE ├── README.md ├── cmd └── diffy.go ├── diffy.go ├── go.mod ├── go.sum └── pkg └── diffy ├── diffy.go ├── split.go └── unified.go /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Overview 2 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | goreleaser: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v3 17 | with: 18 | fetch-depth: 0 19 | - name: Set up Go 20 | uses: actions/setup-go@v3 21 | - name: Run GoReleaser 22 | uses: goreleaser/goreleaser-action@v4 23 | with: 24 | distribution: goreleaser 25 | version: latest 26 | args: release --clean 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GH_PAT }} 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | builds: 2 | - binary: diffy 3 | ldflags: 4 | - -X github.com/ynqa/diffy/cmd.version={{.Version}} 5 | goos: 6 | - darwin 7 | - linux 8 | goarch: 9 | - amd64 10 | 11 | archives: 12 | - format: tar.gz 13 | 14 | brews: 15 | - tap: 16 | owner: ynqa 17 | name: homebrew-tap-archived 18 | homepage: https://github.com/ynqa/diffy/ 19 | description: "Print colored diff more readable" 20 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 diffy authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # diffy 2 | 3 | *diffy* prints diffs like GitHub pull requests (e.g. unified, split), and alternative to [diff](https://linuxjm.osdn.jp/html/gnumaniak/man1/diff.1.html). 4 | 5 | 6 | 7 | 8 | ## Installation 9 | 10 | For MacOS: 11 | 12 | ```bash 13 | $ brew tap ynqa/tap-archived 14 | $ brew install diffy 15 | ``` 16 | 17 | From source codes: 18 | 19 | ```bash 20 | $ go get -u github.com/ynqa/diffy 21 | ``` 22 | 23 | ## Usage 24 | ``` 25 | Print colored diff more readable 26 | 27 | Usage: 28 | diffy [flags] FILE1 FILE2 29 | 30 | Flags: 31 | -c, --context int number of context to print (default 3) 32 | -h, --help help for diffy 33 | --no-header no file name header 34 | -s, --style string output style; one of unified|split (default "unified") 35 | --tab-size int tab stop spacing (default 4) 36 | -v, --version version for diffy 37 | ``` 38 | -------------------------------------------------------------------------------- /cmd/diffy.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | 9 | "github.com/pmezard/go-difflib/difflib" 10 | "github.com/spf13/cobra" 11 | 12 | "github.com/ynqa/diffy/pkg/diffy" 13 | ) 14 | 15 | var ( 16 | context int 17 | noHeader bool 18 | style string 19 | tabSize int 20 | 21 | version = "unversioned" 22 | ) 23 | 24 | func New() *cobra.Command { 25 | cmd := &cobra.Command{ 26 | Use: "diffy [flags] FILE1 FILE2", 27 | Short: "Print colored diff more readable", 28 | RunE: func(cmd *cobra.Command, args []string) error { 29 | if err := validate(args); err != nil { 30 | return err 31 | } 32 | return execute(args) 33 | }, 34 | Version: version, 35 | } 36 | 37 | cmd.Flags().IntVarP(&context, "context", "c", 3, "number of context to print") 38 | cmd.Flags().BoolVar(&noHeader, "no-header", false, "no file name header") 39 | cmd.Flags().StringVarP(&style, "style", "s", "unified", "output style; one of unified|split") 40 | cmd.Flags().IntVar(&tabSize, "tab-size", 4, "tab stop spacing") 41 | return cmd 42 | } 43 | 44 | func fileExists(path string) bool { 45 | _, err := os.Stat(path) 46 | return err == nil 47 | } 48 | 49 | func validate(args []string) error { 50 | if len(args) != 2 { 51 | return errors.New("length for args must be 2") 52 | } 53 | if !fileExists(args[0]) { 54 | return fmt.Errorf("file '%s' was not found", args[0]) 55 | } 56 | if !fileExists(args[1]) { 57 | return fmt.Errorf("file '%s' was not found", args[1]) 58 | } 59 | if context < 1 { 60 | return fmt.Errorf("got context '%d', but it must be positive", context) 61 | } 62 | if tabSize < 1 { 63 | return fmt.Errorf("got tabSize '%d', but it must be positive", tabSize) 64 | } 65 | if style != "unified" && style != "split" { 66 | return fmt.Errorf("got style '%s', but it must be one of unified|split", style) 67 | } 68 | return nil 69 | } 70 | 71 | func execute(args []string) error { 72 | org, err := ioutil.ReadFile(args[0]) 73 | if err != nil { 74 | return err 75 | } 76 | 77 | new, err := ioutil.ReadFile(args[1]) 78 | if err != nil { 79 | return err 80 | } 81 | 82 | diff := difflib.UnifiedDiff{ 83 | A: difflib.SplitLines(string(org)), 84 | B: difflib.SplitLines(string(new)), 85 | FromFile: args[0], 86 | ToFile: args[1], 87 | Context: context, 88 | } 89 | opt := diffy.Option{ 90 | NoHeader: noHeader, 91 | TabSize: tabSize, 92 | SeparatorSymbol: "-", 93 | SeparatorWidth: 4, 94 | SpaceSizeAfterLn: 2, 95 | } 96 | if style == "unified" { 97 | return diffy.WriteUnifiedDiff(os.Stdin, diff, opt) 98 | } else if style == "split" { 99 | return diffy.WriteSplitDiff(os.Stdin, diff, opt) 100 | } 101 | return nil 102 | } 103 | -------------------------------------------------------------------------------- /diffy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/ynqa/diffy/cmd" 7 | ) 8 | 9 | func main() { 10 | cmd := cmd.New() 11 | if err := cmd.Execute(); err != nil { 12 | os.Exit(1) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ynqa/diffy 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/pmezard/go-difflib v1.0.0 7 | github.com/spf13/cobra v1.0.0 8 | gopkg.in/gookit/color.v1 v1.1.6 9 | ) 10 | 11 | require ( 12 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 13 | github.com/spf13/pflag v1.0.3 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 4 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 5 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 6 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 7 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 8 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 9 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 10 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 11 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 12 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 13 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 14 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 15 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 16 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 17 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 18 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 19 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 20 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 21 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 22 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 23 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 24 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 25 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 26 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 27 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 28 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 29 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 30 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 31 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 32 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 33 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 34 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 35 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 36 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 37 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 38 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 39 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 40 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 41 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 42 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 43 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 44 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 45 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 46 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 47 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 48 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 49 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 50 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 51 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 52 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 53 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 54 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 55 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 56 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 57 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 58 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 59 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 60 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 61 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 62 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 63 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 64 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 65 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 66 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 67 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 68 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 69 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 70 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 71 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 72 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 73 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 74 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 75 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 76 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 77 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 78 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 79 | github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= 80 | github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= 81 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 82 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 83 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 84 | github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= 85 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 86 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 87 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 88 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 89 | github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= 90 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 91 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 92 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 93 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 94 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 95 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 96 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 97 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 98 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 99 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 100 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 101 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 102 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 103 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 104 | golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 105 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 106 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 107 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 108 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 109 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 110 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 111 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 112 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 113 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 114 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 115 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 116 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 117 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 118 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 119 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 120 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 121 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 122 | google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 123 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 124 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 125 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 126 | gopkg.in/gookit/color.v1 v1.1.6 h1:5fB10p6AUFjhd2ayq9JgmJWr9WlTrguFdw3qlYtKNHk= 127 | gopkg.in/gookit/color.v1 v1.1.6/go.mod h1:IcEkFGaveVShJ+j8ew+jwe9epHyGpJ9IrptHmW3laVY= 128 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 129 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 130 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 131 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 132 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 133 | -------------------------------------------------------------------------------- /pkg/diffy/diffy.go: -------------------------------------------------------------------------------- 1 | package diffy 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | type Option struct { 8 | NoHeader bool 9 | TabSize int 10 | SeparatorSymbol string 11 | SeparatorWidth int 12 | SpaceSizeAfterLn int 13 | } 14 | 15 | func countDigits(v int) int { 16 | var cnt int 17 | for v != 0 { 18 | v /= 10 19 | cnt++ 20 | } 21 | return cnt 22 | } 23 | 24 | func formatTextLine(text string, tabSize int) string { 25 | text = strings.TrimSuffix(text, "\n") 26 | text = strings.ReplaceAll(text, "\t", strings.Repeat(" ", tabSize)) 27 | return text 28 | } 29 | 30 | func splitText(text string, length, tabSize int) []string { 31 | text = formatTextLine(text, tabSize) 32 | if len(text) < length { 33 | return []string{text} 34 | } 35 | var res []string 36 | for i := 0; i < len(text); i += length { 37 | if i+length < len(text) { 38 | res = append(res, text[i:(i+length)]) 39 | } else { 40 | res = append(res, text[i:]) 41 | } 42 | } 43 | return res 44 | } 45 | 46 | func max(a, b int) int { 47 | if a < b { 48 | return b 49 | } 50 | return a 51 | } 52 | -------------------------------------------------------------------------------- /pkg/diffy/split.go: -------------------------------------------------------------------------------- 1 | package diffy 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "io" 8 | "os" 9 | "runtime" 10 | "strings" 11 | "syscall" 12 | "unsafe" 13 | 14 | "github.com/pmezard/go-difflib/difflib" 15 | "gopkg.in/gookit/color.v1" 16 | ) 17 | 18 | func WriteSplitDiff( 19 | w io.Writer, 20 | diff difflib.UnifiedDiff, 21 | opt Option, 22 | ) error { 23 | lnSpaceSize := countDigits(max(len(diff.A), len(diff.B))) 24 | width, _, err := terminalShape() 25 | if err != nil { 26 | return err 27 | } 28 | mid := width / 2 29 | 30 | buf := bufio.NewWriter(w) 31 | defer buf.Flush() 32 | 33 | groupedOpcodes := difflib.NewMatcher(diff.A, diff.B).GetGroupedOpCodes(diff.Context) 34 | for i, opcodes := range groupedOpcodes { 35 | if i == 0 && !opt.NoHeader { 36 | buf.WriteString(splittedHeader(diff.FromFile, diff.ToFile, color.New(color.Bold), mid)) 37 | } 38 | for _, c := range opcodes { 39 | i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2 40 | if c.Tag == 'e' { 41 | for ln, line := range diff.A[i1:i2] { 42 | texts := splitText(line, mid-2-lnSpaceSize*2, opt.TabSize) 43 | buf.WriteString( 44 | splittedLine( 45 | fmt.Sprintf("%*d", lnSpaceSize, i1+ln+1), 46 | texts[0], 47 | fmt.Sprintf("%*d", lnSpaceSize, j1+ln+1), 48 | texts[0], 49 | color.New(color.Gray), 50 | color.New(), 51 | color.New(), 52 | mid, opt.SpaceSizeAfterLn, 2, 53 | ), 54 | ) 55 | for i := 1; i < len(texts); i++ { 56 | buf.WriteString( 57 | splittedLine( 58 | strings.Repeat(" ", lnSpaceSize), 59 | texts[i], 60 | strings.Repeat(" ", lnSpaceSize), 61 | texts[i], 62 | color.New(color.Gray), 63 | color.New(), 64 | color.New(), 65 | mid, opt.SpaceSizeAfterLn, 2, 66 | ), 67 | ) 68 | } 69 | } 70 | } 71 | if c.Tag == 'd' { 72 | for ln, line := range diff.A[i1:i2] { 73 | texts := splitText(line, mid-2-lnSpaceSize*2, opt.TabSize) 74 | buf.WriteString( 75 | splittedLine( 76 | fmt.Sprintf("%*d", lnSpaceSize, i1+ln+1), 77 | texts[0], 78 | "", 79 | "", 80 | color.New(color.Gray), 81 | color.New(color.Red, color.Bold), 82 | color.New(), 83 | mid, opt.SpaceSizeAfterLn, 2, 84 | ), 85 | ) 86 | for i := 1; i < len(texts); i++ { 87 | buf.WriteString( 88 | splittedLine( 89 | strings.Repeat(" ", lnSpaceSize), 90 | texts[i], 91 | "", 92 | "", 93 | color.New(color.Gray), 94 | color.New(color.Red, color.Bold), 95 | color.New(), 96 | mid, opt.SpaceSizeAfterLn, 2, 97 | ), 98 | ) 99 | } 100 | } 101 | } 102 | if c.Tag == 'i' { 103 | for ln, line := range diff.B[j1:j2] { 104 | texts := splitText(line, mid-2-lnSpaceSize*2, opt.TabSize) 105 | buf.WriteString( 106 | splittedLine( 107 | "", 108 | "", 109 | fmt.Sprintf("%*d", lnSpaceSize, j1+ln+1), 110 | texts[0], 111 | color.New(color.Gray), 112 | color.New(), 113 | color.New(color.Green, color.Bold), 114 | mid, opt.SpaceSizeAfterLn, 2, 115 | ), 116 | ) 117 | for i := 1; i < len(texts); i++ { 118 | buf.WriteString( 119 | splittedLine( 120 | "", 121 | "", 122 | strings.Repeat(" ", lnSpaceSize), 123 | texts[i], 124 | color.New(color.Gray), 125 | color.New(), 126 | color.New(color.Green, color.Bold), 127 | mid, opt.SpaceSizeAfterLn, 2, 128 | ), 129 | ) 130 | } 131 | } 132 | } 133 | if c.Tag == 'r' { 134 | cursor := 0 135 | // compare the number of raws 136 | minLen := i2 - i1 137 | minIsOrg := true 138 | if j2-j1 < minLen { 139 | minLen = j2 - j1 140 | minIsOrg = false 141 | } 142 | for ; cursor < minLen; cursor++ { 143 | orgTexts := splitText(diff.A[i1+cursor], mid-2-lnSpaceSize*2, opt.TabSize) 144 | newTexts := splitText(diff.B[j1+cursor], mid-2-lnSpaceSize*2, opt.TabSize) 145 | buf.WriteString( 146 | splittedLine( 147 | fmt.Sprintf("%*d", lnSpaceSize, i1+cursor+1), 148 | orgTexts[0], 149 | fmt.Sprintf("%*d", lnSpaceSize, j1+cursor+1), 150 | newTexts[0], 151 | color.New(color.Gray), 152 | color.New(color.Red, color.Bold), 153 | color.New(color.Green, color.Bold), 154 | mid, opt.SpaceSizeAfterLn, 2, 155 | ), 156 | ) 157 | textCursor := 1 158 | minTextLen := len(orgTexts) 159 | minTextIsOrg := true 160 | if len(newTexts) < minTextLen { 161 | minTextLen = len(newTexts) 162 | minTextIsOrg = false 163 | } 164 | for ; textCursor < minTextLen; textCursor++ { 165 | buf.WriteString( 166 | splittedLine( 167 | strings.Repeat(" ", lnSpaceSize), 168 | orgTexts[textCursor], 169 | strings.Repeat(" ", lnSpaceSize), 170 | newTexts[textCursor], 171 | color.New(color.Gray), 172 | color.New(color.Red, color.Bold), 173 | color.New(color.Green, color.Bold), 174 | mid, opt.SpaceSizeAfterLn, 2, 175 | ), 176 | ) 177 | } 178 | if minTextIsOrg { 179 | for i := textCursor; i < len(newTexts); i++ { 180 | buf.WriteString( 181 | splittedLine( 182 | "", 183 | "", 184 | strings.Repeat(" ", lnSpaceSize), 185 | newTexts[i], 186 | color.New(color.Gray), 187 | color.New(), 188 | color.New(color.Green, color.Bold), 189 | mid, opt.SpaceSizeAfterLn, 2, 190 | ), 191 | ) 192 | } 193 | } else { 194 | for i := textCursor; i < len(orgTexts); i++ { 195 | buf.WriteString( 196 | splittedLine( 197 | strings.Repeat(" ", lnSpaceSize), 198 | orgTexts[i], 199 | "", 200 | "", 201 | color.New(color.Gray), 202 | color.New(color.Red, color.Bold), 203 | color.New(), 204 | mid, opt.SpaceSizeAfterLn, 2, 205 | ), 206 | ) 207 | } 208 | } 209 | } 210 | if minIsOrg { 211 | for ; cursor < j2-j1; cursor++ { 212 | texts := splitText(diff.B[j1+cursor], mid-2-lnSpaceSize*2, opt.TabSize) 213 | buf.WriteString( 214 | splittedLine( 215 | "", 216 | "", 217 | fmt.Sprintf("%*d", lnSpaceSize, j1+cursor+1), 218 | texts[0], 219 | color.New(color.Gray), 220 | color.New(), 221 | color.New(color.Green, color.Bold), 222 | mid, opt.SpaceSizeAfterLn, 2, 223 | ), 224 | ) 225 | for i := 1; i < len(texts); i++ { 226 | buf.WriteString( 227 | splittedLine( 228 | "", 229 | "", 230 | strings.Repeat(" ", lnSpaceSize), 231 | texts[i], 232 | color.New(color.Gray), 233 | color.New(), 234 | color.New(color.Green, color.Bold), 235 | mid, opt.SpaceSizeAfterLn, 2, 236 | ), 237 | ) 238 | } 239 | } 240 | } else { 241 | for ; cursor < i2-i1; cursor++ { 242 | texts := splitText(diff.A[i1+cursor], mid-2-lnSpaceSize*2, opt.TabSize) 243 | buf.WriteString( 244 | splittedLine( 245 | fmt.Sprintf("%*d", lnSpaceSize, i1+cursor+1), 246 | texts[0], 247 | "", 248 | "", 249 | color.New(color.Gray), 250 | color.New(color.Red, color.Bold), 251 | color.New(), 252 | mid, opt.SpaceSizeAfterLn, 2, 253 | ), 254 | ) 255 | for i := 1; i < len(texts); i++ { 256 | buf.WriteString( 257 | splittedLine( 258 | strings.Repeat(" ", lnSpaceSize), 259 | texts[i], 260 | "", 261 | "", 262 | color.New(color.Gray), 263 | color.New(color.Red, color.Bold), 264 | color.New(), 265 | mid, opt.SpaceSizeAfterLn, 2, 266 | ), 267 | ) 268 | } 269 | } 270 | } 271 | } 272 | } 273 | if i != len(groupedOpcodes)-1 { 274 | buf.WriteString( 275 | splittedFootLine( 276 | opt.SeparatorSymbol, 277 | opt.SeparatorWidth, 278 | color.New(color.Blue), 279 | mid, 280 | ), 281 | ) 282 | } 283 | } 284 | return nil 285 | } 286 | 287 | func get(i int, strs []string) string { 288 | if i < len(strs) { 289 | return strs[i] 290 | } 291 | return "" 292 | } 293 | 294 | func rpad(line string, limit int) string { 295 | if len(line) < limit { 296 | line += strings.Repeat(" ", limit-len(line)) 297 | } 298 | return line 299 | } 300 | 301 | func chopped(line string, limit int) []string { 302 | var res []string 303 | if limit >= len(line) { 304 | res = []string{line} 305 | } else { 306 | for limit < len(line) { 307 | res = append(res, line[:limit]) 308 | line = line[limit:] 309 | } 310 | res = append(res, line) 311 | } 312 | return res 313 | } 314 | 315 | func splittedFootLine(symbol string, width int, style color.Style, boundary int) string { 316 | d := strings.Repeat(symbol, width) 317 | return style.Sprintf("%s%s\n", rpad(d, boundary), d) 318 | } 319 | 320 | func splittedHeader(rawLeftFile, rawRightFile string, style color.Style, boundary int) string { 321 | return style.Sprintf("%s%s\n", rpad(rawLeftFile, boundary), rawRightFile) 322 | } 323 | 324 | func splittedLine( 325 | rawLeftLn, rawLeft, rawRightLn, rawRight string, 326 | lnStyle, leftStyle, rightStyle color.Style, 327 | boundary, spaceSizeAfterLn, spaceSizeOnBoundary int, 328 | ) string { 329 | spaceSizeOnLn := len(rawLeftLn) // = len(rawRightLn) 330 | limit := boundary - (spaceSizeOnLn + spaceSizeAfterLn + spaceSizeOnBoundary) 331 | chl, chr := chopped(rawLeft, limit), chopped(rawRight, limit) 332 | 333 | w := &bytes.Buffer{} 334 | buf := bufio.NewWriter(w) 335 | 336 | longest := len(chl) 337 | if longest < len(chr) { 338 | longest = len(chr) 339 | } 340 | 341 | for i := 0; i < longest; i++ { 342 | leftText, rightText := get(i, chl), get(i, chr) 343 | 344 | var line string 345 | leftText = rpad(leftText, limit) 346 | if i == 0 { 347 | line = fmt.Sprintf( 348 | "%s%s%s%s%s%s%s\n", 349 | lnStyle.Sprint(rawLeftLn), 350 | strings.Repeat(" ", spaceSizeAfterLn), 351 | leftStyle.Sprint(leftText), 352 | strings.Repeat(" ", spaceSizeOnBoundary), 353 | lnStyle.Sprint(rawRightLn), 354 | strings.Repeat(" ", spaceSizeAfterLn), 355 | rightStyle.Sprint(rightText), 356 | ) 357 | } else { 358 | line = fmt.Sprintf( 359 | "%s%s%s\n", 360 | leftStyle.Sprint(leftText), 361 | strings.Repeat(" ", spaceSizeOnBoundary), 362 | rightStyle.Sprint(rightText), 363 | ) 364 | } 365 | buf.WriteString(line) 366 | } 367 | buf.Flush() 368 | return string(w.Bytes()) 369 | } 370 | 371 | func terminalShape() (int, int, error) { 372 | var ( 373 | out *os.File 374 | err error 375 | sz struct { 376 | rows uint16 377 | cols uint16 378 | xpixels uint16 379 | ypixels uint16 380 | } 381 | ) 382 | if runtime.GOOS == "openbsd" { 383 | out, err = os.OpenFile("/dev/tty", os.O_RDWR, 0) 384 | if err != nil { 385 | return 0, 0, err 386 | } 387 | } else { 388 | out, err = os.OpenFile("/dev/tty", os.O_WRONLY, 0) 389 | if err != nil { 390 | return 0, 0, err 391 | } 392 | } 393 | syscall.Syscall( 394 | syscall.SYS_IOCTL, 395 | out.Fd(), 396 | uintptr(syscall.TIOCGWINSZ), 397 | uintptr(unsafe.Pointer(&sz)), 398 | ) 399 | return int(sz.cols), int(sz.rows), nil 400 | } 401 | -------------------------------------------------------------------------------- /pkg/diffy/unified.go: -------------------------------------------------------------------------------- 1 | package diffy 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "strings" 8 | 9 | "github.com/pmezard/go-difflib/difflib" 10 | "gopkg.in/gookit/color.v1" 11 | ) 12 | 13 | func WriteUnifiedDiff( 14 | w io.Writer, 15 | diff difflib.UnifiedDiff, 16 | opt Option, 17 | ) error { 18 | lnSpaceSize := countDigits(max(len(diff.A), len(diff.B))) 19 | 20 | width, _, err := terminalShape() 21 | if err != nil { 22 | return err 23 | } 24 | 25 | buf := bufio.NewWriter(w) 26 | defer buf.Flush() 27 | 28 | groupedOpcodes := difflib.NewMatcher(diff.A, diff.B).GetGroupedOpCodes(diff.Context) 29 | for i, opcodes := range groupedOpcodes { 30 | if i == 0 && !opt.NoHeader { 31 | buf.WriteString(coloredUnifiedHeader(diff.FromFile, diff.ToFile)) 32 | } 33 | for _, c := range opcodes { 34 | i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2 35 | if c.Tag == 'e' { 36 | for ln, line := range diff.A[i1:i2] { 37 | texts := splitText(line, width-2-lnSpaceSize*2-1, opt.TabSize) 38 | buf.WriteString( 39 | fmt.Sprintf( 40 | "%s %s%s%s\n", 41 | color.New(color.Gray).Sprintf("%*d", lnSpaceSize, i1+ln+1), 42 | color.New(color.Gray).Sprintf("%*d", lnSpaceSize, j1+ln+1), 43 | strings.Repeat(" ", opt.SpaceSizeAfterLn), 44 | texts[0], 45 | ), 46 | ) 47 | for i := 1; i < len(texts); i++ { 48 | buf.WriteString( 49 | fmt.Sprintf( 50 | "%s %s\n", 51 | strings.Repeat(" ", opt.SpaceSizeAfterLn+lnSpaceSize*2), 52 | texts[i], 53 | ), 54 | ) 55 | } 56 | } 57 | } 58 | if c.Tag == 'r' || c.Tag == 'd' { 59 | for ln, line := range diff.A[i1:i2] { 60 | texts := splitText(line, width-2-lnSpaceSize*2-1, opt.TabSize) 61 | buf.WriteString( 62 | fmt.Sprintf( 63 | "%s %s%s\n", 64 | color.New(color.Gray).Sprintf("%*d", lnSpaceSize, i1+ln+1), 65 | strings.Repeat(" ", lnSpaceSize+opt.SpaceSizeAfterLn), 66 | color.New(color.Red, color.Bold).Sprintf("%s", texts[0]), 67 | ), 68 | ) 69 | for i := 1; i < len(texts); i++ { 70 | buf.WriteString( 71 | fmt.Sprintf( 72 | "%s %s\n", 73 | strings.Repeat(" ", opt.SpaceSizeAfterLn+lnSpaceSize*2), 74 | color.New(color.Red, color.Bold).Sprintf("%s", texts[i]), 75 | ), 76 | ) 77 | } 78 | } 79 | } 80 | if c.Tag == 'r' || c.Tag == 'i' { 81 | for ln, line := range diff.B[j1:j2] { 82 | texts := splitText(line, width-2-lnSpaceSize*2-1, opt.TabSize) 83 | buf.WriteString( 84 | fmt.Sprintf( 85 | " %s%s%s\n", 86 | color.New(color.Gray).Sprintf("%*d", lnSpaceSize*2, j1+ln+1), 87 | strings.Repeat(" ", opt.SpaceSizeAfterLn), 88 | color.New(color.Green, color.Bold).Sprintf("%s", texts[0]), 89 | ), 90 | ) 91 | for i := 1; i < len(texts); i++ { 92 | buf.WriteString( 93 | fmt.Sprintf( 94 | "%s %s\n", 95 | strings.Repeat(" ", opt.SpaceSizeAfterLn+lnSpaceSize*2), 96 | color.New(color.Green, color.Bold).Sprintf("%s", texts[i]), 97 | ), 98 | ) 99 | } 100 | } 101 | } 102 | } 103 | if i != len(groupedOpcodes)-1 { 104 | buf.WriteString(color.New(color.Blue).Sprintf("%s\n", strings.Repeat(opt.SeparatorSymbol, opt.SeparatorWidth))) 105 | } 106 | } 107 | return nil 108 | } 109 | 110 | func coloredUnifiedHeader(org, new string) string { 111 | return fmt.Sprintf( 112 | "%s %s\n%s %s\n", 113 | color.New(color.Red).Sprint("-"), 114 | color.New(color.Bold).Sprint(org), 115 | color.New(color.Green).Sprint("+"), 116 | color.New(color.Bold).Sprint(new), 117 | ) 118 | } 119 | --------------------------------------------------------------------------------