packages feed

Hsed 0.1 → 0.2

raw patch · 17 files changed

+1267/−1115 lines, 17 filesdep +directory

Dependencies added: directory

Files

− Ast.hs
@@ -1,90 +0,0 @@--- |--- Module      :  Ast--- Copyright   :  (c) Vitaliy Rkavishnikov--- License     :  BSD-style (see the file LICENSE)--- --- Maintainer  :  virukav@gmail.com--- Stability   :  experimental--- Portability :  non-portable------ The main types used in the program--module Ast where --import SedRegex (Pattern)-import Data.ByteString.Char8 (ByteString)---- | Editing commands-data SedCmd = SedCmd Address SedFun  deriving Show---- | Functions represents a single-character command verb-data SedFun = -              Group [SedCmd]         -- ^ { - group of the sed commands-            | LineNum                -- ^ = - write to standard output the current line number-            | Append Text            -- ^ a - append text following each line matched by address -            | Branch (Maybe Label)   -- ^ b - transfer control to Label-            | Change Text            -- ^ c - replace the lines selected by the address with Text-            | Delete                 -- ^ d - delete line(s) from pattern space-            | DeletePat              -- ^ D - delete (up to newline) of multiline pattern space-            | ReplacePat             -- ^ g - copy hold space into the pattern space-            | AppendPat              -- ^ G - add newline followed by hold space into the pattern space-            | ReplaceHold            -- ^ h - copy pattern space into hold space-            | AppendHold             -- ^ H - add newline followed by pattern space into the hold space-            | Insert Text            -- ^ i - insert Text before each line matched by address -            | List                   -- ^ l - list the pattern space, showing non-printing chars in ASCII-            | Next                   -- ^ n - read next line of input into pattern space-            | AppendLinePat          -- ^ N - add next input line and newline into pattern space-            | PrintPat               -- ^ p - print the addressed line(s)-            | WriteUpPat             -- ^ P - print (up to newline) of multiline pattern space   -            | Quit                   -- ^ q - quit when address is encounterd-            | ReadFile FilePath      -- ^ r - add contents of file to the pattern space-            | Substitute  Pattern Replacement Flags  -- ^ s - substitute Replacement for Pattern-            | Test (Maybe Label)     -- ^ t - branch to line marked by :label if substitution was made -            | WriteFile FilePath     -- ^ w - write the line to file if a replacement was done-            | Exchange               -- ^ x - exchange pattern space with hold space-            | Transform Text Text    -- ^ y - transform each char by position in Text to Text-            | Label Label            -- ^ : - label a line in the scipt for transfering by b or t.-            | Comment                -- ^ # - ignore a line in the script except "#n" in the first line -            | EmptyCmd               -- ^   - ignore spaces-    deriving Show---- | The work buffer to keep the selected line(s)---data PatternSpace = PatternSpace [ByteString] deriving Show---- | The work buffer to keep the line(s) temporarily---data HoldSpace = HoldSpace [ByteString] deriving Show---- | An address is either a decimal number that counts input lines cumulatively across files, ---   a '$' character that addresses the last line of input, or a context address as BRE -data Addr = LineNumber Int-          | LastLine-          | Pat Pattern-    deriving Show---- | A permissable address is representing by zero, one or two addresses-data Address = Address (Maybe Addr) (Maybe Addr) Invert-    deriving Show---- | Used in the replacement string. An appersand ('&') will be replaced by the---   string matched the BRE. The characters "\n", where n is a digit will be---   replaced by the corresponding back-reference expression.-data Occurrence = Replace Int | ReplaceAll -    deriving Show---- | The flag to control the pattern space output in the substitute function-type OutputPat = Bool---- | The allowed sequence of the Occurrence and OutputPat flags in the substitute---   function-data OccurrencePrint = OccurrencePrint (Maybe Occurrence) OutputPat |-                       PrintOccurrence OutputPat (Maybe Occurrence)-    deriving Show---- | Flags used in the substitute command-data Flags = Flags (Maybe OccurrencePrint) (Maybe FilePath) -    deriving Show--type Replacement = ByteString-type Invert = Bool-type Text = ByteString-type Label = ByteString
Hsed.cabal view
@@ -1,5 +1,5 @@ Name:                Hsed-Version:             0.1+Version:             0.2 Description:         Haskell Stream Editor License:             BSD3 License-File:        LICENSE@@ -35,21 +35,28 @@                      WriteFile.sed,company.lst Cabal-Version:       >=1.2 +Library+  Hs-Source-Dirs:    src+  Exposed-Modules:   Hsed.Sed+  Other-modules:     Hsed.Ast, Hsed.SedRegex, Hsed.Parsec, Hsed.SedState, Hsed.StreamEd+  Build-Depends:     base >= 3.0.3.2 && < 5, +                     Glob >= 0.5.1, +                     cmdargs >= 0.3, +                     data-accessor >= 0.2.1.4,+                     data-accessor-template >= 0.2.1.5, +                     data-accessor-transformers >= 0.2.1.2, +                     parsec, +                     bytestring, +                     regex-compat, +                     regex-base,+                     regex-posix, +                     array, +                     filepath, +                     directory,+                     mtl, +                     haskell98+ Executable Hsed-    Main-is:             Hsed.hs-    Other-modules:  	 Ast, SedRegex, Parsec, SedState, StreamEd, TestSuite-    Build-Depends:       base >= 3.0.3.2 && < 5, -                         Glob >= 0.5.1, -                         cmdargs >= 0.3, -                         data-accessor >= 0.2.1.4,-                         data-accessor-template >= 0.2.1.5, -                         data-accessor-transformers >= 0.2.1.2, -                         parsec, -                         bytestring, -                         regex-compat, -                         regex-base,-                         regex-posix, -                         array, -                         filepath, -                         mtl, -                         haskell98+    Hs-Source-Dirs:  src+    Main-is:         Main.hs+    Other-modules:   TestSuite
− Hsed.hs
@@ -1,33 +0,0 @@--- |--- Module      :  Main--- Copyright   :  (c) Vitaliy Rkavishnikov--- License     :  BSD-style (see the file LICENSE)--- --- Maintainer  :  virukav@gmail.com--- Stability   :  experimental--- Portability :  non-portable------ See "The Open Group Base Specifications Issue 7" for the requirements--- This version of the Haskell Sed uses regex-posix package to parse all regular --- expressions. At the moment it doesn't supports the back-references in the RE.--module Main where--import System (getArgs)-import StreamEd (run)--main :: IO ()-main = do-    args <- getArgs-    if null args || head args == "--help" then do-      putStrLn usage-      return ()-     else run args--usage :: String-usage = unlines help -  where help = ["usage: Hsed [-n] script [file...]",-                "       Hsed [-n] -e script [-e script]... [-f script_file]... [file...]",-                "       Hsed [-n] [-e script]... -f script_file [-f script_file]... [file...]"-               ]-
− Parsec.hs
@@ -1,235 +0,0 @@--- |--- Module      :  Parsec--- Copyright   :  (c) Vitaliy Rkavishnikov--- License     :  BSD-style (see the file LICENSE)--- --- Maintainer  :  virukav@gmail.com--- Stability   :  experimental--- Portability :  non-portable------ Sed commands parser. See "The Open Group Base Specifications Issue 7" for--- parsing requirements. The current version of the Haskell Sed doesn't supports--- the back-references in the RE.--module Parsec where--import Prelude hiding (readFile, writeFile)-import Text.ParserCombinators.Parsec hiding (label)-import qualified Data.ByteString.Char8 as B-import Ast-import SedRegex---- | If an RE is empty last RE used in the last command applied -data ParserState = ParserState {-    lastRE :: Pattern-} --emptyState = ParserState { -    lastRE = B.pack ""   -}--type SedParser = GenParser Char ParserState-type Stream = String--eol = oneOf "\n\r" >> return ()-eoleof = choice [eol, eof]-slash = char '/'-comma = char ','-semi = char ';'-backslash = char '\\'-number = many1 digit >>= \n -> return $ read n-invert = (spaces >> char '!' >> return True) <|> return False--parseSed :: SedParser a -> Stream -> Either ParseError a-parseSed p = runParser p emptyState ""--parseRE :: String -> SedParser Pattern-parseRE pat = do-    let patB = B.pack pat-    updateState (\(ParserState _)  -> ParserState patB)-    return patB--pattern open close val = do-    pat <- between open close val-    if null pat then do-        s <- getState-        return $ lastRE s-     else parseRE (unesc pat)--addr = -    fmap (Just . LineNumber) number <|>-    (char '$' >> return (Just LastLine)) <|>-    (pattern slash slash val >>= \pat -> return $ Just (Pat pat))-    where val = many (noneOf "/")--addr1 = do-    a1 <- addr-    spaces-    b <- invert-    return $ Address a1 Nothing b--addr2 = do-    a1 <- addr-    comma <?> ","-    a2 <- addr <?> "bad address" -    spaces-    b <- invert-    return $ Address a1 a2 b--address :: SedParser Address-address = -    try addr2 <|> try addr1 <|> -    (invert >>= \b -> return $ Address Nothing Nothing b) --sedCmds :: SedParser [SedCmd]-sedCmds = -    many1 $ try (space >> return emptyCmd) <|>-            (do { x <- sedCmd; endCmd; return x })-    where-    endCmd = choice [eol, eof, semiend, comm, spaces >> return ()]-       where-       semiend = try (spaces >> semi >> spaces >> return ())-       comm = lookAhead (char '#') >> return ()--sedCmd :: SedParser SedCmd-sedCmd = do-    a <- address-    fun <- sedFun-    return $ SedCmd a fun--sedFun :: SedParser SedFun-sedFun = choice functions >>= \f -> return f--functions = -    [substitute, group, append, change, insert, lineNum, delete, deletePat, replacePat, -     appendPat, replaceHold, appendHold, list, next, appendLinePat, printPat,-     writeUpPat, quit, exchange, comment, branch, test, readFile, writeFile,-     label, transform-    ]--append        = textFun 'a' Append-change        = textFun 'c' Change -insert        = textFun 'i' Insert--readFile      = fileFun 'r' ReadFile-writeFile     = fileFun 'w' WriteFile-label         = argFun ':' Label--lineNum       = bareFun '=' LineNum-delete        = bareFun 'd' Delete-deletePat     = bareFun 'D' DeletePat-replacePat    = bareFun 'g' ReplacePat-appendPat     = bareFun 'G' AppendPat-replaceHold   = bareFun 'h' ReplaceHold-appendHold    = bareFun 'H' AppendHold-list          = bareFun 'l' List-next          = bareFun 'n' Next-appendLinePat = bareFun 'N' AppendLinePat-printPat      = bareFun 'p' PrintPat-writeUpPat    = bareFun 'P' WriteUpPat-quit          = bareFun 'q' Quit-exchange      = bareFun 'x' Exchange--branch        = gotoFun 'b' Branch-test          = gotoFun 't' Test--bareFun :: Char -> SedFun -> SedParser SedFun-bareFun c f = char c >> return f--textFun :: Char -> (Text -> SedFun) -> SedParser SedFun-textFun c f = do-    char c-    backslash <?> "backslash"-    eol <?> "end of line"-    parts <- lines-    return $ f (B.pack (init $ unlines parts))-    where-       lines = do {x <- line; try eoleof; return x}-       line = sepBy part (backslash >> eol)-       part = many (noneOf "\\\n")--fileFun :: Char -> (FilePath -> SedFun) -> SedParser SedFun-fileFun c f = -    char c >> spaces >> -    manyTill anyChar (lookAhead eoleof) >>= \l -> return $ f l--argFun :: Char -> (B.ByteString -> SedFun) -> SedParser SedFun-argFun c f = -    char c >> spaces >> -    manyTill anyChar (lookAhead eoleof) >>= \l -> return $ f (B.pack l)--gotoFun :: Char -> (Maybe Label -> SedFun) -> SedParser SedFun-gotoFun c f = do-    char c-    many $ choice[char ' ', char '\t']-    label <- manyTill anyChar (lookAhead eoleof)-    if null label then return $ f Nothing-      else return $ f (Just $ B.pack label)--group = do-    char '{'-    cmds <- sedCmds-    spaces  -    char '}' <?> "}"-    return $ Group cmds--comment = do-    char '#'-    manyTill anyChar (lookAhead eoleof)-    return Comment--transform = do-    char 'y'-    slash <?> "/"-    str1 <- manyTill anyChar slash-    str2 <- manyTill anyChar slash-    return $ Transform (B.pack str1) (B.pack str2)--substitute = do-    char 's'-    delim <- lookAhead anyChar-    let val = many $ noneOf [delim]-    pat <- pattern (char delim) (char delim) val-    repl <- rhs delim-    fs <- flags -    return $ Substitute (B.pack $ unesc (B.unpack pat)) (B.pack $ esc repl) fs-    where-      esc [] = []-      esc [x] | x == '&' = "\\0"-              | otherwise = [x]-      esc (x:y:ys) | [x,y] == "\\n" = '\n':esc ys-                   | [x,y] == "\\\n" = esc (y:ys)-                   | [x,y] == "\\&" = '&':esc ys-                   | x     == '&' = "\\0" ++ esc (y:ys)-                   | otherwise = x:esc(y:ys)           --      rhs delim = manyTill anyChar (char delim)--flags = do-    op <- occur-    out <- outFile-    return $ Flags op out-    where-      occur = occurPrint <|> return Nothing-      outFile = -          (char 'w' >> spaces >> -          manyTill anyChar (lookAhead eoleof) >>= \f -> return $ Just f) <|>-          return Nothing-      occurPrint = -          occurrence >>= \o -> prn >>= \p ->-          return $ Just $ OccurrencePrint o p-      occurrence = -          (char 'g' >> return (Just ReplaceAll)) <|>-          (number >>= \n -> return $ Just $ Replace n) <|>-          return Nothing-      prn = -          (char 'p' >> return True) <|>-          return False--unesc [] = []-unesc [x] = [x]-unesc (x:y:xs) | x:[y] == "\\t" = '\t':unesc xs-               | x:[y] == "\\n" = '\n':unesc xs-               | otherwise = x : unesc (y:xs)--emptyCmd = SedCmd (Address Nothing Nothing False) EmptyCmd
− SedRegex.hs
@@ -1,74 +0,0 @@--- |--- Module      :  SedRegex---- Maintainer  :  virukav@gmail.com--- Stability   :  experimental--- Portability :  non-portable-- |---- The Sed regular expression implementation based on the regex-posix package.   --module SedRegex where--import Data.Array ((!))-import Text.Regex.Posix---import Text.Regex.TDFA-import Text.Regex-import qualified Data.ByteString.Char8 as B--type Pattern = B.ByteString---- | Replaces every occurance of the given regexp with the replacement string. ---   Modification of the subRegex function from regex-posix package.-sedSubRegex :: B.ByteString         -- ^ Search pattern-            -> B.ByteString         -- ^ Input string-            -> B.ByteString         -- ^ Replacement text-            -> Int                  -- ^ Occurrence-            -> (B.ByteString, Bool) -- ^ (Output string, Replacement occurs)---sedSubRegex _ "" _ _ = ("", True)-sedSubRegex pat inp repl n =-  let regexp = makeRegexOpts compExtended defaultExecOpt pat-  --let regexp = mkRegex pat -- for TDFA-      compile _i str [] = \ _m -> B.append str-      compile i str ((xstr,(off,len)):rest) =-        let i' = off+len-            pre = B.take (off-i) str-            str' = B.drop (i'-i) str-            slashEsc = B.pack "\\"-            app m = if xstr == slashEsc then B.append slashEsc-                     else let x = read (B.unpack xstr) :: Int in -                            B.append (fst (m!x))-        in if B.null str' then \ m -> B.append pre . app m-             else \ m -> B.append pre . app m . compile i' str' rest m--      compiled :: MatchText B.ByteString -> B.ByteString -> B.ByteString-      compiled = compile 0 repl findrefs where-        -- bre matches a backslash then capture either a backslash or some digits-        bre = mkRegex "\\\\(\\\\|[0-9]+)"-        findrefs = map (\m -> (fst (m!1),snd (m!0))) (matchAllText bre repl)-      go _i str [] = str-      go i str (m:ms) =-        let (_,(off,len)) = m!0-            i' = off+len-            pre = B.take (off-i) str-            str' = B.drop (i'-i) str-        in if B.null str' then B.concat [pre, compiled m B.empty]-             else B.concat [pre, compiled m (go i' str' ms)]-   -      occur regexp inp repl n pre =-        if inp == B.empty then (B.empty, False)-         else-          case matchOnceText regexp inp of-           Nothing -> (inp, False)-           Just (p, m, r) -> -              if n > 1 then -                 let acc = B.concat [pre, p, fst (m!0)] in-                     occur regexp r repl (n-1) acc-               else-                  (B.concat [pre, go 0 inp [m]], True)-  in if n == 0 then -       let ms = matchAllText regexp inp in-         (go 0 inp ms, (not . null) ms)-      else occur regexp inp repl n B.empty---- | Match the regular expression against text-matchRE pat str = str =~ pat :: Bool
− SedState.hs
@@ -1,67 +0,0 @@-{-# LANGUAGE TemplateHaskell #-}---- |--- Module      :  SedState--- Copyright   :  (c) Vitaliy Rkavishnikov--- License     :  BSD-style (see the file LICENSE)--- --- Maintainer  :  virukav@gmail.com--- Stability   :  experimental--- Portability :  non-portable------ The state of the program --module SedState where--import qualified Control.Monad.State as S-import qualified Data.Accessor.Basic as A-import qualified Data.ByteString.Char8 as B-import Data.Accessor.Template (deriveAccessors)-import System.IO-import Ast--data Env = Env {-  ast_ :: [SedCmd],                 -- ^ Parsed Sed commands-  defOutput_ :: !Bool,              -- ^ Suppress the default output-  lastLine_ :: !Int,                -- ^ The last line index-  curLine_ :: !Int,                 -- ^ The current line index-  inRange_ :: !Bool,                -- ^ Is pattern space matches the address range-  patternSpace_ :: !B.ByteString,   -- ^ The buffer to keep the selected line(s)-  holdSpace_ :: !B.ByteString,      -- ^ The buffer to keep the line(s) temporarily-  appendSpace_ :: [B.ByteString],   -- ^ The buffer to keep the append lines-  memorySpace_ :: !B.ByteString,    -- ^ Store the output in the memory-  useMemSpace_ :: !Bool,            -- ^ If True the Sed output is stored in the memory buffer-  exit_ :: !Bool,                   -- ^ Exit the stream editor-  fileout_ :: [(FilePath, Handle)], -- ^ Write (w command) files handles -  subst_ :: !Bool                   -- ^ The result of the last substitution-} deriving (Show)--$( deriveAccessors ''Env )--type SedState = S.StateT Env IO--initEnv :: Env-initEnv = Env {-  ast_ = [],-  defOutput_ = True,-  lastLine_ = 0,-  curLine_ = 0,-  inRange_ = False,-  patternSpace_ = B.empty,-  holdSpace_ = B.empty,-  appendSpace_ = [],-  memorySpace_ = B.empty,-  useMemSpace_ = False,-  exit_  = False,-  fileout_ = [],-  subst_ = False-}--set :: A.T Env a -> a -> SedState ()-set f x = S.modify (A.set f x)--get :: A.T Env a -> SedState a-get f = S.gets (A.get f)--modify :: A.T Env a -> (a -> a) -> SedState ()-modify f g = S.modify (A.modify f g)
− StreamEd.hs
@@ -1,410 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}---- |--- Module      :  StreamEd--- Copyright   :  (c) Vitaliy Rkavishnikov--- License     :  BSD-style (see the file LICENSE)--- --- Maintainer  :  virukav@gmail.com--- Stability   :  experimental--- Portability :  non-portable------ The Sed runtime engine. The execution sequence includes the parseArgs (parse the Sed options/args),--- compile (parse) Sed commands, execute Sed commands.--module StreamEd where--import System.IO -import System.FilePath (splitFileName)-import Control.Monad (unless, when)-import qualified Control.Monad.State as S-import qualified Control.Exception as E-import qualified System.FilePath.Glob as G (compile, globDir1) -import Data.List (isPrefixOf)-import Data.Char (isPrint)-import Data.Maybe (fromMaybe)-import qualified Data.ByteString.Char8 as B-import Text.Printf (printf)-import Parsec (parseSed, sedCmds)-import Ast-import SedRegex-import SedState--data FileStatus = EOF | Cont -   deriving (Eq, Show)--data CmdSchedule = None | NextCmd | Break | Continue | Jump (Maybe B.ByteString) | Exit-   deriving (Eq, Show)---- | Run Sed program-run :: [String] -> IO ()-run args = -    S.execStateT (do -           (files, cmds) <- parseArgs args-           compile cmds-           execute files-         ) initEnv >> return ()---- | Parse Sed program's arguments-parseArgs :: [String] -> SedState ([FilePath], String)-parseArgs xs = parseArgs' xs ([],[]) where-    parseArgs' [] fs = return fs-    parseArgs' [x] fs -       | x == "-n" = set defOutput False >> return fs-       | otherwise = parseCmds x fs-    parseArgs' (x:y:ys) fs-       | x == "-e" = parseArgs' ys (addCmd fs y)-       | x == "-f" = do-            sed <- S.lift $ System.IO.readFile y `catch` openFileError y-            when ("#n" `isPrefixOf` sed) $ set defOutput False -            parseArgs' ys (addCmd fs sed)-       | x == "-n" = set defOutput False >> parseArgs' (y:ys) fs-       | otherwise = parseCmds x fs >>= \fs' -> parseArgs' (y:ys) fs'-       where-         addCmd (files, cmds) x' = (files, cmds ++ ('\n':x'))--openFileError :: String -> E.IOException -> IO [a]-openFileError f e = putStr ("Error: Couldn't open " ++ f ++ ": " ++ -                    show (e :: E.IOException)) >> return []---- | Parse Sed program's embedded commands and the input files arguments-parseCmds :: String -> ([FilePath], String) -> SedState ([FilePath], String)-parseCmds x (files, cmds) = -    if null cmds then return (files, x) -     else do -       let (dir, fp) = splitFileName x-       fs <- S.lift $ G.globDir1 (G.compile fp) dir-       if null fs then error $ fp ++ ": No such file or directory"-        else return (files ++ fs, cmds)---- | Parse the Sed commands-compile :: String -> SedState ()-compile cmds = do-  case parseSed sedCmds cmds of-     Right x -> set ast x-     Left e  -> error $ show e ++ " in " ++ cmds-  return ()---- | Execute the parsed Sed commands against input data-execute :: [FilePath] -> SedState ()-execute cont = -   if null cont then processStdin-    else do -     let (files,len) = (cont, length cont)-     let fs = zipWith (\x y -> (x, y == len)) files [1..len]-     processFiles fs-     fout <- get fileout-     S.lift $ S.mapM_ hClose (map snd fout)-     return ()---- | The standard input will be used if no file operands are specified-processStdin :: SedState ()-processStdin = do-    e <- get exit-    unless e $ do-      a <- get ast-      loop stdin True a---- | Process the input text files-processFiles :: [(FilePath, Bool)] -> SedState ()-processFiles fs = -    S.forM_ fs $ \(file, lastFile) -> do-        e <- get exit-        unless e $ do-           h <- S.lift $ openFile file ReadMode-           a <- get ast-           loop h lastFile a---- | Cyclically append a line of input without newline into the pattern space-loop :: Handle -> Bool -> [SedCmd] -> SedState ()-loop h b cs = do-      (res, str) <- line h b   -      case res of-        EOF -> S.lift $ hClose h >> return ()-        _   -> do-          set patternSpace str-          set appendSpace []-          sch <- eval h b cs-          case sch of -            Exit -> return ()-            _    -> loop h b cs---- | Read an input line and handle the EOF status-line :: Handle -> Bool -> SedState (FileStatus, B.ByteString)-line h b = do-   p <- S.lift $ hIsEOF h-   if p then return (EOF,B.empty)-     else do -       str <- S.lift $ B.hGetLine h-       modify curLine (+1)-       isLast <- if h == stdin then return False -                   else S.lift (hIsEOF h) >>= \eof -> return eof--       if isLast && b then                -           get curLine >>= \l -> set lastLine l >> return (Cont, str)-        else return (Cont, str)---- | Manage the flow control of the Sed commands-eval :: Handle -> Bool -> [SedCmd] -> SedState CmdSchedule-eval h b cs = do-    sch <- execCmds cs (line h b)-    case sch of-       Jump lbl -> get ast >>= \as -> eval h b (goto as lbl)-       Exit -> printPatSpace >> get appendSpace >>= \a -> -               mapM_ prnStr a >> set exit True >> return Exit-       _ -> get appendSpace >>= \a -> mapM_ prnStr a >> return sch---- | Transfer control to the command marked with the label-goto :: [SedCmd] -> Maybe Label -> [SedCmd]      -goto cmds = maybe [] (go cmds)-  where -        go [] _ = []-        go (SedCmd _ fun:cs) str = case fun of-           Group cs' -> go cs' str-           Label x -> if x == str then cs-                       else go cs str-           _ -> go cs str   ---- | Execute the specified Sed commands-execCmds :: [SedCmd] -> SedState (FileStatus,B.ByteString) -> SedState CmdSchedule-execCmds [] _ = printPatSpace >> return None-execCmds (x:xs) line' = do-    sch <- execCmd x line'-    if sch `elem` [NextCmd, None] then execCmds xs line'-     else if sch == Continue then do-       cs <- get ast-       execCmds cs line'-     else return sch---- | Execute the Sed command if the address interval of the command is matched-execCmd :: SedCmd -> SedState (FileStatus, B.ByteString) -> SedState CmdSchedule-execCmd (SedCmd a fun) line' = do-     b <- matchAddress a-     if b then runCmd fun line' >>= \sch -> return sch-      else return NextCmd---- | Check if the address interval is matched  -matchAddress :: Address -> SedState Bool-matchAddress (Address addr1 addr2 invert) = -    case (addr1,addr2) of-      (Just x, Nothing) -> matchAddr x x >>= \b -> return $ b /= invert-      (Just x, Just y)  -> matchAddr x y >>= \b -> return $ b /= invert-      _                 -> return $ not invert-    where-      matchAddr :: Addr -> Addr -> SedState Bool-      matchAddr a1 a2 = do -         lineNum <- get curLine-         patSpace <- get patternSpace-         lastLineNum <- get lastLine -         case (a1,a2) of-           (LineNumber x, LineNumber y) -> matchRange (x == lineNum) (y == lineNum)-           (LineNumber x, Pat y) -> matchRange (x == lineNum) (matchRE y patSpace)-           (LineNumber x, LastLine) -> matchRange (x == lineNum) (lineNum == lastLineNum)-           (LastLine, _) -> return $ lineNum == lastLineNum-           (Pat x, Pat y) -> matchRange (matchRE x patSpace) (matchRE y patSpace)-           (Pat x, LineNumber y) -> matchRange (matchRE x patSpace) (y == lineNum) -           (Pat x, LastLine) -> matchRange (matchRE x patSpace) (lineNum == lastLineNum)       -      matchRange :: Bool -> Bool -> SedState Bool-      matchRange b1 b2 = do-         let setRange = set inRange-         range <- get inRange           -         if not range then -            if b1 && b2 then return True-             else if b1 then setRange True >> return True-                   else return False-          else if b2 then setRange False >> return True-                else return True---- | Execute the Sed function-runCmd :: SedFun -> SedState (FileStatus,B.ByteString) -> SedState CmdSchedule-runCmd cmd line' = -    case cmd of-      Group cs -> group cs line'-      LineNum -> get curLine >>= (prnStrLn . B.pack . show) >> return NextCmd-      Append txt -> modify appendSpace (++ [txt]) >> return NextCmd-      Branch lbl -> return (Jump lbl)-      Change txt -> change txt-      Delete -> set patternSpace B.empty >> return Break-      DeletePat -> deletePat-      ReplacePat -> get holdSpace >>= \h -> set patternSpace h >> return NextCmd-      AppendPat -> get holdSpace >>= \h -> modify patternSpace (`B.append` B.cons '\n' h) -                   >> return NextCmd-      ReplaceHold -> get patternSpace >>= \p -> set holdSpace p >> return NextCmd-      AppendHold -> get patternSpace >>= \p -> modify holdSpace (`B.append` B.cons '\n' p) -                    >> return NextCmd-      Insert txt -> prnStrLn txt >> return NextCmd-      List -> list-      Next -> next line'-      AppendLinePat -> appendLinePat line'-      PrintPat -> get patternSpace >>= \p -> prnStrLn p >> return NextCmd-      WriteUpPat -> get patternSpace >>= (prnStrLn . B.takeWhile (/='\n')) >> return NextCmd-      Quit -> return Exit-      ReadFile file -> readF file-      Substitute pat repl fs -> substitute pat repl fs-      Test lbl -> get subst >>= \s -> if s then return $ Jump lbl else return NextCmd-      WriteFile file -> writeF file-      Exchange -> exchange-      Transform t1 t2 -> transform t1 t2-      Label _ -> return NextCmd-      Comment -> return NextCmd-      EmptyCmd -> return None---- | Execute 'group' Sed function -group :: [SedCmd] -> SedState (FileStatus, B.ByteString) -> SedState CmdSchedule  -group [] _ = return NextCmd-group (cmd:xs) line' = do-    sch <- execCmd cmd line'-    if sch `elem` [NextCmd, None] then-       group xs line'-     else return sch---- | Execute 'delete' from multiline pattern space Sed function-deletePat :: SedState CmdSchedule-deletePat = do-    p <- get patternSpace-    set patternSpace (B.drop 1 $ B.dropWhile (/='\n') p)-    return Continue---- | Execute 'substitute' Sed function-substitute :: B.ByteString -> B.ByteString -> Flags -> SedState CmdSchedule-substitute pat repl fs = do-  let (gn, p, w) = getFlags fs -  patSpace <- get patternSpace-  let (repl', b) = sedSubRegex pat patSpace repl gn-  set subst b-  if b then do-     set patternSpace repl'-     _ <- if p then get patternSpace >>= \ps -> prnStrLn ps >> return NextCmd -           else return NextCmd-     if (not.null) w then writeF w >> return NextCmd else return NextCmd-   else return NextCmd-   where-     getFlags :: Flags -> (Int, Bool, FilePath)-     getFlags (Flags o f) = (occurr, printPat, file) where-        (occurr, printPat) = case o of-           Nothing -> (1, False)-           Just (OccurrencePrint x y) -> (occ x, y)-           Just (PrintOccurrence x y) -> (occ y, x)-        occ x = case x of-           Nothing -> 1-           Just ReplaceAll -> 0-           Just (Replace n) -> n        -        file = fromMaybe "" f---- | Execute 'change' Sed function-change :: B.ByteString -> SedState CmdSchedule-change txt = do-    range <- get inRange-    if not range then do-       prnStrLn txt-       return Break-     else return Break---- | Execute 'next' Sed function-next :: SedState (FileStatus, B.ByteString) -> SedState CmdSchedule-next line' = do-    printPatSpace-    (res,str) <- line'-    set patternSpace str-    if res == EOF then return Break else return NextCmd---- | Execute 'list' Sed function-list :: SedState CmdSchedule-list = do-    patSpace <- get patternSpace-    S.forM_ (B.unpack patSpace) $ \ch ->-      if isPrint ch then prnChar ch-       else case lookup ch esc of-             Nothing -> do -                 prnChar '\\'-                 prnPrintf ch-             Just x -> prnStr (B.pack x)-    prnChar '\n'-    return NextCmd-    where esc = zip "\\\a\b\f\r\t\v"-                    ["\\\\","\\a", "\\b", "\\f", "\\r", "\\t", "\\v"]---- | Execute 'exchange' Sed function-exchange :: SedState CmdSchedule-exchange = do-    hold <- get holdSpace-    pat  <- get patternSpace-    set holdSpace pat-    set patternSpace hold-    return NextCmd---- | Execute 'appendLinePat' Sed function-appendLinePat :: SedState (FileStatus, B.ByteString) -> SedState CmdSchedule-appendLinePat line' = do-    (res,ln) <- line'-    if res == EOF then return Break-      else do-       let suffix = B.append (B.pack "\n") ln-       modify patternSpace (`B.append` suffix)-       return None---- | Execute 'transform' Sed function-transform :: B.ByteString -> B.ByteString -> SedState CmdSchedule-transform t1 t2 = do-    when (B.length t1 /= B.length t2) $ -      error "Transform strings are not the same length"-    patSpace <- get patternSpace-    let tr = B.map go patSpace-    set patternSpace tr -    return NextCmd-    where go ch = fromMaybe ch (lookup ch (B.zip t1 t2))---- | Write contents of the pattern space to the file-writeF :: FilePath -> SedState CmdSchedule-writeF file = do-    fout <- get fileout-    patSpace <- get patternSpace-    let printFileout h = S.lift $ B.hPutStrLn h patSpace-    case lookup file fout of-       Nothing -> do-          h <- S.lift $ openFile file WriteMode-          modify fileout (++ [(file,h)])-          printFileout h-       Just h -> printFileout h-    return NextCmd---- | Add contents of the input file to the append space-readF :: FilePath -> SedState CmdSchedule-readF file = do-    cont <- S.lift $ B.readFile file `catch` \_ -> return B.empty-    modify appendSpace (++ [cont])-    return NextCmd---- | Print the pattern space to the standard output-printPatSpace :: SedState ()-printPatSpace = do-   out <- get defOutput-   when out $ get patternSpace >>= \p -> prnStrLn p---- | Check if the current line in the pattern space is the last line-isLastLine :: SedState Bool-isLastLine = do-    l <- get lastLine-    cur <- get curLine-    return $ l == cur---- | Writes the string to the standard output or save the string in the memory buffer-prnStr :: B.ByteString -> SedState ()-prnStr str = do-   useMem <- get useMemSpace-   if useMem then modify memorySpace (`B.append` str) -     else S.lift $ B.putStr str---- | The same as prnStr, but adds a newline character-prnStrLn :: B.ByteString -> SedState ()-prnStrLn str = prnStr $ B.snoc str '\n'---- | The same as prnStr, but for char-prnChar :: Char -> SedState ()-prnChar c = prnStr $ B.singleton c---- | Print the character as three-digit octal number-prnPrintf :: Char -> SedState ()-prnPrintf c = do-    let str = printf "%03o" c :: String -    prnStr $ B.pack str
− TestSuite.hs
@@ -1,187 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--import System.Directory (getDirectoryContents)-import System.FilePath (replaceExtension, takeExtension, (</>))-import Control.Monad (forM_, when)-import qualified Control.Exception as E-import qualified Control.Monad.State as S-import System.Console.CmdArgs-import Data.List (isPrefixOf)-import qualified Data.ByteString.Char8 as B-import Parsec (parseSed, sedCmds)-import StreamEd-import SedState--data Config = Config {-  dir :: FilePath,-  inp :: String,-  ok :: String,-  file :: FilePath-} deriving (Show, Data, Typeable)--config :: Mode (CmdArgs Config)-config = cmdArgsMode $ Config-   {dir = def &= typFile &= help "Directory with the test cases"-   ,inp = def &= help "Input file extension"-   ,ok  = def &= help "Output file extension"-   ,file= def &= typFile &= help "Sed script to test"-   } &=-   program "TestSuite" &=-   summary "TestSuite 0.1" &=-   help "" &=-   details []--data Result = OK | Diff String | Error String -  deriving (Show, Eq, Typeable)--main :: IO ()-main = do-  cnf <- cmdArgsRun config-  let script = file cnf-  testParse-  if (not.null) script then-    testFile script (inp cnf) (ok cnf)-   else-    testDir (dir cnf) (inp cnf) (ok cnf) --passed :: String -> String-passed script = result script " is passed"--failed :: String -> String-failed script = result script " is failed"--result :: String -> String -> String-result script str = "Test " ++ script ++ str--parseFile :: FilePath -> String -> SedState ([FilePath],String)-parseFile script inpfile = do-    sed <- S.lift $ readFile script `catch` openFileError script-    when ("#n" `isPrefixOf` sed) $ set defOutput False-    set useMemSpace True-    return ([inpfile], sed)--testParse :: IO ()-testParse = do-   putStrLn "<-- Sed parser tests started"-   forM_ ptests $ \(str, etalon) -> -      case (parseSed sedCmds str) of-        Left x -> putStrLn  (failed str ++ " -> ") >> print x-        Right y -> if show y == etalon then putStrLn $ passed (show str)-                    else do -                     putStrLn $ failed str-                     print etalon-                     print "====>"-                     print y--   putStrLn "--> Sed parser tests ended\n"-   -testFile :: FilePath -> String -> String -> IO ()-testFile script inext okext = do-    let inpfile = replaceExtension script inext-    let okfile = replaceExtension script okext    -    env <- S.execStateT (do-                (files, cmds) <- parseFile script inpfile-                compile cmds-                execute files-           ) initEnv-    okf <- B.readFile okfile `catch` openFileErrorB okfile -    let res = memorySpace_ env-    if res == okf then -      putStrLn $ passed script-     else do-      putStrLn $ failed script  -      mapM_ print (B.lines okf)-      print "===>"-      mapM_ print (B.lines (memorySpace_ env))-    where-      openFileErrorB f e = putStr ("Error: Couldn't open " ++ f ++ -           ": " ++ show (e :: E.IOException)) >> return B.empty--testDir :: FilePath -> String -> String -> IO ()-testDir path inpext okext = do-    putStrLn "<-- Sed script tests started"-    names <- getDirectoryContents path-    forM_ names $ \n -> do-       let filePath = path </> n-       when (takeExtension filePath == ".sed") $-           testFile filePath inpext okext-    putStrLn "--> Sed script tests ended"--ptests :: [(String, String)]-ptests = [- ("/123/=",-  "[SedCmd (Address (Just (Pat \"123\")) Nothing False) LineNum]"),- ("/123/{\n=\n}",-  "[SedCmd (Address (Just (Pat \"123\")) Nothing False) (Group [SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) LineNum])]"),- ("a\\\n123\\\n456",-  "[SedCmd (Address Nothing Nothing False) (Append \"123\\n456\")]"),- ("a\\\n~",-  "[SedCmd (Address Nothing Nothing False) (Append \"~\")]"),- ("/123/,/456/b", -  "[SedCmd (Address (Just (Pat \"123\")) (Just (Pat \"456\")) False) (Branch Nothing)]"),- ("y/aaa/bbb/",-  "[SedCmd (Address Nothing Nothing False) (Transform \"aaa\" \"bbb\")]"),- ("s/aaa/bbb/g",-  "[SedCmd (Address Nothing Nothing False) (Substitute \"aaa\" \"bbb\" (Flags (Just (OccurrencePrint (Just ReplaceAll) False)) Nothing))]"),- ("s'aaa'b&bb'1pw 123",-  "[SedCmd (Address Nothing Nothing False) (Substitute \"aaa\" \"b\\\\0bb\" (Flags (Just (OccurrencePrint (Just (Replace 1)) True)) (Just \"123\")))]"),- ("s/aaa/aa\1bb/g",-  "[SedCmd (Address Nothing Nothing False) (Substitute \"aaa\" \"aa\\SOHbb\" (Flags (Just (OccurrencePrint (Just ReplaceAll) False)) Nothing))]"),- ("s/aaa/\3/",-  "[SedCmd (Address Nothing Nothing False) (Substitute \"aaa\" \"\\ETX\" (Flags (Just (OccurrencePrint Nothing False)) Nothing))]"),- ("r ReadFile.cmd",-  "[SedCmd (Address Nothing Nothing False) (ReadFile \"ReadFile.cmd\")]"),- ("w WriteFile.cmd",-  "[SedCmd (Address Nothing Nothing False) (WriteFile \"WriteFile.cmd\")]"),- ("{\n=\n=\n}",-  "[SedCmd (Address Nothing Nothing False) (Group [SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) LineNum,SedCmd (Address Nothing Nothing False) LineNum])]"),- ("=\n=\n",-  "[SedCmd (Address Nothing Nothing False) LineNum,SedCmd (Address Nothing Nothing False) LineNum]"),- (" #=",-  "[SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) Comment]"),- ("#n Print line before and after changes.\n{\n=\n}",-  "[SedCmd (Address Nothing Nothing False) Comment,SedCmd (Address Nothing Nothing False) (Group [SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) LineNum])]"),- ("r ReadFile.cmd\nr ReadFile.cmd",-  "[SedCmd (Address Nothing Nothing False) (ReadFile \"ReadFile.cmd\"),SedCmd (Address Nothing Nothing False) (ReadFile \"ReadFile.cmd\")]"),- ("/^\\.H1/n\n/^$/d",-  "[SedCmd (Address (Just (Pat \"^\\\\.H1\")) Nothing False) Next,SedCmd (Address (Just (Pat \"^$\")) Nothing False) Delete]"),- ("s/      /\\n/2",-  "[SedCmd (Address Nothing Nothing False) (Substitute \"      \" \"\\n\" (Flags (Just (OccurrencePrint (Just (Replace 2)) False)) Nothing))]"),- ("s/Owner and Operator\\nGuide/aaa/g",-  "[SedCmd (Address Nothing Nothing False) (Substitute \"Owner and Operator\\nGuide\" \"aaa\" (Flags (Just (OccurrencePrint (Just ReplaceAll) False)) Nothing))]"),- ("/Operator$/{\nN\ns/Owner and Operator\\nGuide/Installation Guide/\n}\n",-  "[SedCmd (Address (Just (Pat \"Operator$\")) Nothing False) (Group [SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) AppendLinePat,SedCmd (Address Nothing Nothing False) (Substitute \"Owner and Operator\\nGuide\" \"Installation Guide\" (Flags (Just (OccurrencePrint Nothing False)) Nothing))])]"),- ("s/Owner and Operator Guide/Installation Guide/\n/Owner/{\nN\ns/ *\n/ /\ns/Owner andOperator Guide */Installation Guide\\n/\n}",-  "[SedCmd (Address Nothing Nothing False) (Substitute \"Owner and Operator Guide\" \"Installation Guide\" (Flags (Just (OccurrencePrint Nothing False)) Nothing)),SedCmd (Address (Just (Pat \"Owner\")) Nothing False) (Group [SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) AppendLinePat,SedCmd (Address Nothing Nothing False) (Substitute \" *\\n\" \" \" (Flags (Just (OccurrencePrint Nothing False)) Nothing)),SedCmd (Address Nothing Nothing False) (Substitute \"Owner andOperator Guide *\" \"Installation Guide\\n\" (Flags (Just (OccurrencePrint Nothing False)) Nothing))])]"),- ("    /^\n$/D",-  "[SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address (Just (Pat \"^\\n$\")) Nothing False) DeletePat]"),- ("{\n{\n}\n}",-  "[SedCmd (Address Nothing Nothing False) (Group [SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) (Group [SedCmd (Address Nothing Nothing False) EmptyCmd])])]"),- ("{\n{\nN\n}\n}",-  "[SedCmd (Address Nothing Nothing False) (Group [SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) (Group [SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) AppendLinePat])])]"),- ("/UNIX/{\n  N\n  /\nSystem/{\n  s// Operating &/\n  P\n  D\n  }\n}",-  "[SedCmd (Address (Just (Pat \"UNIX\")) Nothing False) (Group [SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) AppendLinePat,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address (Just (Pat \"\\nSystem\")) Nothing False) (Group [SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) (Substitute \"\\nSystem\" \" Operating \\\\0\" (Flags (Just (OccurrencePrint Nothing False)) Nothing)),SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) WriteUpPat,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) DeletePat,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd])])]"),- ("=\n=\n  #\n",-  "[SedCmd (Address Nothing Nothing False) LineNum,SedCmd (Address Nothing Nothing False) LineNum,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) Comment]"),- ("  \n =\n \n =\n",-  "[SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) LineNum,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) LineNum]"),- ("  \n \n \n \n",-  "[SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd]"),- ("{N\nN\n{=\n  }\n}",-  "[SedCmd (Address Nothing Nothing False) (Group [SedCmd (Address Nothing Nothing False) AppendLinePat,SedCmd (Address Nothing Nothing False) AppendLinePat,SedCmd (Address Nothing Nothing False) (Group [SedCmd (Address Nothing Nothing False) LineNum,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd])])]"),- ("{\n }",-  "[SedCmd (Address Nothing Nothing False) (Group [SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd])]"),- ("{\nN\n}\n#/Owner/{}",-  "[SedCmd (Address Nothing Nothing False) (Group [SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) AppendLinePat]),SedCmd (Address Nothing Nothing False) Comment]"),- ("{\n}\nP",-  "[SedCmd (Address Nothing Nothing False) (Group [SedCmd (Address Nothing Nothing False) EmptyCmd]),SedCmd (Address Nothing Nothing False) WriteUpPat]"),- ("N\nP",-  "[SedCmd (Address Nothing Nothing False) AppendLinePat,SedCmd (Address Nothing Nothing False) WriteUpPat]"),- ("1!H;1!{ x; p; x \n};d",-  "[SedCmd (Address (Just (LineNumber 1)) Nothing True) AppendHold,SedCmd (Address (Just (LineNumber 1)) Nothing True) (Group [SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) Exchange,SedCmd (Address Nothing Nothing False) PrintPat,SedCmd (Address Nothing Nothing False) Exchange]),SedCmd (Address Nothing Nothing False) Delete]"),- ("/^0$/{\nc\\\nyes\n}",-  "[SedCmd (Address (Just (Pat \"^0$\")) Nothing False) (Group [SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) (Change \"yes\")])]"),- ("2,5 {\ns/[\t ]//g\n}",-  "[SedCmd (Address (Just (LineNumber 2)) (Just (LineNumber 5)) False) (Group [SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) (Substitute \"[\\t ]\" \"\" (Flags (Just (OccurrencePrint (Just ReplaceAll) False)) Nothing))])]")---}- ]
+ src/Hsed/Ast.hs view
@@ -0,0 +1,86 @@+-- |+-- Module      :  Ast+-- Copyright   :  (c) Vitaliy Rkavishnikov+-- License     :  BSD-style (see the file LICENSE)+-- +-- Maintainer  :  virukav@gmail.com+-- Stability   :  experimental+-- Portability :  non-portable+--+-- The main types used in the program++module Hsed.Ast where ++import Hsed.SedRegex (Pattern)+import Data.ByteString.Char8 (ByteString)++-- | Editing commands+data SedCmd = SedCmd Address SedFun  deriving Show++-- | Functions represents a single-character command verb+data SedFun = +              Group [SedCmd]         -- ^ { - group of the sed commands+            | LineNum                -- ^ = - write to standard output the current line number+            | Append Text            -- ^ a - append text following each line matched by address +            | Branch (Maybe Label)   -- ^ b - transfer control to Label+            | Change Text            -- ^ c - replace the lines selected by the address with Text+            | DeleteLine             -- ^ d - delete line(s) from pattern space+            | DeletePat              -- ^ D - delete (up to newline) of multiline pattern space+            | ReplacePat             -- ^ g - copy hold space into the pattern space+            | AppendPat              -- ^ G - add newline followed by hold space into the pattern space+            | ReplaceHold            -- ^ h - copy pattern space into hold space+            | AppendHold             -- ^ H - add newline followed by pattern space into the hold space+            | Insert Text            -- ^ i - insert Text before each line matched by address +            | List                   -- ^ l - list the pattern space, showing non-printing chars in ASCII+            | NextLine               -- ^ n - read next line of input into pattern space+            | AppendLinePat          -- ^ N - add next input line and newline into pattern space+            | PrintPat               -- ^ p - print the addressed line(s)+            | WriteUpPat             -- ^ P - print (up to newline) of multiline pattern space   +            | Quit                   -- ^ q - quit when address is encounterd+            | ReadFile FilePath      -- ^ r - add contents of file to the pattern space+            | Substitute  Pattern Replacement Flags  -- ^ s - substitute Replacement for Pattern+            | Test (Maybe Label)     -- ^ t - branch to line marked by :label if substitution was made +            | WriteFile FilePath     -- ^ w - write the line to file if a replacement was done+            | Exchange               -- ^ x - exchange pattern space with hold space+            | Transform Text Text    -- ^ y - transform each char by position in Text to Text+            | Label Label            -- ^ : - label a line in the scipt for transfering by b or t.+            | Comment                -- ^ # - ignore a line in the script except "#n" in the first line +            | EmptyCmd               -- ^   - ignore spaces+    deriving Show++-- | An address is either a decimal number that counts input lines cumulatively across files, +--   a '$' character that addresses the last line of input, or a context address as BRE +data Addr = LineNumber Int+          | LastLine+          | Pat Pattern+    deriving Show++-- | A permissable address is representing by zero, one or two addresses+data Address = Address (Maybe Addr) (Maybe Addr) Invert+    deriving Show++-- | Used in the replacement string. An appersand ('&') will be replaced by the+--   string matched the BRE. The characters "\n", where n is a digit will be+--   replaced by the corresponding back-reference expression.+data Occurrence = Replace Int | ReplaceAll +    deriving Show++-- | The flag to control the pattern space output in the substitute function+type OutputPat = Bool++-- | The allowed sequence of the Occurrence and OutputPat flags in the substitute+--   function+data OccurrencePrint = OccurrencePrint (Maybe Occurrence) OutputPat |+                       PrintOccurrence OutputPat (Maybe Occurrence)+    deriving Show++-- | Flags used in the substitute command+data Flags = Flags (Maybe OccurrencePrint) (Maybe FilePath) +    deriving Show++type Replacement = ByteString+type Invert = Bool+type Text = ByteString+type Label = ByteString++
+ src/Hsed/Parsec.hs view
@@ -0,0 +1,235 @@+-- |+-- Module      :  Parsec+-- Copyright   :  (c) Vitaliy Rkavishnikov+-- License     :  BSD-style (see the file LICENSE)+-- +-- Maintainer  :  virukav@gmail.com+-- Stability   :  experimental+-- Portability :  non-portable+--+-- Sed commands parser. See "The Open Group Base Specifications Issue 7" for+-- parsing requirements. The current version of the Haskell Sed doesn't supports+-- the back-references in the RE.++module Hsed.Parsec where++import Prelude hiding (readFile, writeFile)+import Text.ParserCombinators.Parsec hiding (label)+import qualified Data.ByteString.Char8 as B+import Hsed.Ast+import Hsed.SedRegex++-- | If an RE is empty last RE used in the last command applied +data ParserState = ParserState {+    lastRE :: Pattern+} ++emptyState = ParserState { +    lastRE = B.pack ""   +}++type SedParser = GenParser Char ParserState+type Stream = String++eol = oneOf "\n\r" >> return ()+eoleof = choice [eol, eof]+slash = char '/'+comma = char ','+semi = char ';'+backslash = char '\\'+number = many1 digit >>= \n -> return $ read n+invert = (spaces >> char '!' >> return True) <|> return False++parseSed :: SedParser a -> Stream -> Either ParseError a+parseSed p = runParser p emptyState ""++parseRE :: String -> SedParser Pattern+parseRE pat = do+    let patB = B.pack pat+    updateState (\(ParserState _)  -> ParserState patB)+    return patB++pattern open close val = do+    pat <- between open close val+    if null pat then do+        s <- getState+        return $ lastRE s+     else parseRE (unesc pat)++addr = +    fmap (Just . LineNumber) number <|>+    (char '$' >> return (Just LastLine)) <|>+    (pattern slash slash val >>= \pat -> return $ Just (Pat pat))+    where val = many (noneOf "/")++addr1 = do+    a1 <- addr+    spaces+    b <- invert+    return $ Address a1 Nothing b++addr2 = do+    a1 <- addr+    comma <?> ","+    a2 <- addr <?> "bad address" +    spaces+    b <- invert+    return $ Address a1 a2 b++address :: SedParser Address+address = +    try addr2 <|> try addr1 <|> +    (invert >>= \b -> return $ Address Nothing Nothing b) ++sedCmds :: SedParser [SedCmd]+sedCmds = +    many1 $ try (space >> return emptyCmd) <|>+            (do { x <- sedCmd; endCmd; return x })+    where+    endCmd = choice [eol, eof, semiend, comm, spaces >> return ()]+       where+       semiend = try (spaces >> semi >> spaces >> return ())+       comm = lookAhead (char '#') >> return ()++sedCmd :: SedParser SedCmd+sedCmd = do+    a <- address+    fun <- sedFun+    return $ SedCmd a fun++sedFun :: SedParser SedFun+sedFun = choice functions >>= \f -> return f++functions = +    [substitute, group, append, change, insert, lineNum, delete, deletePat, replacePat, +     appendPat, replaceHold, appendHold, list, next, appendLinePat, printPat,+     writeUpPat, quit, exchange, comment, branch, test, readFile, writeFile,+     label, transform+    ]++append        = textFun 'a' Append+change        = textFun 'c' Change +insert        = textFun 'i' Insert++readFile      = fileFun 'r' ReadFile+writeFile     = fileFun 'w' WriteFile+label         = argFun ':' Label++lineNum       = bareFun '=' LineNum+delete        = bareFun 'd' DeleteLine+deletePat     = bareFun 'D' DeletePat+replacePat    = bareFun 'g' ReplacePat+appendPat     = bareFun 'G' AppendPat+replaceHold   = bareFun 'h' ReplaceHold+appendHold    = bareFun 'H' AppendHold+list          = bareFun 'l' List+next          = bareFun 'n' NextLine+appendLinePat = bareFun 'N' AppendLinePat+printPat      = bareFun 'p' PrintPat+writeUpPat    = bareFun 'P' WriteUpPat+quit          = bareFun 'q' Quit+exchange      = bareFun 'x' Exchange++branch        = gotoFun 'b' Branch+test          = gotoFun 't' Test++bareFun :: Char -> SedFun -> SedParser SedFun+bareFun c f = char c >> return f++textFun :: Char -> (Text -> SedFun) -> SedParser SedFun+textFun c f = do+    char c+    backslash <?> "backslash"+    eol <?> "end of line"+    parts <- lines+    return $ f (B.pack (init $ unlines parts))+    where+       lines = do {x <- line; try eoleof; return x}+       line = sepBy part (backslash >> eol)+       part = many (noneOf "\\\n")++fileFun :: Char -> (FilePath -> SedFun) -> SedParser SedFun+fileFun c f = +    char c >> spaces >> +    manyTill anyChar (lookAhead eoleof) >>= \l -> return $ f l++argFun :: Char -> (B.ByteString -> SedFun) -> SedParser SedFun+argFun c f = +    char c >> spaces >> +    manyTill anyChar (lookAhead eoleof) >>= \l -> return $ f (B.pack l)++gotoFun :: Char -> (Maybe Label -> SedFun) -> SedParser SedFun+gotoFun c f = do+    char c+    many $ choice[char ' ', char '\t']+    label <- manyTill anyChar (lookAhead eoleof)+    if null label then return $ f Nothing+      else return $ f (Just $ B.pack label)++group = do+    char '{'+    cmds <- sedCmds+    spaces  +    char '}' <?> "}"+    return $ Group cmds++comment = do+    char '#'+    manyTill anyChar (lookAhead eoleof)+    return Comment++transform = do+    char 'y'+    slash <?> "/"+    str1 <- manyTill anyChar slash+    str2 <- manyTill anyChar slash+    return $ Transform (B.pack str1) (B.pack str2)++substitute = do+    char 's'+    delim <- lookAhead anyChar+    let val = many $ noneOf [delim]+    pat <- pattern (char delim) (char delim) val+    repl <- rhs delim+    fs <- flags +    return $ Substitute (B.pack $ unesc (B.unpack pat)) (B.pack $ esc repl) fs+    where+      esc [] = []+      esc [x] | x == '&' = "\\0"+              | otherwise = [x]+      esc (x:y:ys) | [x,y] == "\\n" = '\n':esc ys+                   | [x,y] == "\\\n" = esc (y:ys)+                   | [x,y] == "\\&" = '&':esc ys+                   | x     == '&' = "\\0" ++ esc (y:ys)+                   | otherwise = x:esc(y:ys)           ++      rhs delim = manyTill anyChar (char delim)++flags = do+    op <- occur+    out <- outFile+    return $ Flags op out+    where+      occur = occurPrint <|> return Nothing+      outFile = +          (char 'w' >> spaces >> +          manyTill anyChar (lookAhead eoleof) >>= \f -> return $ Just f) <|>+          return Nothing+      occurPrint = +          occurrence >>= \o -> prn >>= \p ->+          return $ Just $ OccurrencePrint o p+      occurrence = +          (char 'g' >> return (Just ReplaceAll)) <|>+          (number >>= \n -> return $ Just $ Replace n) <|>+          return Nothing+      prn = +          (char 'p' >> return True) <|>+          return False++unesc [] = []+unesc [x] = [x]+unesc (x:y:xs) | x:[y] == "\\t" = '\t':unesc xs+               | x:[y] == "\\n" = '\n':unesc xs+               | otherwise = x : unesc (y:xs)++emptyCmd = SedCmd (Address Nothing Nothing False) EmptyCmd
+ src/Hsed/Sed.hs view
@@ -0,0 +1,45 @@+-- |+-- Module      :  Sed+-- Copyright   :  (c) Vitaliy Rkavishnikov+-- License     :  BSD-style (see the file LICENSE)+-- +-- Maintainer  :  virukav@gmail.com+-- Stability   :  experimental+-- Portability :  non-portable+--+-- This module provides functions to execute the sed script+-- ++module Hsed.Sed + ( execScript+ , execScript_+ , SedScript+ ) where++import Control.Monad (when)+import qualified Control.Monad.State as S+import Data.List (isPrefixOf)+import qualified Data.ByteString.Char8 as B+import Hsed.StreamEd (runSed)+import Hsed.SedState++type SedScript = String++-- | Execute the sed script and print the output to ByteString+execScript :: [FilePath] -> SedScript -> IO B.ByteString+execScript files script = do+   env <- runSed files script (initEnv {useMemSpace_ = True})+   return $ memorySpace_ env++-- | Execute the sed script and print the result to stdout +execScript_ :: [FilePath] -> SedScript -> IO ()+execScript_ files script = do+   runSed files script initEnv+   return ()+++++++
+ src/Hsed/SedRegex.hs view
@@ -0,0 +1,74 @@+-- |+-- Module      :  SedRegex++-- Maintainer  :  virukav@gmail.com+-- Stability   :  experimental+-- Portability :  non-portable-- |++-- The Sed regular expression implementation based on the regex-posix package.   ++module Hsed.SedRegex where++import Data.Array ((!))+import Text.Regex.Posix+--import Text.Regex.TDFA+import Text.Regex+import qualified Data.ByteString.Char8 as B++type Pattern = B.ByteString++-- | Replaces every occurance of the given regexp with the replacement string. +--   Modification of the subRegex function from regex-posix package.+sedSubRegex :: B.ByteString         -- ^ Search pattern+            -> B.ByteString         -- ^ Input string+            -> B.ByteString         -- ^ Replacement text+            -> Int                  -- ^ Occurrence+            -> (B.ByteString, Bool) -- ^ (Output string, Replacement occurs)+--sedSubRegex _ "" _ _ = ("", True)+sedSubRegex pat inp repl n =+  let regexp = makeRegexOpts compExtended defaultExecOpt pat+  --let regexp = mkRegex pat -- for TDFA+      compile _i str [] = \ _m -> B.append str+      compile i str ((xstr,(off,len)):rest) =+        let i' = off+len+            pre = B.take (off-i) str+            str' = B.drop (i'-i) str+            slashEsc = B.pack "\\"+            app m = if xstr == slashEsc then B.append slashEsc+                     else let x = read (B.unpack xstr) :: Int in +                            B.append (fst (m!x))+        in if B.null str' then \ m -> B.append pre . app m+             else \ m -> B.append pre . app m . compile i' str' rest m++      compiled :: MatchText B.ByteString -> B.ByteString -> B.ByteString+      compiled = compile 0 repl findrefs where+        -- bre matches a backslash then capture either a backslash or some digits+        bre = mkRegex "\\\\(\\\\|[0-9]+)"+        findrefs = map (\m -> (fst (m!1),snd (m!0))) (matchAllText bre repl)+      go _i str [] = str+      go i str (m:ms) =+        let (_,(off,len)) = m!0+            i' = off+len+            pre = B.take (off-i) str+            str' = B.drop (i'-i) str+        in if B.null str' then B.concat [pre, compiled m B.empty]+             else B.concat [pre, compiled m (go i' str' ms)]+   +      occur regexp inp repl n pre =+        if inp == B.empty then (B.empty, False)+         else+          case matchOnceText regexp inp of+           Nothing -> (inp, False)+           Just (p, m, r) -> +              if n > 1 then +                 let acc = B.concat [pre, p, fst (m!0)] in+                     occur regexp r repl (n-1) acc+               else+                  (B.concat [pre, go 0 inp [m]], True)+  in if n == 0 then +       let ms = matchAllText regexp inp in+         (go 0 inp ms, (not . null) ms)+      else occur regexp inp repl n B.empty++-- | Match the regular expression against text+matchRE pat str = str =~ pat :: Bool
+ src/Hsed/SedState.hs view
@@ -0,0 +1,69 @@+{-# LANGUAGE TemplateHaskell #-}++-- |+-- Module      :  SedState+-- Copyright   :  (c) Vitaliy Rkavishnikov+-- License     :  BSD-style (see the file LICENSE)+-- +-- Maintainer  :  virukav@gmail.com+-- Stability   :  experimental+-- Portability :  non-portable+--+-- The state of the program ++module Hsed.SedState where++import qualified Control.Monad.State as S+import qualified Data.Accessor.Basic as A+import qualified Data.ByteString.Char8 as B+import Data.Accessor.Template (deriveAccessors)+import System.IO+import Hsed.Ast++data Env = Env {+  ast_ :: [SedCmd],                 -- ^ Parsed Sed commands+  defOutput_ :: !Bool,              -- ^ Suppress the default output+  lastLine_ :: !Int,                -- ^ The last line index+  curLine_ :: !Int,                 -- ^ The current line index+  inRange_ :: !Bool,                -- ^ Is pattern space matches the address range+  patternSpace_ :: !B.ByteString,   -- ^ The buffer to keep the selected line(s)+  holdSpace_ :: !B.ByteString,      -- ^ The buffer to keep the line(s) temporarily+  appendSpace_ :: [B.ByteString],   -- ^ The buffer to keep the append lines+  memorySpace_ :: !B.ByteString,    -- ^ Store the output in the memory+  useMemSpace_ :: !Bool,            -- ^ If True the Sed output is stored in the memory buffer+  exit_ :: !Bool,                   -- ^ Exit the stream editor+  fileout_ :: [(FilePath, Handle)], -- ^ Write (w command) files handles +  subst_ :: !Bool,                  -- ^ The result of the last substitution+  curFile_ :: (Handle, Bool)        -- ^ Current input file handle  +} deriving (Show)++$( deriveAccessors ''Env )++type SedState = S.StateT Env IO++initEnv :: Env+initEnv = Env {+  ast_ = [],+  defOutput_ = True,+  lastLine_ = 0,+  curLine_ = 0,+  inRange_ = False,+  patternSpace_ = B.empty,+  holdSpace_ = B.empty,+  appendSpace_ = [],+  memorySpace_ = B.empty,+  useMemSpace_ = False,+  exit_  = False,+  fileout_ = [],+  subst_ = False,+  curFile_ = (stdin, True)+}++set :: A.T Env a -> a -> SedState ()+set f x = S.modify (A.set f x)++get :: A.T Env a -> SedState a+get f = S.gets (A.get f)++modify :: A.T Env a -> (a -> a) -> SedState ()+modify f g = S.modify (A.modify f g)
+ src/Hsed/StreamEd.hs view
@@ -0,0 +1,473 @@+{-# LANGUAGE DeriveDataTypeable #-}++-- |+-- Module      :  StreamEd+-- Copyright   :  (c) Vitaliy Rkavishnikov+-- License     :  BSD-style (see the file LICENSE)+-- +-- Maintainer  :  virukav@gmail.com+-- Stability   :  experimental+-- Portability :  non-portable+--+-- The Sed runtime engine. The execution sequence includes the parseArgs (parse the Sed options/args),+-- compile (parse) Sed commands, execute Sed commands.++module Hsed.StreamEd where++import System.IO +import Control.Monad (unless, when)+import qualified Control.Monad.State as S+import Data.List (isPrefixOf)+import Data.Char (isPrint)+import Data.Maybe (fromMaybe)+import qualified Data.ByteString.Char8 as B+import Text.Printf (printf)+import Hsed.Parsec (parseSed, sedCmds)+import Hsed.Ast+import Hsed.SedRegex+import Hsed.SedState++data Status = EOF | Cont +   deriving (Eq, Show)++data FlowControl = +    Next                      -- ^ Apply the next sed command from the script to the pattern space+  | Break                     -- ^ Read the new line to the pattern space and apply sed script  +  | Continue                  -- ^ Reapply the sed script to the current pattern space+  | Goto (Maybe B.ByteString) -- ^ Jump to the marked sed command and apply it to the pattern space   +  | Exit                      -- ^ Quit +   deriving (Eq, Show)++-- | Compile and execute the sed script+runSed :: [FilePath] -> String -> Env -> IO Env+runSed fs sed env = +   S.execStateT (do+     when ("#n" `isPrefixOf` sed) $ +       set defOutput False +     compile sed +     execute fs+   ) env++-- | Parse the Sed commands+compile :: String -> SedState ()+compile cmds = do+  case parseSed sedCmds cmds of+     Right x -> set ast x+     Left e  -> error $ show e ++ " in " ++ cmds+  return ()++-- | Execute the parsed Sed commands against input data+execute :: [FilePath] -> SedState ()+execute fs = do+   if null fs then processStdin+     else processFiles fs+   fout <- get fileout+   S.lift $ S.mapM_ hClose (map snd fout)++-- | The standard input will be used if no file operands are specified+processStdin :: SedState ()+processStdin = do+    e <- get exit+    unless e $ do+      set curFile (stdin, True)+      a <- get ast+      loop a++-- | Process the input text files+processFiles :: [FilePath] -> SedState ()+processFiles files = do+    let len = length files+    let fs = zipWith (\x y -> (x, y == len)) files [1..len]+    S.forM_ fs $ \(file, lastFile) -> do+        e <- get exit+        unless e $ do+           h <- S.lift $ openFile file ReadMode+           set curFile (h, lastFile)+           a <- get ast+           loop a++-- | Cyclically append a line of input without newline into the pattern space+loop :: [SedCmd] -> SedState ()+loop cs = do+      (res, str) <- line +      case res of+        EOF -> get curFile >>= \(h,_) -> S.lift $ hClose h >> return ()+        _   -> do+          set patternSpace str+          set appendSpace []+          sch <- eval cs+          case sch of +            Exit -> return ()+            _    -> loop cs++-- | Read an input line+line :: SedState (Status, B.ByteString)+line = do+   (h,b) <- get curFile+   p <- S.lift $ hIsEOF h+   if p then return (EOF,B.empty)+     else do +       str <- S.lift $ B.hGetLine h+       modify curLine (+1)+       isLast <- if h == stdin then return False +                   else S.lift (hIsEOF h) >>= \eof -> return eof++       if isLast && b then                +           get curLine >>= \l -> set lastLine l >> return (Cont, str)+        else return (Cont, str)++-- | Manage the flow control of the Sed commands+eval :: [SedCmd] -> SedState FlowControl+eval cs = do+    sch <- execCmds cs+    case sch of+       Goto lbl -> get ast >>= \as -> eval (goto as lbl)+       Exit -> printPatSpace >> get appendSpace >>= \a -> +               mapM_ prnStr a >> set exit True >> return Exit+       _ -> get appendSpace >>= \a -> mapM_ prnStr a >> return sch++-- | Transfer control to the command marked with the label+goto :: [SedCmd] -> Maybe Label -> [SedCmd]      +goto cmds = maybe [] (go cmds)+  where +        go [] _ = []+        go (SedCmd _ fun:cs) str = case fun of+           Group cs' -> go cs' str+           Label x -> if x == str then cs+                       else go cs str+           _ -> go cs str   ++-- | Execute Sed commands+execCmds :: [SedCmd] -> SedState FlowControl+execCmds [] = printPatSpace >> return Next+execCmds (x:xs) = do+    sch <- execCmd x+    if sch `elem` [Next] then execCmds xs+     else if sch == Continue then do+       cs <- get ast+       execCmds cs+     else return sch++-- | Execute the Sed function if the address is matched+execCmd :: SedCmd -> SedState FlowControl+execCmd (SedCmd a fun) = do+     b <- matchAddress a+     if b then runCmd fun+      else return Next+++-- | Check if the address interval is matched  +matchAddress :: Address -> SedState Bool+matchAddress (Address addr1 addr2 invert) = +    case (addr1,addr2) of+      (Just x, Nothing) -> matchAddr x x >>= \b -> return $ b /= invert+      (Just x, Just y)  -> matchAddr x y >>= \b -> return $ b /= invert+      _                 -> return $ not invert+    where+      matchAddr :: Addr -> Addr -> SedState Bool+      matchAddr a1 a2 = do +         lineNum <- get curLine+         patSpace <- get patternSpace+         lastLineNum <- get lastLine +         case (a1,a2) of+           (LineNumber x, LineNumber y) -> matchRange (x == lineNum) (y == lineNum)+           (LineNumber x, Pat y) -> matchRange (x == lineNum) (matchRE y patSpace)+           (LineNumber x, LastLine) -> matchRange (x == lineNum) (lineNum == lastLineNum)+           (LastLine, _) -> return $ lineNum == lastLineNum+           (Pat x, Pat y) -> matchRange (matchRE x patSpace) (matchRE y patSpace)+           (Pat x, LineNumber y) -> matchRange (matchRE x patSpace) (y == lineNum) +           (Pat x, LastLine) -> matchRange (matchRE x patSpace) (lineNum == lastLineNum)       +      matchRange :: Bool -> Bool -> SedState Bool+      matchRange b1 b2 = do+         let setRange = set inRange+         range <- get inRange           +         if not range then +            if b1 && b2 then return True+             else if b1 then setRange True >> return True+                   else return False+          else if b2 then setRange False >> return True+                else return True++-- | Execute the Sed function+runCmd :: SedFun -> SedState FlowControl+runCmd cmd = +    case cmd of+      Group cs -> group cs+      LineNum -> lineNum+      Append txt -> append txt+      Branch lbl -> branch lbl+      Change txt -> change txt+      DeleteLine -> deleteLine+      DeletePat -> deletePat+      ReplacePat -> replacePat+      AppendPat -> appendPat+      ReplaceHold -> replaceHold+      AppendHold -> appendHold+      Insert txt -> insert txt+      List -> list+      NextLine -> next+      AppendLinePat -> appendLinePat+      PrintPat -> printPat+      WriteUpPat -> writeUpPat+      Quit -> quit+      ReadFile file -> readF file+      Substitute pat repl fs -> substitute pat repl fs+      Test lbl -> test lbl+      WriteFile file -> writeF file+      Exchange -> exchange+      Transform t1 t2 -> transform t1 t2+      Label lbl -> label lbl+      Comment -> comment+      EmptyCmd -> emptyCmd++-- | '{cmd...}' Groups subcommands enclosed in {} (braces)+group :: [SedCmd]  -> SedState FlowControl +group [] = return Next+group (cmd:xs) = do+    sch <- execCmd cmd+    if sch == Next then+       group xs+     else return sch++-- | '=' Writes the current line number to standard output as a line+lineNum :: SedState FlowControl+lineNum = +    get curLine >>= +    (prnStrLn . B.pack . show) >> +    return Next++-- | 'a\\ntext' Places the text variable in output before reading +-- the next input line+append :: B.ByteString -> SedState FlowControl+append txt = +    modify appendSpace (++ [txt]) >> +    return Next ++-- | 'b label' Transfer control to :label elsewhere in script+branch :: Maybe Label -> SedState FlowControl+branch = return . Goto++-- | 'c\\ntext' Replace the lines with the text variable+change :: B.ByteString -> SedState FlowControl+change txt = do+    range <- get inRange+    unless range $ prnStrLn txt+    return Break++-- | 'd' Delete line(s) from pattern space+deleteLine :: SedState FlowControl+deleteLine = +    set patternSpace B.empty >> +    return Break++-- | 'D' Delete first part (up to embedded newline) of multiline pattern space+deletePat :: SedState FlowControl+deletePat = do+    p <- get patternSpace+    let p' = B.drop 1 $ B.dropWhile (/='\n') p+    set patternSpace p'+    return Continue++-- | 'g' Copy contents of hold space into the pattern space+replacePat :: SedState FlowControl+replacePat = +    get holdSpace >>= \h -> +    set patternSpace h >> +    return Next++-- | 'G' Append newline followed by contents of hold space +-- to contents of the pattern space.+appendPat :: SedState FlowControl+appendPat = +    get holdSpace >>= \h -> +    modify patternSpace (`B.append` B.cons '\n' h) >> +    return Next++-- | 'h' Copy pattern space into hold space+replaceHold :: SedState FlowControl+replaceHold = +    get patternSpace >>= \p -> +    set holdSpace p >> +    return Next++-- | 'H' Append newline and contents of pattern space to contents +-- of the hold space+appendHold :: SedState FlowControl+appendHold = +    get patternSpace >>= \p -> +    modify holdSpace (`B.append` B.cons '\n' p) >> +    return Next++-- | 'i\\ntext' Writes the text variable to standard output before +-- reading the next line into the pattern space.+insert :: B.ByteString -> SedState FlowControl+insert txt = prnStrLn txt >> return Next++-- | 't label' Jump to line if successful substitutions have been made+test :: Maybe Label -> SedState FlowControl+test lbl = +    get subst >>= \s -> +    if s then return $ Goto lbl +     else return Next++-- | 's/pattern/replacement/[flags]' Substitute replacement for pattern+substitute :: B.ByteString -> B.ByteString -> Flags -> SedState FlowControl+substitute pat repl fs = do+  let (gn, p, w) = getFlags fs +  patSpace <- get patternSpace+  let (repl', b) = sedSubRegex pat patSpace repl gn+  set subst b+  when b $ do+    set patternSpace repl'+    when p $ get patternSpace >>= \ps -> prnStrLn ps+    unless (null w) $ writeF w >> return ()+  return Next+   where+     getFlags :: Flags -> (Int, Bool, FilePath)+     getFlags (Flags o f) = (occurr, printPat, file) where+        (occurr, printPat) = case o of+           Nothing -> (1, False)+           Just (OccurrencePrint x y) -> (occ x, y)+           Just (PrintOccurrence x y) -> (occ y, x)+        occ x = case x of+           Nothing -> 1+           Just ReplaceAll -> 0+           Just (Replace n) -> n        +        file = fromMaybe "" f++-- | 'n' Read next line of input into pattern space. +next :: SedState FlowControl+next = do+    printPatSpace+    (res,str) <- line+    set patternSpace str+    if res == EOF then return Break else return Next++-- | 'l' List the contents of the pattern space, showing +-- nonprinting characters as ASCII codes+list :: SedState FlowControl+list = do+    patSpace <- get patternSpace+    S.forM_ (B.unpack patSpace) $ \ch ->+      if isPrint ch then prnChar ch+       else case lookup ch esc of+             Nothing -> do +                 prnChar '\\'+                 prnPrintf ch+             Just x -> prnStr (B.pack x)+    prnChar '\n'+    return Next+    where esc = zip "\\\a\b\f\r\t\v"+                    ["\\\\","\\a", "\\b", "\\f", "\\r", "\\t", "\\v"]++-- | 'x' Exchange contents of the pattern space with the +-- contents of the hold space+exchange :: SedState FlowControl+exchange = do+    hold <- get holdSpace+    pat  <- get patternSpace+    set holdSpace pat+    set patternSpace hold+    return Next++-- | 'N' Append next input line to contents of pattern space+appendLinePat :: SedState FlowControl+appendLinePat = do+    (res,ln) <- line+    if res == EOF then return Break+      else do+       let suffix = B.append (B.pack "\n") ln+       modify patternSpace (`B.append` suffix)+       return Next++-- | 'p' Print the lines+printPat :: SedState FlowControl+printPat = +     get patternSpace >>= \p -> +     prnStrLn p >> +     return Next++-- | 'P' Print first part (up to embedded newline) of +-- multiline pattern space+writeUpPat :: SedState FlowControl+writeUpPat = +     get patternSpace >>= +     (prnStrLn . B.takeWhile (/='\n')) >> +     return Next++-- | 'q' Quit+quit :: SedState FlowControl+quit = return Exit++-- | 'y/abc/xyz' Transform each character by position in string abc +-- to its equivalent in string xyz+transform :: B.ByteString -> B.ByteString -> SedState FlowControl+transform t1 t2 = do+    when (B.length t1 /= B.length t2) $ +      error "Transform strings are not the same length"+    patSpace <- get patternSpace+    let tr = B.map go patSpace+    set patternSpace tr +    return Next+    where go ch = fromMaybe ch (lookup ch (B.zip t1 t2))++-- | 'w file' Append contents of pattern space to file+writeF :: FilePath -> SedState FlowControl+writeF file = do+    fout <- get fileout+    patSpace <- get patternSpace+    let printFileout h = S.lift $ B.hPutStrLn h patSpace+    case lookup file fout of+       Nothing -> do+          h <- S.lift $ openFile file WriteMode+          modify fileout (++ [(file,h)])+          printFileout h+       Just h -> printFileout h+    return Next++-- | 'r' Read contents of file and append after the contents of the +-- pattern space+readF :: FilePath -> SedState FlowControl+readF file = do+    cont <- S.lift $ B.readFile file `catch` \_ -> return B.empty+    modify appendSpace (++ [cont])+    return Next++-- | Skip label, comment and empty command+label _ = return Next+comment  = return Next+emptyCmd = return Next++-- | Print the pattern space to the standard output+printPatSpace :: SedState ()+printPatSpace = do+   out <- get defOutput+   when out $ get patternSpace >>= \p -> prnStrLn p++-- | Check if the current line in the pattern space is the last line+isLastLine :: SedState Bool+isLastLine = do+    l <- get lastLine+    cur <- get curLine+    return $ l == cur++-- | Writes the string to the standard output or save the string in the memory buffer+prnStr :: B.ByteString -> SedState ()+prnStr str = do+   useMem <- get useMemSpace+   if useMem then modify memorySpace (`B.append` str) +     else S.lift $ B.putStr str++-- | The same as prnStr, but adds a newline character+prnStrLn :: B.ByteString -> SedState ()+prnStrLn str = prnStr $ B.snoc str '\n'++-- | The same as prnStr, but for char+prnChar :: Char -> SedState ()+prnChar c = prnStr $ B.singleton c++-- | Print the character as three-digit octal number+prnPrintf :: Char -> SedState ()+prnPrintf c = do+    let str = printf "%03o" c :: String +    prnStr $ B.pack str
+ src/Main.hs view
@@ -0,0 +1,82 @@+-- |+-- Module      :  Main+-- Copyright   :  (c) Vitaliy Rukavishnikov+-- License     :  BSD-style (see the file LICENSE)+-- +-- Maintainer  :  virukav@gmail.com+-- Stability   :  experimental+-- Portability :  non-portable+--+-- See "The Open Group Base Specifications Issue 7" for the requirements+-- This version of the Haskell Sed uses regex-posix package to parse all regular +-- expressions. At the moment it doesn't supports the back-references in the RE.++module Main where++import System.IO+import System (getArgs)+import System.FilePath (splitFileName)+import Control.Monad (unless, when)+import qualified Control.Exception as E+import qualified System.FilePath.Glob as G (compile, globDir1) ++import Hsed.StreamEd (runSed)+import Hsed.SedState ++data SedArgs = SedArgs {+  files :: [FilePath],+  script :: String,+  defOut :: Maybe Bool+} deriving Show+++main :: IO ()+main = do+    args <- getArgs+    if null args || head args == "--help" then do+      putStrLn usage+      return ()+     else do+       SedArgs fs sed out <- parseArgs args+       let setDef b = initEnv {defOutput_ = b}+       runSed fs sed (maybe initEnv setDef out)+       return ()++-- | Parse Sed program's arguments+parseArgs :: [String] -> IO SedArgs+parseArgs xs = parseArgs' xs (SedArgs [] "" Nothing) where+    parseArgs' [] sargs = return sargs+    parseArgs' [x] sargs+       | x == "-n" = return $ sargs {defOut = Just False}+       | otherwise = parseCmds x sargs+    parseArgs' (x:y:ys) sargs+       | x == "-e" = parseArgs' ys (addCmd sargs y)+       | x == "-f" = do+            sed <- System.IO.readFile y `catch` openFileError y+            parseArgs' ys (addCmd sargs sed)+       | x == "-n" =  parseArgs' (y:ys) (sargs {defOut = Just False})+       | otherwise = parseCmds x sargs >>= \sargs' -> parseArgs' (y:ys) sargs'+       where+         addCmd s@sargs x' = s {script = script s ++ ('\n':x')}++openFileError :: String -> E.IOException -> IO [a]+openFileError f e = putStr ("Error: Couldn't open " ++ f ++ ": " ++ +                    show (e :: E.IOException)) >> return []++-- | Parse Sed program's embedded commands and the input files arguments+parseCmds :: String -> SedArgs -> IO SedArgs+parseCmds x s@(SedArgs files script _) = +    if null script then return $ s {script = x} +     else do +       let (dir, fp) = splitFileName x+       fs <- G.globDir1 (G.compile fp) dir+       if null fs then error $ fp ++ ": No such file or directory"+        else return $ s {files = files ++ fs}++usage :: String+usage = unlines help +  where help = ["usage: Hsed [-n] script [file...]",+                "       Hsed [-n] -e script [-e script]... [-f script_file]... [file...]",+                "       Hsed [-n] [-e script]... -f script_file [-f script_file]... [file...]"+               ]+
+ src/TestSuite.hs view
@@ -0,0 +1,177 @@+{-# LANGUAGE DeriveDataTypeable #-}++import System.Directory (getDirectoryContents)+import System.FilePath (replaceExtension, takeExtension, (</>))+import Control.Monad (forM_, when)+import qualified Control.Exception as E+import qualified Control.Monad.State as S+import System.Console.CmdArgs+import Data.List (isPrefixOf)+import qualified Data.ByteString.Char8 as B+import Hsed.Parsec (parseSed, sedCmds)+import Hsed.StreamEd+import Hsed.SedState+import Hsed.Sed (execScript)++data Config = Config {+  dir :: FilePath,+  inp :: String,+  ok :: String,+  file :: FilePath+} deriving (Show, Data, Typeable)++config :: Mode (CmdArgs Config)+config = cmdArgsMode $ Config+   {dir = def &= typFile &= help "Directory with the test cases"+   ,inp = def &= help "Input file extension"+   ,ok  = def &= help "Output file extension"+   ,file= def &= typFile &= help "Sed script to test"+   } &=+   program "TestSuite" &=+   summary "TestSuite 0.1" &=+   help "" &=+   details []++data Result = OK | Diff String | Error String +  deriving (Show, Eq, Typeable)++main :: IO ()+main = do+  cnf <- cmdArgsRun config+  let script = file cnf+  testParse+  if (not.null) script then+    testFile script (inp cnf) (ok cnf)+   else+    testDir (dir cnf) (inp cnf) (ok cnf) ++passed :: String -> String+passed script = result script " is passed"++failed :: String -> String+failed script = result script " is failed"++result :: String -> String -> String+result script str = "Test " ++ script ++ str++testParse :: IO ()+testParse = do+   putStrLn "<-- Sed parser tests started"+   forM_ ptests $ \(str, etalon) -> +      case parseSed sedCmds str of+        Left x -> putStrLn  (failed str ++ " -> ") >> print x+        Right y -> if show y == etalon then putStrLn $ passed (show str)+                    else do +                     putStrLn $ failed str+                     print etalon+                     print "====>"+                     print y++   putStrLn "--> Sed parser tests ended\n"+   +testFile :: FilePath -> String -> String -> IO ()+testFile script inext okext = do+    let inpfile = replaceExtension script inext+    let okfile = replaceExtension script okext    +    sed <- B.readFile script `catch` openFileError script+    res <- execScript [inpfile] (B.unpack sed)+    okf <- B.readFile okfile `catch` openFileError okfile +    if res == okf then +      putStrLn $ passed script+     else do+      putStrLn $ failed script  +      mapM_ print (B.lines okf)+      print "===>"+      mapM_ print (B.lines res)+    where+      openFileError f e = putStr ("Error: Couldn't open " ++ f ++ +           ": " ++ show (e :: E.IOException)) >> return B.empty      ++testDir :: FilePath -> String -> String -> IO ()+testDir path inpext okext = do+    putStrLn "<-- Sed script tests started"+    names <- getDirectoryContents path+    forM_ names $ \n -> do+       let filePath = path </> n+       when (takeExtension filePath == ".sed") $+           testFile filePath inpext okext+    putStrLn "--> Sed script tests ended"++ptests :: [(String, String)]+ptests = [+ ("/123/=",+  "[SedCmd (Address (Just (Pat \"123\")) Nothing False) LineNum]"),+ ("/123/{\n=\n}",+  "[SedCmd (Address (Just (Pat \"123\")) Nothing False) (Group [SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) LineNum])]"),+ ("a\\\n123\\\n456",+  "[SedCmd (Address Nothing Nothing False) (Append \"123\\n456\")]"),+ ("a\\\n~",+  "[SedCmd (Address Nothing Nothing False) (Append \"~\")]"),+ ("/123/,/456/b", +  "[SedCmd (Address (Just (Pat \"123\")) (Just (Pat \"456\")) False) (Branch Nothing)]"),+ ("y/aaa/bbb/",+  "[SedCmd (Address Nothing Nothing False) (Transform \"aaa\" \"bbb\")]"),+ ("s/aaa/bbb/g",+  "[SedCmd (Address Nothing Nothing False) (Substitute \"aaa\" \"bbb\" (Flags (Just (OccurrencePrint (Just ReplaceAll) False)) Nothing))]"),+ ("s'aaa'b&bb'1pw 123",+  "[SedCmd (Address Nothing Nothing False) (Substitute \"aaa\" \"b\\\\0bb\" (Flags (Just (OccurrencePrint (Just (Replace 1)) True)) (Just \"123\")))]"),+ ("s/aaa/aa\1bb/g",+  "[SedCmd (Address Nothing Nothing False) (Substitute \"aaa\" \"aa\\SOHbb\" (Flags (Just (OccurrencePrint (Just ReplaceAll) False)) Nothing))]"),+ ("s/aaa/\3/",+  "[SedCmd (Address Nothing Nothing False) (Substitute \"aaa\" \"\\ETX\" (Flags (Just (OccurrencePrint Nothing False)) Nothing))]"),+ ("r ReadFile.cmd",+  "[SedCmd (Address Nothing Nothing False) (ReadFile \"ReadFile.cmd\")]"),+ ("w WriteFile.cmd",+  "[SedCmd (Address Nothing Nothing False) (WriteFile \"WriteFile.cmd\")]"),+ ("{\n=\n=\n}",+  "[SedCmd (Address Nothing Nothing False) (Group [SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) LineNum,SedCmd (Address Nothing Nothing False) LineNum])]"),+ ("=\n=\n",+  "[SedCmd (Address Nothing Nothing False) LineNum,SedCmd (Address Nothing Nothing False) LineNum]"),+ (" #=",+  "[SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) Comment]"),+ ("#n Print line before and after changes.\n{\n=\n}",+  "[SedCmd (Address Nothing Nothing False) Comment,SedCmd (Address Nothing Nothing False) (Group [SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) LineNum])]"),+ ("r ReadFile.cmd\nr ReadFile.cmd",+  "[SedCmd (Address Nothing Nothing False) (ReadFile \"ReadFile.cmd\"),SedCmd (Address Nothing Nothing False) (ReadFile \"ReadFile.cmd\")]"),+ ("/^\\.H1/n\n/^$/d",+  "[SedCmd (Address (Just (Pat \"^\\\\.H1\")) Nothing False) NextLine,SedCmd (Address (Just (Pat \"^$\")) Nothing False) DeleteLine]"),+ ("s/      /\\n/2",+  "[SedCmd (Address Nothing Nothing False) (Substitute \"      \" \"\\n\" (Flags (Just (OccurrencePrint (Just (Replace 2)) False)) Nothing))]"),+ ("s/Owner and Operator\\nGuide/aaa/g",+  "[SedCmd (Address Nothing Nothing False) (Substitute \"Owner and Operator\\nGuide\" \"aaa\" (Flags (Just (OccurrencePrint (Just ReplaceAll) False)) Nothing))]"),+ ("/Operator$/{\nN\ns/Owner and Operator\\nGuide/Installation Guide/\n}\n",+  "[SedCmd (Address (Just (Pat \"Operator$\")) Nothing False) (Group [SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) AppendLinePat,SedCmd (Address Nothing Nothing False) (Substitute \"Owner and Operator\\nGuide\" \"Installation Guide\" (Flags (Just (OccurrencePrint Nothing False)) Nothing))])]"),+ ("s/Owner and Operator Guide/Installation Guide/\n/Owner/{\nN\ns/ *\n/ /\ns/Owner andOperator Guide */Installation Guide\\n/\n}",+  "[SedCmd (Address Nothing Nothing False) (Substitute \"Owner and Operator Guide\" \"Installation Guide\" (Flags (Just (OccurrencePrint Nothing False)) Nothing)),SedCmd (Address (Just (Pat \"Owner\")) Nothing False) (Group [SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) AppendLinePat,SedCmd (Address Nothing Nothing False) (Substitute \" *\\n\" \" \" (Flags (Just (OccurrencePrint Nothing False)) Nothing)),SedCmd (Address Nothing Nothing False) (Substitute \"Owner andOperator Guide *\" \"Installation Guide\\n\" (Flags (Just (OccurrencePrint Nothing False)) Nothing))])]"),+ ("    /^\n$/D",+  "[SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address (Just (Pat \"^\\n$\")) Nothing False) DeletePat]"),+ ("{\n{\n}\n}",+  "[SedCmd (Address Nothing Nothing False) (Group [SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) (Group [SedCmd (Address Nothing Nothing False) EmptyCmd])])]"),+ ("{\n{\nN\n}\n}",+  "[SedCmd (Address Nothing Nothing False) (Group [SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) (Group [SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) AppendLinePat])])]"),+ ("/UNIX/{\n  N\n  /\nSystem/{\n  s// Operating &/\n  P\n  D\n  }\n}",+  "[SedCmd (Address (Just (Pat \"UNIX\")) Nothing False) (Group [SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) AppendLinePat,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address (Just (Pat \"\\nSystem\")) Nothing False) (Group [SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) (Substitute \"\\nSystem\" \" Operating \\\\0\" (Flags (Just (OccurrencePrint Nothing False)) Nothing)),SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) WriteUpPat,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) DeletePat,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd])])]"),+ ("=\n=\n  #\n",+  "[SedCmd (Address Nothing Nothing False) LineNum,SedCmd (Address Nothing Nothing False) LineNum,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) Comment]"),+ ("  \n =\n \n =\n",+  "[SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) LineNum,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) LineNum]"),+ ("  \n \n \n \n",+  "[SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd]"),+ ("{N\nN\n{=\n  }\n}",+  "[SedCmd (Address Nothing Nothing False) (Group [SedCmd (Address Nothing Nothing False) AppendLinePat,SedCmd (Address Nothing Nothing False) AppendLinePat,SedCmd (Address Nothing Nothing False) (Group [SedCmd (Address Nothing Nothing False) LineNum,SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd])])]"),+ ("{\n }",+  "[SedCmd (Address Nothing Nothing False) (Group [SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) EmptyCmd])]"),+ ("{\nN\n}\n#/Owner/{}",+  "[SedCmd (Address Nothing Nothing False) (Group [SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) AppendLinePat]),SedCmd (Address Nothing Nothing False) Comment]"),+ ("{\n}\nP",+  "[SedCmd (Address Nothing Nothing False) (Group [SedCmd (Address Nothing Nothing False) EmptyCmd]),SedCmd (Address Nothing Nothing False) WriteUpPat]"),+ ("N\nP",+  "[SedCmd (Address Nothing Nothing False) AppendLinePat,SedCmd (Address Nothing Nothing False) WriteUpPat]"),+ ("1!H;1!{ x; p; x \n};d",+  "[SedCmd (Address (Just (LineNumber 1)) Nothing True) AppendHold,SedCmd (Address (Just (LineNumber 1)) Nothing True) (Group [SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) Exchange,SedCmd (Address Nothing Nothing False) PrintPat,SedCmd (Address Nothing Nothing False) Exchange]),SedCmd (Address Nothing Nothing False) DeleteLine]"),+ ("/^0$/{\nc\\\nyes\n}",+  "[SedCmd (Address (Just (Pat \"^0$\")) Nothing False) (Group [SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) (Change \"yes\")])]"),+ ("2,5 {\ns/[\t ]//g\n}",+  "[SedCmd (Address (Just (LineNumber 2)) (Just (LineNumber 5)) False) (Group [SedCmd (Address Nothing Nothing False) EmptyCmd,SedCmd (Address Nothing Nothing False) (Substitute \"[\\t ]\" \"\" (Flags (Just (OccurrencePrint (Just ReplaceAll) False)) Nothing))])]")+--}+ ]
tests/ReadFile.sed view
@@ -1,1 +1,1 @@-/^Company-list/r tests/company.lst+/^Company-list/r ../tests/company.lst