Skip to content
This repository has been archived by the owner on Sep 16, 2023. It is now read-only.

Commit

Permalink
Merge faa6116 into af85aec
Browse files Browse the repository at this point in the history
  • Loading branch information
tamada committed May 14, 2019
2 parents af85aec + faa6116 commit 417d278
Show file tree
Hide file tree
Showing 11 changed files with 433 additions and 95 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
vendor
uniq2

### https://raw.github.com/github/gitignore/5c5a1b536b6f9bf11515518f03d58addaf29f681/Go.gitignore
Expand Down
5 changes: 2 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ go:

env:
global:
secure: b+I7yx6AzH14T5LoBorRh/rIIBxNIm39S5hH8Pb7d3OXMPA56tndPWYeGfZ33TQnWJXDkE/UysEqx3d7Kz5eqBoF/rQQ+44AfbjXjYecaszmRxaswG7Mxt3/hCGVnYk74Z3e8SSF0E97cstCS8SskWtnarYbCJOYp83WVZaMFh0zBw2YKASl5SRSb4gTfaltfA43v8rMKHRbU2dx+dSsRtX+0adR2umHV6pIMV1xzuTV0Kr5dni8/y4uivKcHXCbdr+yP++QB7kvm9rtN+u/2FSmO3oSnvlIBMsK8A10qtZBY+SHnYV6+0DxnaQWQhXLiWDhD9yjQPOwGgAqo95WW6ZOmGUQr3vtFTNiKFTWeHYI9JP31sEUh1kHkpK4Ff2wABsQlDlkabTuqAYGfvzEYMB5gvSkceiUyQGOv1BQhbHK4Cj3lXFEkeeR6S4H/eZdBEN7hrngH79G33F8JAMs94FuqWcSpkNbcLgZ5PsWD3KJIPCdTtUi+q9yQl2w2aY1HDFw7eGKNiSc9x73GoCpZIF5nQCTbotrX/WHs+G/yPUnATEb16fEe0azErfqOovWGQP3y1Bcj9Iu8BHEO3ZIfbBLnOT5fMKptd3FX8kHEY99Mc8zKecVnowsdagUedK69pfAim+8nNFGWfEPZKNnY5w+vptzLm3NCcI5EupIRB8=

secure: "urG/JslIMCq0BVtu4kJWzfX005QxE7Npp7lQ17mn2ZEWs11q/P6ZYM1/YiaXoJMd2mHd6oi2G4Anxq2hZUgPsKFYptva8uwAEMjA3nKhHRSFrR1jwqW2iAJa1mGbl37g8LxbqVxxpNGMpejWLU6AgJ8YB6TiMjb1sl/nSuhiPEazV0uUdjAs416NTTcvHgiZRA/hwRkKaIBKAt2iYbypvGLaK+z/SBnBw2GGCM9UR/K4PzfnhWWNJzK/uC2zJf/2rSkhJyb1A/T3zVxwx2uG9fKqFXh6lp3ZHNGGnGzLAH55yJPE0f0wekyqm9sBHnNGcMKwZOPVi35eE9WKQEBV3k7GX06yPr7RA6/e3TOflxxPYXR65jjpWZ1qmPsgw1dWEVpQDIGWtltarsAHsd9Rvj48nDoJ+gBescErMvrFE773y0Gca36aBPGLpPHd0RO3TR2gT8oS11w+qZPtUbfpkkuG6NFDalOnvcmvKM+fMNKEbHnoLvmD5BBfrni6jmz0NTJ49nDi7rfyzt2IafE3cE2ztoKdH1tBIczsNiwBOt7cAhqS+OyDV0Uuj70tfLAONOyTAZ55MOV+C23DOPHIQVMHQOLgaaVxjRpJMmBX+zrfPq4VAl6+2ZTUXhLofF2Rg8R4DlUeQrQPFyEi5vB6l12usnsnOed+zyeSnLe5HQ8="
script:
- make test build
- $GOPATH/bin/goveralls -coverprofile=coverage.out -service=travis-ci -repotoken $COVERALLS_TOKEN
- $GOPATH/bin/goveralls -repotoken=$COVERALLS_TOKEN -coverprofile=coverage.out -service=travis-ci
10 changes: 9 additions & 1 deletion Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
# unused-packages = true


[[constraint]]
name = "github.com/urfave/cli"
version = "1.20.0"

[prune]
go-tests = true
unused-packages = true
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ deps:
setup: deps
git submodule update --init

test: setup
test: setup
$(GO) test -covermode=count -coverprofile=coverage.out $$(go list ./... | grep -v vendor)

build: setup
$(GO) build -o $(NAME) -v
cd cmd/uniq2; $(GO) build -o $(NAME) -v

clean:
$(GO) clean
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

Delete duplicated lines.

GNU core utilities have `uniq` command for deleting duplicate lines.
[GNU core utilities](https://www.gnu.org/software/coreutils/) has `uniq` command for deleting duplicate lines.
However, `uniq` command deletes only continuous duplicate lines.
When deleting not continuous duplicate lines, we use `sort` command together, in that case, the order of the list was not kept.

Expand Down
103 changes: 103 additions & 0 deletions cmd/uniq2/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package main

import (
"fmt"
"os"

"github.com/tamada/uniq2/lib"
"github.com/urfave/cli"
)

const VERSION = "0.2.0"

func printHelp(app *cli.App) {
fmt.Printf(`%s
OPTIONS
-a, --adjacent delete only adjacent duplicated lines.
-c, --show-counts show counts of deleted lines.
-d, --delete-lines only prints deleted lines.
-i, --ignore-case case sensitive.
-h, --help print this message.
INPUT gives file name of input. If argument is single dash ('-')
or absent, the program read strings from stdin.
OUTPUT represents the destination.
`, app.Usage)
}

func buildFlags() []cli.Flag {
return []cli.Flag{
cli.BoolFlag{
Name: "adjacent, a",
Usage: "delete only adjacent duplicated lines.",
},
cli.BoolFlag{
Name: "show-counts, c",
Usage: "show counts of deleted lines.",
},
cli.BoolFlag{
Name: "delete-lines, d",
Usage: "only prints deleted lines.",
},
cli.BoolFlag{
Name: "ignore-case, i",
Usage: "case sensitive",
},
}
}

func constructApp() *cli.App {
var app = cli.NewApp()
app.Name = "uniq2"
app.Usage = "Eliminates duplicated lines"
app.UsageText = "uniq2 [OPTIONS] [INPUT [OUTPUT]]"
app.Version = VERSION
app.Flags = buildFlags()
app.Action = func(c *cli.Context) error {
return action(app, c)
}
return app
}

func parseOptions(c *cli.Context) *lib.Options {
return &lib.Options{
Adjacent: c.Bool("adjacent"),
ShowCounts: c.Bool("show-counts"),
DeleteLines: c.Bool("delete-lines"),
IgnoreCase: c.Bool("ignore-case"),
}
}

func perform(args *lib.Arguments) error {
defer args.Close()
return args.Perform()
}

func action(app *cli.App, c *cli.Context) error {
var options = parseOptions(c)
if c.Bool("help") {
printHelp(app)
return nil
}
var args, err = lib.NewArguments(options, c.Args())
if err != nil {
return err
}
return perform(args)
}

func goMain() int {
var app = constructApp()
var err = app.Run(os.Args)
if err != nil {
fmt.Println(err.Error())
return 1
}
return 0
}

func main() {
// separates main function in order to run defers before exit.
var exitStatus = goMain()
os.Exit(exitStatus)
}
169 changes: 169 additions & 0 deletions lib/uniq2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package lib

import (
"bufio"
"fmt"
"io"
"os"
"strings"
)

/*
Options represents option parameter values.
*/
type Options struct {
Adjacent bool
ShowCounts bool
DeleteLines bool
IgnoreCase bool
}

/*
Arguments represents the command line arguments.
*/
type Arguments struct {
Options *Options
Input io.Reader
Output io.Writer
}

type entry struct {
line string
count map[bool]int
}

/*
Close finalize the files of Arguments.
*/
func (args *Arguments) Close() {
var inputFile, ok1 = args.Input.(*os.File)
if ok1 {
inputFile.Close()
}
var outputFile, ok2 = args.Output.(*os.File)
if ok2 {
outputFile.Close()
}
}

/*
NewArguments construct an instance of Arguments with the given parameters.
*/
func NewArguments(opts *Options, args []string) (*Arguments, error) {
var arguments = Arguments{Options: opts}
var input, output, err = parseCliArguments(args)
arguments.Input = input
arguments.Output = output
return &arguments, err
}

func parseCliArguments(args []string) (*os.File, *os.File, error) {
switch len(args) {
case 0:
return os.Stdin, os.Stdout, nil
case 1:
var input, err = createInput(args[0])
return input, os.Stdout, err
case 2:
var input, output *os.File
var err error
input, err = createInput(args[0])
if err == nil {
output, err = createOutput(args[1])
}
return input, output, err
}
return nil, nil, fmt.Errorf("too many arguments: %v", args)
}

func createOutput(output string) (*os.File, error) {
if output == "-" {
return os.Stdout, nil
}
return os.OpenFile(output, os.O_CREATE|os.O_WRONLY, 0644)
}

func createInput(input string) (*os.File, error) {
if input == "-" {
return os.Stdin, nil
}
return os.Open(input)
}

/*
Perform reads files from args.Input and writes result to args.Output.
*/
func (args *Arguments) Perform() error {
var scanner = bufio.NewScanner(args.Input)
var writer = bufio.NewWriter(args.Output)

return args.runUnique(scanner, writer)
}

func isPrint(uniqFlag bool, deleteLineFlag bool) bool {
return !uniqFlag && !deleteLineFlag ||
uniqFlag && deleteLineFlag
}

func updateDatabase(line string, uniqFlag bool, entries []entry) []entry {
for i, entry := range entries {
if entry.line == line {
entries[i].count[uniqFlag]++
}
}
return entries
}

func upsertDatabase(line string, uniqFlag bool, entries []entry) []entry {
if uniqFlag {
return updateDatabase(line, uniqFlag, entries)
}
var entry = entry{line: line, count: map[bool]int{uniqFlag: 1}}
return append(entries, entry)
}

func (args *Arguments) runUnique(scanner *bufio.Scanner, writer *bufio.Writer) error {
var entries = []entry{}
for scanner.Scan() {
var line = scanner.Text()
var uniqFlag, lineToDB = args.isUniqLine(line, entries)
entries = upsertDatabase(lineToDB, uniqFlag, entries)
if isPrint(uniqFlag, args.Options.DeleteLines) {
writer.WriteString(line)
writer.WriteString("\n")
}
}
writer.Flush()
return nil
}

func (opts *Options) match(readLine string, lineOfDB entry) bool {
return readLine == lineOfDB.line
}

func (opts *Options) isFoundLineInAdjacentDB(line string, list []entry) bool {
if len(list) == 0 {
return false
}
return opts.match(line, list[len(list)-1])
}

func (opts *Options) isFoundLineInDB(line string, list []entry) bool {
for _, lineInList := range list {
if line == lineInList.line {
return true
}
}
return false
}

func (args *Arguments) isUniqLine(line string, list []entry) (flag bool, lineToDB string) {
lineToDB = line
if args.Options.IgnoreCase {
lineToDB = strings.ToLower(line)
}
if args.Options.Adjacent {
return args.Options.isFoundLineInAdjacentDB(lineToDB, list), lineToDB
}
return args.Options.isFoundLineInDB(lineToDB, list), lineToDB
}

0 comments on commit 417d278

Please sign in to comment.