@@ -2,7 +2,10 @@ package main
22
33import (
44 "fmt"
5+ "errors"
6+ "bytes"
57 "io/ioutil"
8+ "bufio"
69 "os"
710 "strings"
811 "regexp"
@@ -15,83 +18,207 @@ const (
1518)
1619
1720var 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
69196func 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