Skip to content

Commit 25a3bef

Browse files
committed
Implement main features
1 parent 4d0c12b commit 25a3bef

File tree

2 files changed

+184
-40
lines changed

2 files changed

+184
-40
lines changed

README.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,21 @@
11
# goreplace
22
Replace cli utility written in golang
33

4-
Inspired by https://github.com/piranha/goreplace
4+
Inspired by https://github.com/piranha/goreplace
5+
6+
## Usage
7+
```
8+
Usage:
9+
goreplace
10+
11+
Application Options:
12+
-s, --search= search term
13+
-r, --replace= replacement term
14+
-i, --ignore-case ignore pattern case
15+
--whole-line replace whole line
16+
--regex treat pattern as regex
17+
-v, --verbose verbose mode
18+
--dry-run dry run mode
19+
-V, --version show version and exit
20+
-h, --help show this help message
21+
```

goreplace.go

Lines changed: 166 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ package main
22

33
import (
44
"fmt"
5+
"errors"
6+
"bytes"
57
"io/ioutil"
8+
"bufio"
69
"os"
710
"strings"
811
"regexp"
@@ -15,83 +18,207 @@ const (
1518
)
1619

1720
var opts struct {
18-
Search string `short:"s" long:"regex" description:"Search regexp" value-name:"RE"`
19-
Replace string `short:"r" long:"replace" description:"replace found substrings with RE" value-name:"RE"`
20-
IgnoreCase bool `short:"i" long:"ignore-case" description:"ignore pattern case"`
21-
PlainText bool `short:"p" long:"plain" description:"treat pattern as plain text"`
22-
Verbose bool `short:"v" long:"verbose" description:"verbose mode"`
23-
ShowVersion bool `short:"V" long:"version" description:"show version and exit"`
24-
ShowHelp bool `short:"h" long:"help" description:"show this help message"`
21+
Search string `short:"s" long:"search" required:"true" description:"search term"`
22+
SearchRegex *regexp.Regexp
23+
Replace string `short:"r" long:"replace" required:"true" description:"replacement term" `
24+
IgnoreCase bool `short:"i" long:"ignore-case" description:"ignore pattern case"`
25+
WholeLine bool ` long:"whole-line" description:"replace whole line"`
26+
Regex bool ` long:"regex" description:"treat pattern as regex"`
27+
Verbose bool `short:"v" long:"verbose" description:"verbose mode"`
28+
DryRun bool ` long:"dry-run" description:"dry run mode"`
29+
ShowVersion bool `short:"V" long:"version" description:"show version and exit"`
30+
ShowHelp bool `short:"h" long:"help" description:"show this help message"`
2531
}
2632

27-
var argparser = flags.NewParser(&opts, flags.PrintErrors|flags.PassDoubleDash)
28-
29-
func replaceInFile(file string) {
33+
// Replace content parts in file
34+
func replaceContentInFile(filepath string) {
3035
var err error
3136

32-
read, err := ioutil.ReadFile(file)
37+
read, err := ioutil.ReadFile(filepath)
38+
if err != nil {
39+
panic(err)
40+
}
41+
42+
content, replaceStatus := replaceText(string(read))
43+
44+
if replaceStatus {
45+
writeContentToFile(filepath, content)
46+
} else {
47+
logMessage(fmt.Sprintf("%s no match", filepath))
48+
}
49+
50+
}
51+
52+
// Replace line (if match is found) in file
53+
func replaceLineInFile(filepath string) {
54+
file, err := os.Open(filepath)
3355
if err != nil {
3456
panic(err)
3557
}
3658

37-
content := string(read)
59+
replaceStatus := false
60+
var buffer bytes.Buffer
61+
62+
r := bufio.NewReader(file)
63+
line, e := Readln(r)
64+
for e == nil {
65+
if searchMatch(line) {
66+
buffer.WriteString(opts.Replace + "\n")
67+
replaceStatus = true
68+
} else {
69+
buffer.WriteString(line + "\n")
70+
}
71+
72+
line, e = Readln(r)
73+
}
74+
75+
if replaceStatus {
76+
writeContentToFile(filepath, buffer.String())
77+
} else {
78+
logMessage(fmt.Sprintf("%s no match", filepath))
79+
}
80+
}
81+
82+
// Readln returns a single line (without the ending \n)
83+
// from the input buffered reader.
84+
// An error is returned iff there is an error with the
85+
// buffered reader.
86+
func Readln(r *bufio.Reader) (string, error) {
87+
var (isPrefix bool = true
88+
err error = nil
89+
line, ln []byte
90+
)
91+
for isPrefix && err == nil {
92+
line, isPrefix, err = r.ReadLine()
93+
ln = append(ln, line...)
94+
}
95+
return string(ln),err
96+
}
97+
3898

39-
if opts.PlainText {
99+
// Checks if there is a match in content, based on search options
100+
func searchMatch(content string) (bool) {
101+
if opts.Regex {
102+
if opts.SearchRegex.MatchString(content) {
103+
return true
104+
}
105+
} else {
40106
if strings.Contains(content, opts.Search) {
107+
return true
108+
}
109+
}
110+
111+
return false
112+
}
113+
114+
// Replace text in whole content based on search options
115+
func replaceText(content string) (string, bool) {
116+
status := false
117+
118+
if searchMatch(content) {
119+
status = true
120+
121+
if opts.Regex {
122+
content = opts.SearchRegex.ReplaceAllString(content, opts.Replace)
123+
} else {
41124
content = strings.Replace(content, opts.Search, opts.Replace, -1)
42-
writeContentToFile(file, content)
43125
}
126+
}
127+
128+
return content, status
129+
}
130+
131+
// Write content to file
132+
func writeContentToFile(filepath string, content string) {
133+
if opts.DryRun {
134+
title := fmt.Sprintf("%s:", filepath)
135+
136+
fmt.Println(title)
137+
fmt.Println(strings.Repeat("-", len(title)))
138+
fmt.Println(content)
139+
fmt.Println()
140+
fmt.Println()
44141
} else {
142+
var err error
143+
err = ioutil.WriteFile(filepath, []byte(content), 0)
144+
if err != nil {
145+
panic(err)
146+
}
147+
}
148+
}
149+
150+
151+
// Log message
152+
func logMessage(message string) {
153+
if opts.Verbose {
154+
fmt.Println(message)
155+
}
156+
}
157+
158+
func logError(err error) {
159+
fmt.Printf("Error: %s\n", err)
160+
}
161+
162+
// Process search option
163+
// Compiles regexp if regexp is used
164+
func processSearch() {
165+
if opts.Regex {
45166
regex := opts.Search
46167

47168
if opts.IgnoreCase {
48169
regex = "(?i:" + regex + ")"
49170
}
50171

51-
re := regexp.MustCompile(regex)
52-
53-
if re.MatchString(content) {
54-
content = re.ReplaceAllString(content, opts.Replace)
55-
writeContentToFile(file, content)
56-
}
172+
opts.SearchRegex = regexp.MustCompile(regex)
57173
}
58-
59174
}
60175

61-
func writeContentToFile(file string, content string) {
62-
var err error
63-
err = ioutil.WriteFile(file, []byte(content), 0)
64-
if err != nil {
65-
panic(err)
176+
func handleSpecialOptions(argparser *flags.Parser, args []string) {
177+
if (opts.ShowVersion) {
178+
fmt.Printf("goreplace %s\n", Version)
179+
os.Exit(0)
66180
}
181+
182+
if (opts.ShowHelp) {
183+
argparser.WriteHelp(os.Stdout)
184+
os.Exit(1)
185+
}
186+
187+
if (len(args) == 0) {
188+
err := errors.New("No files specified")
189+
logError(err)
190+
fmt.Println()
191+
argparser.WriteHelp(os.Stdout)
192+
os.Exit(1)
193+
}
67194
}
68195

69196
func main() {
197+
var argparser = flags.NewParser(&opts, flags.PassDoubleDash)
70198
args, err := argparser.Parse()
199+
71200
if err != nil {
72-
panic(err)
201+
handleSpecialOptions(argparser, args)
202+
203+
logError(err)
204+
fmt.Println()
205+
argparser.WriteHelp(os.Stdout)
73206
os.Exit(1)
74207
}
75208

76-
if opts.ShowVersion {
77-
fmt.Printf("goreplace %s\n", Version)
78-
return
79-
}
209+
handleSpecialOptions(argparser, args)
80210

81-
if (opts.ShowHelp) || (opts.Search == "") || (opts.Replace == "") || (len(args) > 0) {
82-
fmt.Println("Usage: golang -s <search regex> -r <replace string> file1 file2 file3")
83-
argparser.WriteHelp(os.Stdout)
84-
os.Exit(1)
85-
}
211+
processSearch()
86212

87213
for i := range args {
88214
var file string
89215
file = args[i]
90216

91-
if opts.Verbose {
92-
fmt.Printf(" - checking %s\n", file)
217+
if opts.WholeLine {
218+
replaceLineInFile(file)
219+
} else {
220+
replaceContentInFile(file)
93221
}
94-
replaceInFile(file)
95222
}
96223

97224
os.Exit(0)

0 commit comments

Comments
 (0)