only 0.0.4.0 → 0.0.5.0
raw patch · 18 files changed
+399/−43 lines, 18 files
Files
- AUTHORS +1/−0
- README +0/−0
- only.1 +204/−0
- only.cabal +19/−1
- only.hs +86/−42
- tests/Makefile +8/−0
- tests/README +6/−0
- tests/e-both.txt +6/−0
- tests/i-all.sh +15/−0
- tests/t-eq-abs.sh +6/−0
- tests/t-eq-nil.sh +6/−0
- tests/t-eq-regex-char.sh +6/−0
- tests/t-eq-regex-zero.sh +6/−0
- tests/t-eq-regex.sh +6/−0
- tests/t-grep-after.sh +6/−0
- tests/t-grep-before.sh +6/−0
- tests/t-grep-dot.sh +6/−0
- tests/t-grep-to.sh +6/−0
+ AUTHORS view
@@ -0,0 +1,1 @@+Andrew Robbins <and_j_rob(AT)yahoo.com>
+ README view
+ only.1 view
@@ -0,0 +1,204 @@+.\" Process this file with+.\" groff -man -Tascii foo.1+.\"+.TH ONLY 1 "" Haskell ""+.SH NAME+only \- an advanced filter for words, lines, and more+.SH SYNOPSIS+.B only [\-[bcwlf]+.I EXPR+.B ] ...+.I file+.B ...+.SH DESCRIPTION+.B Only+is an advanced filtering tool, like+.B grep, +but instead of filtering only on lines,+it can also filter on characters or words, called+.I tokens+in general.+When tokens +.I match, +there are two options that allow for greater control than+.B grep. +They can appear before and/or after a+.I regex+and are called+.I absolute indices+and+.I relative indices, +respectively. +.I Absolute indices+refer to matches, whereas+.I relative indices+refer to tokens, with the match being token zero.+For example, +.B -l+.I N/regex/M+will show the M-th line after the N-th occurance of+.I regex.+.P+For a more detailed description, see below.+.SH OPTIONS+.IP (-b|--bytes)=EXPR+Byte mode+.IP (-c|--chars)=EXPR+Character mode+.IP (-w|--words)=EXPR+Word mode+.IP (-l|--lines)=EXPR+Line mode+.IP (-f|--files)=EXPR+File mode+.SH EXTENDED DESCRIPTION+The original goal of+.B 'only'+was to combine the features of+.B head,+.B tail,+.B grep,+and+.B cut+into a single utility that was capable of all of their features,+but with the power to do so much more. For example, +.B head+and+.B tail+are good for selecting the first n-lines or last n-lines of a file,+but what if you want lines 10-30? Neither utility would be very+good alone, and combining them to accomplish your goal would be+a nightmare. Granted, one could probably construct a one liner in+.B awk+or+.B perl+to achieve the desired effect, but at the expense of clarity.+.P+To overview the features of+.B only,+there are two major kinds of inputs: +.I files +and +.I modes. +A file can+either be a filename or+.B '\-'+which means standard input.+The modes currently supported are:+.B bytes,+.B characters,+.B words,+.B lines,+and+.B files.+The difference between each mode is what the pattern /^.*$/ will match.+When no pattern is given, and a number is given instead, then+it will refer to the appropriate token type, for example, the first word, +the second line, etc. +.P+In byte mode, the input is broken up into 8-bit octets,+so the patterns must only match a single byte. In character mode, the input+is broken up according to the specified encoding (or UTF-8 if unspecified),+where each character may be multiple bytes. In word mode, the separators+can be any white-space, so it tries to remember what+separator was there in the beginning, +and puts it back before displaying.+In line mode,+.B only+behaves very similar to+.B grep+but with a few extra features.+In file mode, the filenames are not shown (unless -F is used) but the +entire file is shown if it matches the pattern.+.SH \ \ \ Syntax+.I Matching expressions+are expressions written in a small language +that forms a super-set of regular expressions.+The +.B syntax +of matching expressions+are the same regardless of what the current mode is.+This is true even of byte mode, where you must write "\\xFF" if you want a+non-printable character. Matching expressions can be as simple as a number+or a word. First,+.B only+tries to parse an expression as a number, then as an expression of the form+.I M/regex/N+and if that fails, then it treats the entire expression as a regex. Each M+and N may be a numeric expression.+.I Numeric expressions+have the syntax (in pseudo-Parsec):++ num = [+\-][0-9]++ numeric = sepBy numbers ','+ numbers = num ';' num ':' num # from A to C step (A-B)+ | num ':' num ';' num # from A to B step C+ | num ':' num # from A to B+ | num++which means you can specify just a single number (3) or something as complicated+as multiple ranges (such as 3:5,100:109). These numeric expressions can occur on+either side of the regex, or both sides with a combined effect. The+syntax of the entire matching expression is:++ expr = do optional numeric+ c <- punct ; regex ; c+ (try c ; regex ; c ; optional num+ | optional numeric)+ | numeric+ | regex++where+.I punct+is any ASCII punctuation character except ".,:;",+and+.I regex+is a POSIX extended regular expression. +.\" This serves to discribe the syntax of matching expressions.+.SH \ \ \ Semantics+The+.B semantics+of matching expressions are a little harder to describe. However, a generalization+of the example given above should hold true:++ "N/regex/M" means the M-th +.I tokens +relative to the N-th +.I matches++The default for+.I N+is +.B 1:-1 +and the default for+.I M+is +.B 0. The+.I N+are known as+.I absolute indices,+and the+.I M+are known as+.I relative indices. +Absolute indices will take the list of matches (the list of tokens that were matched by the regular expression), and apply use the numbers in M as the indices of this list. This gives you the ability to select the first match (1) or the last match (-1). If you use negative numbers, then it will count from the end of file going backwards, so (-2) would be the second to last match. Relative indices will take the list of matches, the original list of tokens, and for each match, it forms a virtual list where 0 refers to the match's index in the list of tokens. This allows one to emulate+.B grep's+\-A (after) and \-B (before) options. +.P+Here are some equivalent command-lines for "after" a match:++ grep -A3 expr file.txt+ only -l/expr/0:3 file.txt++Here are some equivalent command-lines for "before" a match:++ grep -B3 expr file.txt+ only -l/expr/-3:0 file.txt++Normally, these would be used to select+line numbers, like if you got a compiler error in a file with 10 million lines,+and you just wanted to see the surrounding text.+.SH FILES+.I ~/.onlyrc+.RS+A user configuration file. [Not implemented yet]
only.cabal view
@@ -1,5 +1,5 @@ Cabal-version: >= 1.2.0.0-version: 0.0.4.0+version: 0.0.5.0 name: only license: GPL@@ -15,6 +15,24 @@ on word patterns or line patterns like never before! Not only can you search with 'only -l patt' but you can select the n-th match with '-l n\/patt\/' and the next 3 lines with '-l \/patt\/0:3'.+extra-source-files:+ AUTHORS+ LICENSE+ README+ only.1+ tests/e-both.txt+ tests/i-all.sh+ tests/Makefile+ tests/README+ tests/t-eq-abs.sh+ tests/t-eq-nil.sh+ tests/t-eq-regex-char.sh+ tests/t-eq-regex-zero.sh+ tests/t-eq-regex.sh+ tests/t-grep-after.sh+ tests/t-grep-before.sh+ tests/t-grep-dot.sh+ tests/t-grep-to.sh executable only main-is: only.hs
only.hs view
@@ -20,7 +20,7 @@ progName = "only" -- usage stuff-version = "only (GNU Only) 0.0.4.0\n"+version = "only (GNU Only) 0.0.5.0\n" header = unlines [ version,@@ -36,8 +36,9 @@ "\tNUMBER Token (word/line) selection", "\t/REGEX/NUMBER Token (word/line) relative to match (0-based, +/-)", "\tNUMBER/REGEX/ Select which matches to print (1-based, +)",- "\tNUMBER/REGEX/NUMBER Both", "",- " where [NUMBER] is one of:",+ "\tNUMBER/REGEX/NUMBER Both",+ "\t/REGEX/:/REGEX/ Between two expressions", + "", " where [NUMBER] is one of:", "\tAT One", "\tFROM:TO Range", "\tFROM:TO;STEP Range with step",@@ -77,7 +78,8 @@ -- data stuff type Match = (Int, String) -data OnlyExpr = OnlyExpr {+data OnlyExpr = OnlyRange [Int] String String Int+ | OnlyExpr { oeAbs :: [Int], oePat :: String, oeRel :: [Int]} deriving (Read, Show, Eq)@@ -102,64 +104,79 @@ print ctx -- parser stuff-parseDigits :: CharParser () [Int]-parseDigits = do+parseNum :: CharParser () Int+parseNum = do+ -- this is where to implement '--negative' behaviour+ sign <- option "" (char '-' >> return "-")+ num <- many1 digit+ return (read $ sign ++ num)+ +parseNums :: CharParser () [Int]+parseNums = do lists <- sepBy ((try nexts) <|> (try steps) <|> (try range) <|> number) (char ',' >> return []) return (concat lists)- where digits :: CharParser () Int- digits = do- -- this is where to implement '--negative' behaviour- sign <- option "" (char '-' >> return "-")- num <- many1 digit- return (read $ sign ++ num)- number :: CharParser () [Int]+ where number :: CharParser () [Int] number = do- num <- digits+ num <- parseNum return [num] range :: CharParser () [Int] range = do- start <- digits+ start <- parseNum char ':'- end <- digits+ end <- parseNum return [start .. end] steps :: CharParser () [Int] steps = do- start <- digits+ start <- parseNum char ':'- end <- digits+ end <- parseNum char ';'- step <- digits+ step <- parseNum return [start, (start + step) .. end] nexts :: CharParser () [Int] nexts = do- start <- digits+ start <- parseNum char ';'- next <- digits+ next <- parseNum char ':'- end <- digits+ end <- parseNum return [start, next .. end] parseRegex :: CharParser () OnlyExpr-parseRegex = (try regex) <|> (try number) <|> word+parseRegex = (try pattRange) <|> (try pattIndex) <|> (try number) <|> word where word = do str <- many anyChar return (OnlyExpr [] str []) number = do- abs <- parseDigits+ abs <- parseNums if abs == [] then word else return (OnlyExpr abs "" [])- regex = do- abs <- parseDigits+ regexChar = do ch <- noneOf $ ".,:;" ++ ['A'..'Z'] ++ ['a'..'z']+ return ch+ regex = do+ ch <- regexChar pat <- many $ noneOf [ch] char ch- rel <- parseDigits+ return pat+ pattIndex = do+ abs <- parseNums+ pat <- regex+ rel <- parseNums return (OnlyExpr abs pat rel)+ pattRange = do+ abs <- parseNums+ pat1 <- regex+ char ':'+ pat2 <- regex+ rel <- parseNum -- only one number allowed!+ return (OnlyRange abs pat1 pat2 rel) + readRegex :: FilePath -> String -> IO OnlyExpr readRegex path expr = if expr == ""@@ -257,25 +274,52 @@ doSep :: (String -> [String]) -> String -> OnlyCtx -> OnlyMode -> IO OnlyCtx doSep sep expr ctx@(OnlyCtx orig _) mode = do+ -- initial state+ let full = map (\n -> (n, getIndex seps n "")) [1..length seps]+ seps = sep orig++ -- normalize expression path <- fileGet- oe@(OnlyExpr abs pat rel) <- readRegex path expr+ oexpr <- readRegex path expr+ parts <- case oexpr of+ oe@(OnlyExpr abs pat rel) -> return [doParts full oe]+ oe@(OnlyRange _ _ _ _) -> do+ let oes = doRange full oe+ return (map (doParts full) oes)+ -- final processing+ return (ctx {ocParts = concat parts}) - let abs2ls :: [Match] -> Int -> Match- abs2ls ms n = getIndex ms n (-1, "")- rel2ls :: [Match] -> [Match] -> Match -> Int -> Match- rel2ls fs as (na, _) nr = tupleLookup n fs where n = na + nr- tupleLookup n xs = (n, (fromMaybe "")$lookup n xs)+doAbs :: [a] -> [Int] -> [Int]+doAbs some abs = if abs == [] then [1..length some] else abs - let seps = sep orig- some = doMatch oe full- full = map (\n -> (n, getIndex seps n "")) [1..length seps]- abss = [abs2ls some a | - a <- (if abs == [] then [1..length some] else abs)]- rels = [rel2ls full abss a r | - r <- (if rel == [] then [0] else rel), a <- abss]- parts = map (initCtx . snd) rels+doRel :: [Int] -> [Int]+doRel rel = if rel == [] then [0] else rel - return (ctx {ocParts = parts})+doParts :: [Match] -> OnlyExpr -> [OnlyCtx]+doParts full oe@(OnlyExpr abs pat rel) = parts where+ abs2ls :: [Match] -> Int -> Match+ abs2ls ms n = getIndex ms n (-1, "")+ rel2ls :: [Match] -> [Match] -> Match -> Int -> Match+ rel2ls fs as (na, _) nr = tupleLookup n fs where n = na + nr+ tupleLookup n xs = (n, (fromMaybe "")$lookup n xs)+ abss = [abs2ls some a | a <- doAbs some abs]+ rels = [rel2ls full abss a r | r <- doRel rel, a <- abss]+ parts = map (initCtx . snd) rels+ some = doMatch oe full++doRange :: [Match] -> OnlyExpr -> [OnlyExpr]+doRange full (OnlyRange abs pat1 pat2 rel) = oes where+ oes = map (m2exp ls) (doAbs ls abs)+ ls = filter (/=[]) $ map (toRange match2) match1+ match1 = doMatch (OnlyExpr [] pat1 []) full+ match2 = doMatch (OnlyExpr [] pat2 []) full+ m2exp ls k = OnlyExpr [k] pat1 (getIndex ls k [])+ nextAfter :: Int -> [Match] -> [Match]+ nextAfter i ms = dropWhile (\(j,_) -> i >= j) ms+ toRange :: [Match] -> Match -> [Int]+ toRange ms m@(i,_) = case nextAfter i ms of+ (j,_):_ -> [0 .. j - i + rel]+ [] -> [] doMatch :: OnlyExpr -> [Match] -> [Match] doMatch oe = if pat == "" then id
+ tests/Makefile view
@@ -0,0 +1,8 @@+all: test++test:+ @echo+ @for TEST in $$(ls t-*) ; do bash $$TEST ; done++clean:+ @if [ x"$$(echo *~)" != x'*~' ] ; then rm -f *~ ; fi
+ tests/README view
@@ -0,0 +1,6 @@+The files in this directory (t/) +have the following naming convention:++ e-*.txt = example files+ i-*.sh = include files+ t-*.sh = test files
+ tests/e-both.txt view
@@ -0,0 +1,6 @@+1 Alfred - This is going to be the longest comment section of them all...+2 Barbra - She has nothing to say.+3 Cheese - Mice love it!+4 Dorsey - The only one who seems to know anything.+5 Edward - Not just a syllable anymore!+6 Finley - Someone who you might not want to meet.
+ tests/i-all.sh view
@@ -0,0 +1,15 @@+NAME="$0"+BASE="${NAME%.sh}"+ONLY="../dist/build/only/only"+GREP="grep"++cd "$(dirname $NAME)"++function test-eq () {+ if [ x"$1" = x"$2" ] ; then+ echo -n PASS+ else+ echo -n FAIL+ fi+ echo " : $BASE"+}
+ tests/t-eq-abs.sh view
@@ -0,0 +1,6 @@+#!/bin/sh+. i-all.sh++test-eq \+ "$($ONLY e-both.txt -l3)" \+ "$($ONLY e-both.txt -l3//)"
+ tests/t-eq-nil.sh view
@@ -0,0 +1,6 @@+#!/bin/sh+. i-all.sh++test-eq \+ "$($ONLY e-both.txt -l '')" \+ "$($ONLY e-both.txt -l //)"
+ tests/t-eq-regex-char.sh view
@@ -0,0 +1,6 @@+#!/bin/sh+. i-all.sh++test-eq \+ "$($ONLY e-both.txt -l @to@)" \+ "$($ONLY e-both.txt -l /to/)"
+ tests/t-eq-regex-zero.sh view
@@ -0,0 +1,6 @@+#!/bin/sh+. i-all.sh++test-eq \+ "$($ONLY e-both.txt -l3//)" \+ "$($ONLY e-both.txt -l3//0)"
+ tests/t-eq-regex.sh view
@@ -0,0 +1,6 @@+#!/bin/sh+. i-all.sh++test-eq \+ "$($ONLY e-both.txt -lto)" \+ "$($ONLY e-both.txt -l/to/)"
+ tests/t-grep-after.sh view
@@ -0,0 +1,6 @@+#!/bin/sh+. i-all.sh++test-eq \+ "$($GREP -A2 She e-both.txt)" \+ "$($ONLY -l/She/0:2 e-both.txt)"
+ tests/t-grep-before.sh view
@@ -0,0 +1,6 @@+#!/bin/sh+. i-all.sh++test-eq \+ "$($GREP -B1 She e-both.txt)" \+ "$($ONLY -l/She/-1:0 e-both.txt)"
+ tests/t-grep-dot.sh view
@@ -0,0 +1,6 @@+#!/bin/sh+. i-all.sh++test-eq \+ "$($GREP '\.' e-both.txt)" \+ "$($ONLY -l'\.' e-both.txt)"
+ tests/t-grep-to.sh view
@@ -0,0 +1,6 @@+#!/bin/sh+. i-all.sh++test-eq \+ "$($GREP to e-both.txt)" \+ "$($ONLY -lto e-both.txt)"