packages feed

only 0.0.4.0 → 0.0.5.0

raw patch · 18 files changed

+399/−43 lines, 18 files

Files

+ 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)"