Skip to content

Commit

Permalink
Add --stdin
Browse files Browse the repository at this point in the history
Allow stdin content streams
  • Loading branch information
mblaschke committed Apr 6, 2017
1 parent 9d3e3ed commit ca59701
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 84 deletions.
24 changes: 16 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,29 @@ Usage:
goreplace
Application Options:
-m, --mode=[replace|line|lineinfile] replacement mode - replace: replace match with term; line: replace line with term; lineinfile:
replace line with term or if not found append to term to file (default: replace)
-m, --mode=[replace|line|lineinfile] replacement mode - replace: replace
match with term; line: replace line with
term; lineinfile: replace line with term
or if not found append to term to file
(default: replace)
-s, --search= search term
-r, --replace= replacement term
-i, --case-insensitive ignore case of pattern to match upper and lowercase characters
-i, --case-insensitive ignore case of pattern to match upper
and lowercase characters
--stdin process stdin as input
--once replace search term only one in a file
--once-remove-match replace search term only one in a file and also don't keep matching lines (for line and lineinfile
mode)
--once-remove-match replace search term only one in a file
and also don't keep matching lines (for
line and lineinfile mode)
--regex treat pattern as regex
--regex-backrefs enable backreferences in replace term
--regex-posix parse regex term as POSIX regex
--path= use files in this path
--path-pattern= file pattern (* for wildcard, only basename of file)
--path-pattern= file pattern (* for wildcard, only
basename of file)
--path-regex= file pattern (regex, full path)
--ignore-empty ignore empty file list, otherwise this will result in an error
--ignore-empty ignore empty file list, otherwise this
will result in an error
-v, --verbose verbose mode
--dry-run dry run mode
-V, --version show version and exit
Expand Down Expand Up @@ -57,7 +65,7 @@ Application Options:
## Installation

```bash
GOREPLACE_VERSION=0.4.0 \
GOREPLACE_VERSION=0.5.0 \
&& wget -O /usr/local/bin/go-replace https://github.com/webdevops/goreplace/releases/download/$GOREPLACE_VERSION/gr-64-linux \
&& chmod +x /usr/local/bin/go-replace
```
Expand Down
196 changes: 120 additions & 76 deletions goreplace.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (

const (
Author = "webdevops.io"
Version = "0.4.0"
Version = "0.5.0"
)

type changeset struct {
Expand Down Expand Up @@ -44,6 +44,7 @@ var opts struct {
Search []string `short:"s" long:"search" required:"true" description:"search term"`
Replace []string `short:"r" long:"replace" required:"true" description:"replacement term" `
CaseInsensitive bool `short:"i" long:"case-insensitive" description:"ignore case of pattern to match upper and lowercase characters"`
Stdin bool ` long:"stdin" description:"process stdin as input"`
Once bool ` long:"once" description:"replace search term only one in a file"`
OnceRemoveMatch bool ` long:"once-remove-match" description:"replace search term only one in a file and also don't keep matching lines (for line and lineinfile mode)"`
Regex bool ` long:"regex" description:"treat pattern as regex"`
Expand Down Expand Up @@ -81,40 +82,14 @@ func applyChangesetsToFile(fileitem fileitem, changesets []changeset) (string, b
r := bufio.NewReader(file)
line, e := Readln(r)
for e == nil {
writeLine := true
newLine, lineChanged, skipLine := applyChangesetsToLine(line, changesets)

for i := range changesets {
changeset := changesets[i]

// --once, only do changeset once if already applied to file
if opts.Once && changeset.MatchFound {
// --once-without-match, skip matching lines
if opts.OnceRemoveMatch && searchMatch(line, changeset) {
// matching line, not writing to buffer as requsted
writeLine = false
writeBufferToFile = true
break
}
} else {
// search and replace
if searchMatch(line, changeset) {
// --mode=line or --mode=lineinfile
if opts.ModeIsReplaceLine || opts.ModeIsLineInFile {
// replace whole line with replace term
line = changeset.Replace
} else {
// replace only term inside line
line = replaceText(line, changeset)
}

changesets[i].MatchFound = true
writeBufferToFile = true
}
}
if lineChanged || skipLine {
writeBufferToFile = true
}

if (writeLine) {
buffer.WriteString(line + "\n")
if !skipLine {
buffer.WriteString(newLine + "\n")
}

line, e = Readln(r)
Expand All @@ -140,6 +115,43 @@ func applyChangesetsToFile(fileitem fileitem, changesets []changeset) (string, b
return output, status, err
}

func applyChangesetsToLine(line string, changesets []changeset) (string, bool, bool) {
changed := false
skipLine := false

for i := range changesets {
changeset := changesets[i]

// --once, only do changeset once if already applied to file
if opts.Once && changeset.MatchFound {
// --once-without-match, skip matching lines
if opts.OnceRemoveMatch && searchMatch(line, changeset) {
// matching line, not writing to buffer as requsted
skipLine = true
changed = true
break
}
} else {
// search and replace
if searchMatch(line, changeset) {
// --mode=line or --mode=lineinfile
if opts.ModeIsReplaceLine || opts.ModeIsLineInFile {
// replace whole line with replace term
line = changeset.Replace
} else {
// replace only term inside line
line = replaceText(line, changeset)
}

changesets[i].MatchFound = true
changed = true
}
}
}

return line, changed, skipLine
}

// Readln returns a single line (without the ending \n)
// from the input buffered reader.
// An error is returned iff there is an error with the
Expand Down Expand Up @@ -195,13 +207,13 @@ func writeContentToFile(fileitem fileitem, content bytes.Buffer) (string, bool)
// Log message
func logMessage(message string) {
if opts.Verbose {
fmt.Println(message)
fmt.Fprintln(os.Stderr, message)
}
}

// Log error object as message
func logError(err error) {
fmt.Printf("Error: %s\n", err)
fmt.Fprintln(os.Stderr, "Error: %s\n", err)
}

// Build search term
Expand Down Expand Up @@ -343,43 +355,23 @@ func handleSpecialCliOptions(argparser *flags.Parser, args []string) ([]string)
return args
}

func main() {
var changesets = []changeset {}
func actionProcessStdin(changesets []changeset) (int) {
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
line := scanner.Text()

var argparser = flags.NewParser(&opts, flags.PassDoubleDash)
args, err := argparser.Parse()
newLine, _, skipLine := applyChangesetsToLine(line, changesets)

args = handleSpecialCliOptions(argparser, args)

// check if there is an parse error
if err != nil {
logError(err)
fmt.Println()
argparser.WriteHelp(os.Stdout)
os.Exit(1)
}

// check if search and replace options have equal lenght (equal number of options)
if len(opts.Search) != len(opts.Replace) {
// error: unequal numbers of search and replace options
err := errors.New("Unequal numbers of search or replace options")
logError(err)
fmt.Println()
argparser.WriteHelp(os.Stdout)
os.Exit(1)
if !skipLine {
fmt.Println(newLine)
}
}

// build changesets
for i := range opts.Search {
search := opts.Search[i]
replace := opts.Replace[i]

changeset := changeset{buildSearchTerm(search), replace, false}

changesets = append(changesets, changeset)
}
return 0
}

// check if there is at least one file to process
func actionProcessFiles(changesets []changeset, args []string, argparser *flags.Parser) (int) {
// check if there is at least one file to process
if (len(args) == 0) {
if (opts.IgnoreEmpty) {
// no files found, but we should ignore empty filelist
Expand All @@ -389,9 +381,9 @@ func main() {
// no files found, print error and exit with error code
err := errors.New("No files specified")
logError(err)
fmt.Println()
fmt.Fprintln(os.Stderr, "")
argparser.WriteHelp(os.Stdout)
os.Exit(1)
return 1
}
}

Expand Down Expand Up @@ -426,20 +418,72 @@ func main() {
} else if opts.Verbose {
title := fmt.Sprintf("%s:", result.File.Path)

fmt.Println()
fmt.Println(title)
fmt.Println(strings.Repeat("-", len(title)))
fmt.Println()
fmt.Println(result.Output)
fmt.Println()
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, title)
fmt.Fprintln(os.Stderr, strings.Repeat("-", len(title)))
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, result.Output)
fmt.Fprintln(os.Stderr, "")
}
}

if errorCount >= 1 {
fmt.Println(fmt.Sprintf("[ERROR] %s failed with %d error(s)", argparser.Command.Name, errorCount))
fmt.Fprintln(os.Stderr, fmt.Sprintf("[ERROR] %s failed with %d error(s)", argparser.Command.Name, errorCount))
return 1
}

return 0
}

func buildChangesets(argparser *flags.Parser) ([]changeset){
var changesets []changeset

// check if search and replace options have equal lenght (equal number of options)
if len(opts.Search) != len(opts.Replace) {
// error: unequal numbers of search and replace options
err := errors.New("Unequal numbers of search or replace options")
logError(err)
fmt.Fprintln(os.Stderr, "")
argparser.WriteHelp(os.Stdout)
os.Exit(1)
}

// build changesets
for i := range opts.Search {
search := opts.Search[i]
replace := opts.Replace[i]

changeset := changeset{buildSearchTerm(search), replace, false}
changesets = append(changesets, changeset)
}

return changesets
}

func main() {
var argparser = flags.NewParser(&opts, flags.PassDoubleDash)
args, err := argparser.Parse()

args = handleSpecialCliOptions(argparser, args)

// check if there is an parse error
if err != nil {
logError(err)
fmt.Fprintln(os.Stderr, "")
argparser.WriteHelp(os.Stdout)
os.Exit(1)
}

changesets := buildChangesets(argparser)

exitMode := 0
if opts.Stdin {
// use stdin as input
exitMode = actionProcessStdin(changesets)
} else {
os.Exit(0)
// use and process files (see args)
exitMode = actionProcessFiles(changesets, args, argparser)
}

os.Exit(exitMode)
}
15 changes: 15 additions & 0 deletions tests/main.t
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Usage:
-r, --replace= replacement term
-i, --case-insensitive ignore case of pattern to match upper
and lowercase characters
--stdin process stdin as input
--once replace search term only one in a file
--once-remove-match replace search term only one in a file
and also don't keep matching lines (for
Expand Down Expand Up @@ -62,6 +63,20 @@ Testing replace mode:
this is the third ___xxx line
this is the last line
Testing replace mode with stdin:
$ cat > test.txt <<EOF
> this is a testline
> this is the second line
> this is the third foobar line
> this is the last line
> EOF
$ cat test.txt | goreplace -s foobar -r ___xxx --stdin
this is a testline
this is the second line
this is the third ___xxx line
this is the last line
Testing replace mode with multiple matches:
$ cat > test.txt <<EOF
Expand Down

0 comments on commit ca59701

Please sign in to comment.