-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy patho-grep.odin
239 lines (192 loc) · 5.42 KB
/
o-grep.odin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
/*
Usage:
o_grep -e "search term" -f filename.txt
or:
o_grep -e "search term" < filename.txt
TODO: Add -i option for case insensitive
*/
package main
//core libraries
import "core:fmt"
import "core:os"
main :: proc() {
cmd_args := os.args[1:] //get an array of strings of args (excluded arg 0 which is exe path)
num_args := len(cmd_args)
fd_in, fd_out: os.Handle // input and output file decripters of Handle type
fd_out = os.stdout // output Handle is always stdout for this app
in_buf: [] byte // input buffer to hold text read from stdin or file
out_buf: [dynamic] byte // output buffer to fill with matches and output to stdout
defer { //delete at end of scope
delete(in_buf)
delete(out_buf)
}
read_ok:bool = false
fname,search: string
//error guard. Exit if no args supplied
if num_args > 0 {
search, fname = cmd_line_parser(cmd_args)
} else {
print_usage()
return
}
// use stdin as Handle unless filename is provided
if fname != "" {
err:os.Errno
fd_in,err = os.open(fname,os.O_RDONLY)
if err != 0 {
print_usage()
return //bail out
}
} else {
fd_in = os.stdin
}
//fill input buffer from ether stdin or file
in_buf,read_ok = os.read_entire_file(fd_in,context.allocator)
if !read_ok {
fmt.println("Read Error, aborting")
return //bail out
}
//fill output buffer with matched lines with highlighted searches
out_buf = grep(in_buf,search)
//write output buffer to stdout
for char in out_buf{
os.write_byte(fd_out, cast(byte)char)
}
}
/*
grep:
takes a data buffer and a search string as input.
outputs a buffer containing any lines with the search
string ( with added ANSI colour highlighting )
*/
grep :: proc (data: []byte, search: string) -> (out_buf: [dynamic]byte){
match: bool = false
in_line := make([dynamic]byte, 0, 1000)
ret_line := make([dynamic]byte, 0, 1000)
defer {
delete(in_line)
delete(ret_line)
}
for dat in data {
//fill buffer with current line
if dat != '\n'{
append(&in_line,dat)
}
else
{
//line finished, process it for a match
ret_line, match = line_grep(in_line,search)
//if a match, add line (including colour highlighting) to the output buffer
if match {
for letter in ret_line {
append(&out_buf, letter)
}
append(&out_buf, '\n') // add line feed back in to buffer.
match = false
}
//clear out old line and ret buffers
delete_dynamic_array(in_line)
delete_dynamic_array(ret_line)
//re-create them for next go around
in_line = make([dynamic]byte, 0, 1000)
ret_line = make([dynamic]byte, 0, 1000)
}
} //end of for dat in data
return
} //end of proc grep
/*
line_grep:
takes a dynamic buffer containg a line and search query as a string.
returns a dynamic buffer containing the line with any matches highlighted
and a boolean to indicate a successful match
*/
@private
line_grep :: proc (data: [dynamic]byte, search: string) -> (out_buf: [dynamic]byte, ret_match: bool){
sch_idx: int = 0
match := false
ret_match = false
for dat,dat_idx in data {
if data[dat_idx] == search[0] && !match { // no existing match and we've matched 1st char of search
for letter, idx in search
{
if cast(byte)letter == data[dat_idx + idx] { //lookahead the length of the search string to see if an exact match
match = true //keep matching chars
}
else {
match = false //doesn't match full search string
break // want to exit look ahead for loop
}
} //end of look ahead for loop
}
//colour highlighting logic (uses ANSI terminal codes) for fancy output
if(match) {
ret_match = true
if (len(search) == 1) //special case for single char search
{
append(&out_buf, ANSI_PUR) //start colour
append(&out_buf, data[dat_idx])
append(&out_buf, ANSI_RST) //end colour
match = false
}
else if sch_idx < len(search) - 1 // haven't got to last char of match yet
{
if sch_idx == 0 {
append(&out_buf, ANSI_PUR)
}
append(&out_buf, data[dat_idx])
sch_idx += 1
}
else
{
append(&out_buf, data[dat_idx]) //last char of match
append(&out_buf, ANSI_RST) //turn off colour
match = false // back to normal processing
sch_idx = 0
}
} // end of if match
else { //process non-coloured text
append(&out_buf, data[dat_idx])
}
} //end of for loop for processing input line buffer
return
} // end of proc line_grep
/*
cmd_line_parser:
takes an array of strings containing options
Valid options:
-e "search string"
-f "filename"
Returns:
a string with search query: search
a string with filename: fname
*/
cmd_line_parser :: proc(cmd_args: []string) -> (search: string, fname: string){
search_arg,file_arg: bool = false, false
for arg in cmd_args {
switch(arg)
{
case "-e": search_arg = true //set true for next swing through args
case "-f": file_arg = true
case: {
if search_arg { //last arg was -e, so this arg is "search string"
search_arg = false
search = arg
//fmt.println("search term:",search)
} else if file_arg { //last arg was -f, so this arg is "filename"
file_arg = false
fname = arg
}
}
}
}
return
}
/* err_to_string :: proc(err: os.Errno) -> (string){
return cast(string)os._darwin_string_error(cast(i32)err)
} */
print_usage :: proc() {
fmt.println("Usage:")
fmt.println("\to_grep -e \"search term\" -f filename.txt")
fmt.println("or:")
fmt.println("\to_grep -e \"search term\" < filename.txt")
}