diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 0000000..dad3a67 --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,50 @@ +name: Go Build and Release + +on: + push: + branches: + - master + tags: + - '*' + +jobs: + build: + name: Build and Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: '1.x' + + - name: Install Dependencies + run: sudo apt-get install -y rpm + + - name: Build + run: | + go build -ldflags "-X main.version=${GITHUB_REF#refs/tags/}" . + + - name: Run Lint and Test + run: make lint test + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v1 + with: + token: ${{secrets.CODECOV_TOKEN}} # This requires CODECOV_TOKEN in your GitHub repository secrets + + release: + needs: build + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Run Goreleaser + uses: goreleaser/goreleaser-action@v2 + with: + version: latest + args: release --rm-dist + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, no need to create one diff --git a/.gitignore b/.gitignore index 528a834..7e2a0e3 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ *.out .idea/* +.vscode/* out/ @@ -27,4 +28,5 @@ coverage.txt majestic_million*.csv # Output binary -csvdiff \ No newline at end of file +csvdiff +dist/ diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..4094b76 --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,46 @@ +# This is an example .goreleaser.yml file with some sensible defaults. +# Make sure to check the documentation at https://goreleaser.com + +# The lines below are called `modelines`. See `:help modeline` +# Feel free to remove those if you don't want/need to use them. +# yaml-language-server: $schema=https://goreleaser.com/static/schema.json +# vim: set ts=2 sw=2 tw=0 fo=cnqoj + +version: 1 + +before: + hooks: + # You may remove this if you don't use go modules. + - go mod tidy + # you may remove this if you don't need go generate + - go generate ./... + +builds: + - env: + - CGO_ENABLED=0 + goos: + - linux + - windows + - darwin + +archives: + - format: tar.gz + # this name template makes the OS and Arch compatible with the results of `uname`. + name_template: >- + {{ .ProjectName }}_ + {{- title .Os }}_ + {{- if eq .Arch "amd64" }}x86_64 + {{- else if eq .Arch "386" }}i386 + {{- else }}{{ .Arch }}{{ end }} + {{- if .Arm }}v{{ .Arm }}{{ end }} + # use zip for windows archives + format_overrides: + - goos: windows + format: zip + +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" diff --git a/.goreleaser.yml b/.goreleaser.yml deleted file mode 100644 index d9712fc..0000000 --- a/.goreleaser.yml +++ /dev/null @@ -1,31 +0,0 @@ -project_name: csvdiff - -release: - github: - owner: aswinkarthik - name: csvdiff - -builds: - - main: ./main.go - binary: csvdiff - goos: - - windows - - darwin - - linux - goarch: - - amd64 -nfpms: - - file_name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" - replacements: - amd64: 64-bit - 386: 32-bit - darwin: macOS - linux: linux - vendor: aswinkarthik - homepage: https://github.com/aswinkarthik/csvdiff - maintainer: aswinkarthik - description: A Blazingly fast diff tool for comparing csv files. - license: MIT - formats: - - deb - - rpm \ No newline at end of file diff --git a/cmd/formatter.go b/cmd/formatter.go index 38aa914..dbf7895 100644 --- a/cmd/formatter.go +++ b/cmd/formatter.go @@ -1,23 +1,27 @@ package cmd import ( + "encoding/csv" "encoding/json" "fmt" + "io" + "strings" + "github.com/aswinkarthik/csvdiff/pkg/digest" "github.com/fatih/color" - "io" ) const ( - rowmark = "rowmark" - jsonFormat = "json" - legacyJSONFormat = "legacy-json" - lineDiff = "diff" - wordDiff = "word-diff" - colorWords = "color-words" + rowmark = "rowmark" + rowmarkWithHeader = "rowmark-with-header" + jsonFormat = "json" + legacyJSONFormat = "legacy-json" + lineDiff = "diff" + wordDiff = "word-diff" + colorWords = "color-words" ) -var allFormats = []string{rowmark, jsonFormat, legacyJSONFormat, lineDiff, wordDiff, colorWords} +var allFormats = []string{rowmark, rowmarkWithHeader, jsonFormat, legacyJSONFormat, lineDiff, wordDiff, colorWords} // Formatter can print the differences to stdout // and accompanying metadata to stderr @@ -45,6 +49,8 @@ func (f *Formatter) Format(diff digest.Differences) error { return f.json(diff) case rowmark: return f.rowMark(diff) + case rowmarkWithHeader: + return f.rowmarkWithHeader(diff) case lineDiff: return f.lineDiff(diff) case wordDiff: @@ -155,6 +161,48 @@ func (f *Formatter) rowMark(diff digest.Differences) error { _, _ = fmt.Fprintf(f.stderr, "Rows:\n") includes := f.ctx.GetIncludeColumnPositions() + separator := f.ctx.separator + + additions := make([]string, 0, len(diff.Additions)) + for _, addition := range diff.Additions { + additions = append(additions, includes.String(addition, f.ctx.separator)) + } + + modifications := make([]string, 0, len(diff.Modifications)) + for _, modification := range diff.Modifications { + modifications = append(modifications, includes.String(modification.Current, f.ctx.separator)) + } + + deletions := make([]string, 0, len(diff.Deletions)) + for _, deletion := range diff.Deletions { + deletions = append(deletions, includes.String(deletion, f.ctx.separator)) + } + + for _, added := range additions { + _, _ = fmt.Fprintf(f.stdout, "%s%s%s\n", added, string(separator), "ADDED") + } + + for _, modified := range modifications { + _, _ = fmt.Fprintf(f.stdout, "%s%s%s\n", modified, string(separator), "MODIFIED") + } + + for _, deleted := range deletions { + _, _ = fmt.Fprintf(f.stdout, "%s%s%s\n", deleted, string(separator), "DELETED") + } + + return nil +} + +// RowMarkFormatter formats diff by marking each row as +// ADDED/MODIFIED. It mutates the row and adds as a new column. +func (f *Formatter) rowmarkWithHeader(diff digest.Differences) error { + _, _ = fmt.Fprintf(f.stderr, "Additions %d\n", len(diff.Additions)) + _, _ = fmt.Fprintf(f.stderr, "Modifications %d\n", len(diff.Modifications)) + _, _ = fmt.Fprintf(f.stderr, "Deletions %d\n", len(diff.Deletions)) + _, _ = fmt.Fprintf(f.stderr, "\n") + + includes := f.ctx.GetIncludeColumnPositions() + separator := f.ctx.separator additions := make([]string, 0, len(diff.Additions)) for _, addition := range diff.Additions { @@ -171,16 +219,36 @@ func (f *Formatter) rowMark(diff digest.Differences) error { deletions = append(deletions, includes.String(deletion, f.ctx.separator)) } + fs, err := f.ctx.fs.Open(f.ctx.baseFilename) + if err != nil { + return fmt.Errorf("unable to open file %s, %v", f.ctx.baseFilename, err) + } + + defer fs.Close() + r := csv.NewReader(fs) + headers, err := r.Read() + if err != nil { + if err == io.EOF { + return fmt.Errorf("unable to process headers from csv file for delta file. EOF reached. invalid CSV file, %v", err) + } + + return fmt.Errorf("unable to process headers from csv file, %v", err) + } + + if len(additions)+len(modifications)+len(deletions) > 0 { + _, _ = fmt.Fprintf(f.stdout, "%s%s%s\n", strings.Join(headers, string(separator)), string(separator), "CSVDIFF") + } + for _, added := range additions { - _, _ = fmt.Fprintf(f.stdout, "%s,%s\n", added, "ADDED") + _, _ = fmt.Fprintf(f.stdout, "%s%s%s\n", added, string(separator), "ADDED") } for _, modified := range modifications { - _, _ = fmt.Fprintf(f.stdout, "%s,%s\n", modified, "MODIFIED") + _, _ = fmt.Fprintf(f.stdout, "%s%s%s\n", modified, string(separator), "MODIFIED") } for _, deleted := range deletions { - _, _ = fmt.Fprintf(f.stdout, "%s,%s\n", deleted, "DELETED") + _, _ = fmt.Fprintf(f.stdout, "%s%s%s\n", deleted, string(separator), "DELETED") } return nil diff --git a/go.mod b/go.mod index 91756f0..7f7e505 100644 --- a/go.mod +++ b/go.mod @@ -8,8 +8,8 @@ require ( github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/afero v1.1.2 github.com/spf13/cobra v0.0.5 - github.com/stretchr/testify v1.4.0 - golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa // indirect + github.com/stretchr/testify v1.8.4 + golang.org/x/sys v0.1.0 // indirect ) go 1.13 diff --git a/go.sum b/go.sum index 6ac221e..4e17a77 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,4 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI= github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= @@ -10,7 +9,6 @@ github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -31,7 +29,6 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -45,21 +42,25 @@ github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa h1:KIDDMLT1O0Nr7TSxp8xM5tJcdn8tgyAONntO829og1M= -golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=