diff options
-rwxr-xr-x | tools/nimgrep.nim | 191 |
1 files changed, 191 insertions, 0 deletions
diff --git a/tools/nimgrep.nim b/tools/nimgrep.nim new file mode 100755 index 000000000..cc1f89a74 --- /dev/null +++ b/tools/nimgrep.nim @@ -0,0 +1,191 @@ +# +# +# Nimrod Grep Utility +# (c) Copyright 2010 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +import + os, strutils, parseopt, pegs, re, terminal + +const + Usage = """ +Usage: nimgrep [options] [pattern] [files/directory] +Options: + --find, -f find the pattern (default) + --replace, -r replace the pattern + --peg pattern is a peg (default) + --re pattern is a regular expression + --recursive process directories recursively + --confirm confirm each occurence/replacement; there is a chance + to abort any time without touching the file(s) + --stdin read pattern from stdin (to avoid the shell's confusing + quoting rules) + --word, -w the pattern should have word boundaries + --ignore_case, -i be case insensitive + --ignore_style, -y be style insensitive +""" + +type + TOption = enum + optFind, optReplace, optPeg, optRegex, optRecursive, optConfirm, optStdin, + optWord, optIgnoreCase, optIgnoreStyle + TOptions = set[TOption] + TConfirmEnum = enum + ceAbort, ceYes, ceAll, ceNo, ceNone + +var + filename = "" + pattern = "" + replacement = "" + options: TOptions + +proc ask(msg: string): string = + stdout.write(msg) + result = stdin.readline() + +proc Confirm: TConfirmEnum = + while true: + case normalize(ask("[a]bort; [y]es, a[l]l, [n]o, non[e]: ")) + of "a", "abort": return ceAbort + of "y", "yes": return ceYes + of "l", "all": return ceAll + of "n", "no": return ceNo + of "e", "none": return ceNone + else: nil + +proc highlight(a, b, c: string) = + stdout.write(a) + terminal.WriteStyled(b) + stdout.writeln(c) + +proc countLines(s: string, first = 0, last = s.high): int = + var i = first + while i <= last: + if s[i] == '\13': + inc result + if i < last and s[i+1] == '\10': inc(i) + elif s[i] == '\10': + inc result + inc i + +proc processFile(filename: string) = + var buffer = system.readFile(filename) + if isNil(buffer): quit("cannot open file: " & filename) + var pegp: TPeg + var rep: TRegex + var result: string + + if optRegex in options: + if optIgnoreCase in options: + rep = re(pattern, {reExtended, reIgnoreCase}) + else: + rep = re(pattern) + else: + pegp = peg(pattern) + + if optReplace in options: + result = newString(buffer.len) + setLen(result, 0) + + var line = 1 + var i = 0 + var matches: array[0..re.MaxSubpatterns-1. string] + var reallyReplace = true + while i < buffer.len: + var t: tuple[first, last: int] + if optRegex in options: + quit "to implement" + else: + t = findBounds(buffer, pegp, matches, i) + + if t.first <= 0: break + inc(line, countLines(buffer, i, t.first-1)) + + var wholeMatch = buffer.copy(t.first, t.last) + echo "line ", line, ": ", wholeMatch + + if optReplace in options: + var r = replace(wholeMatch, pegp, replacement) + + if optConfirm in options: + case Confirm() + of ceAbort: + of ceYes: + of ceAll: + reallyReplace = true + of ceNo: + reallyReplace = false + of ceNone: + reallyReplace = false + if reallyReplace: + + + inc(line, countLines(buffer, t.first, t.last)) + + i = t.last+1 + + +proc walker(dir: string) = + for kind, path in walkDir(dir): + case kind + of pcFile: processFile(path) + of pcDirectory: + if optRecursive in options: + walker(path) + else: nil + +proc writeHelp() = quit(Usage) +proc writeVersion() = quit("1.0") + +proc checkOptions(subset: TOptions, a, b: string) = + if subset <= options: + quit("cannot specify both '$#' and '$#'" % [a, b]) + +for kind, key, val in getopt(): + case kind + of cmdArgument: + if options.contains(optStdIn): + filename = key + elif pattern.len == 0: + pattern = key + elif options.contains(optReplace) and replacement.len == 0: + replacement = key + else: + filename = key + of cmdLongOption, cmdShortOption: + case normalize(key) + of "find", "f": incl(options, optFind) + of "replace", "r": incl(options, optReplace) + of "peg": incl(options, optPeg) + of "re": incl(options, optRegex) + of "recursive": incl(options, optRecursive) + of "confirm": incl(options, optConfirm) + of "stdin": incl(options, optStdin) + of "word", "w": incl(options, optWord) + of "ignorecase", "i": incl(options, optIgnoreCase) + of "ignorestyle", "y": incl(options, optIgnoreStyle) + of "help", "h": writeHelp() + of "version", "v": writeVersion() + else: writeHelp() + of cmdEnd: assert(false) # cannot happen + +checkOptions({optFind, optReplace}, "find", "replace") +checkOptions({optPeg, optRegex}, "peg", "re") +checkOptions({optIgnoreCase, optIgnoreStyle}, "ignore_case", "ignore_style") +checkOptions({optIgnoreCase, optPeg}, "ignore_case", "peg") + +if optStdin in options: + pattern = ask("pattern [ENTER to exit]: ") + if IsNil(pattern) or pattern.len == 0: quit(0) + if optReplace in options: + replacement = ask("replacement [supports $1, $# notations]: ") + +if pattern.len == 0: + writeHelp() +else: + if filename.len == 0: filename = os.getCurrentDir() + walker(filename) + |