git-annex/Build/EvilSplicer.hs

667 lines
20 KiB
Haskell
Raw Normal View History

2013-04-13 20:00:36 +00:00
{- Expands template haskell splices
-
- You should probably just use http://hackage.haskell.org/package/zeroth
- instead. I wish I had known about it before writing this.
2013-04-13 20:00:36 +00:00
-
- First, the code must be built with a ghc that supports TH,
- and the splices dumped to a log. For example:
- cabal build --ghc-options=-ddump-splices 2>&1 | tee log
-
- Along with the log, a headers file may also be provided, containing
2013-04-13 20:00:36 +00:00
- additional imports needed by the template haskell code.
-
- This program will parse the log, and expand all splices therein,
- writing files to the specified destdir (which can be "." to modify
- the source tree directly). They can then be built a second
2013-04-13 20:00:36 +00:00
- time, with a ghc that does not support TH.
-
- Note that template haskell code may refer to symbols that are not
- exported by the library that defines the TH code. In this case,
- the library has to be modifed to export those symbols.
-
- There can also be other problems with the generated code; it may
- need modifications to compile.
-
-
- Copyright 2013 Joey Hess <joey@kitenet.net>
-
- Licensed under the GNU GPL version 3 or higher.
-}
2013-04-17 15:57:46 +00:00
module Main where
2013-04-13 20:00:36 +00:00
import Text.Parsec
import Text.Parsec.String
import Control.Applicative ((<$>))
import Data.Either
2013-12-27 04:05:15 +00:00
import Data.List hiding (find)
2013-04-13 20:00:36 +00:00
import Data.String.Utils
import Data.Char
import System.Environment
import System.FilePath
import System.Directory
2013-12-27 04:10:04 +00:00
import System.IO
import Control.Monad
2013-12-27 04:05:15 +00:00
import Prelude hiding (log)
2013-04-13 20:00:36 +00:00
import Utility.Monad
import Utility.Misc
import Utility.Exception
import Utility.Path
2013-12-27 04:10:04 +00:00
import Utility.FileSystemEncoding
2013-04-13 20:00:36 +00:00
data Coord = Coord
{ coordLine :: Int
, coordColumn :: Int
}
deriving (Read, Show)
offsetCoord :: Coord -> Coord -> Coord
offsetCoord a b = Coord
(coordLine a - coordLine b)
(coordColumn a - coordColumn b)
data SpliceType = SpliceExpression | SpliceDeclaration
deriving (Read, Show, Eq)
2013-04-13 20:00:36 +00:00
data Splice = Splice
{ splicedFile :: FilePath
, spliceStart :: Coord
, spliceEnd :: Coord
, splicedExpression :: String
2013-04-13 21:38:11 +00:00
, splicedCode :: String
, spliceType :: SpliceType
2013-04-13 20:00:36 +00:00
}
deriving (Read, Show)
isExpressionSplice :: Splice -> Bool
isExpressionSplice s = spliceType s == SpliceExpression
2013-04-13 20:00:36 +00:00
number :: Parser Int
number = read <$> many1 digit
2013-04-13 21:38:11 +00:00
{- A pair of Coords is written in one of three ways:
- "95:21-73", "1:1", or "(92,25)-(94,2)"
2013-04-13 20:00:36 +00:00
-}
coordsParser :: Parser (Coord, Coord)
2013-04-13 21:38:11 +00:00
coordsParser = (try singleline <|> try weird <|> multiline) <?> "Coords"
2013-04-13 20:00:36 +00:00
where
singleline = do
line <- number
2013-12-27 04:05:15 +00:00
void $ char ':'
2013-04-13 20:00:36 +00:00
startcol <- number
2013-12-27 04:05:15 +00:00
void $ char '-'
2013-04-13 20:00:36 +00:00
endcol <- number
return $ (Coord line startcol, Coord line endcol)
2013-04-13 21:38:11 +00:00
weird = do
line <- number
2013-12-27 04:05:15 +00:00
void $ char ':'
2013-04-13 21:38:11 +00:00
col <- number
return $ (Coord line col, Coord line col)
2013-04-13 20:00:36 +00:00
multiline = do
start <- fromparens
2013-12-27 04:05:15 +00:00
void $ char '-'
2013-04-13 20:00:36 +00:00
end <- fromparens
return $ (start, end)
fromparens = between (char '(') (char ')') $ do
line <- number
2013-12-27 04:05:15 +00:00
void $ char ','
2013-04-13 20:00:36 +00:00
col <- number
return $ Coord line col
indent :: Parser String
indent = many1 $ char ' '
restOfLine :: Parser String
restOfLine = newline `after` many (noneOf "\n")
indentedLine :: Parser String
indentedLine = indent >> restOfLine
spliceParser :: Parser Splice
spliceParser = do
file <- many1 (noneOf ":\n")
2013-12-27 04:05:15 +00:00
void $ char ':'
2013-04-13 20:00:36 +00:00
(start, end) <- coordsParser
2013-12-27 04:05:15 +00:00
void $ string ": Splicing "
splicetype <- tosplicetype
<$> (string "expression" <|> string "declarations")
2013-12-27 04:05:15 +00:00
void newline
2013-04-13 20:00:36 +00:00
2013-04-15 18:23:39 +00:00
getthline <- expressionextractor
expression <- unlines <$> many1 getthline
2013-04-13 20:00:36 +00:00
2013-12-27 04:05:15 +00:00
void indent
void $ string "======>"
void newline
2013-04-13 20:00:36 +00:00
2013-04-15 18:23:39 +00:00
getcodeline <- expressionextractor
realcoords <- try (Right <$> getrealcoords file) <|> (Left <$> getcodeline)
2013-04-13 21:38:11 +00:00
codelines <- many getcodeline
return $ case realcoords of
Left firstcodeline ->
Splice file start end expression
(unlines $ firstcodeline:codelines)
splicetype
2013-04-13 21:38:11 +00:00
Right (realstart, realend) ->
Splice file realstart realend expression
(unlines codelines)
splicetype
where
tosplicetype "declarations" = SpliceDeclaration
tosplicetype "expression" = SpliceExpression
tosplicetype s = error $ "unknown splice type: " ++ s
2013-04-13 20:00:36 +00:00
2013-04-15 18:23:39 +00:00
{- All lines of the indented expression start with the same
- indent, which is stripped. Any other indentation is preserved. -}
expressionextractor = do
i <- lookAhead indent
return $ try $ do
2013-12-27 04:05:15 +00:00
void $ string i
2013-04-15 18:23:39 +00:00
restOfLine
{- When splicing declarations, GHC will output a splice
- at 1:1, and then inside the splice code block,
- the first line will give the actual coordinates of the
- line that was spliced. -}
getrealcoords file = do
2013-12-27 04:05:15 +00:00
void indent
void $ string file
void $ char ':'
2013-04-15 18:23:39 +00:00
char '\n' `after` coordsParser
2013-04-13 20:00:36 +00:00
{- Extracts the splices, ignoring the rest of the compiler output. -}
splicesExtractor :: Parser [Splice]
splicesExtractor = rights <$> many extract
where
extract = try (Right <$> spliceParser) <|> (Left <$> compilerJunkLine)
compilerJunkLine = restOfLine
{- Modifies the source file, expanding the splices, which all must
- have the same splicedFile. Writes the new file to the destdir.
2013-04-13 20:00:36 +00:00
-
- Each splice's Coords refer to the original position in the file,
- and not to its position after any previous splices may have inserted
- or removed lines.
-
- To deal with this complication, the file is broken into logical lines
- (which can contain any String, including a multiline or empty string).
- Each splice is assumed to be on its own block of lines; two
- splices on the same line is not currently supported.
- This means that a splice can modify the logical lines within its block
- as it likes, without interfering with the Coords of other splices.
-
2013-04-13 20:00:36 +00:00
- As well as expanding splices, this can add a block of imports to the
- file. These are put right before the first line in the file that
- starts with "import "
-}
applySplices :: FilePath -> Maybe String -> [Splice] -> IO ()
2013-12-27 04:05:15 +00:00
applySplices _ _ [] = noop
applySplices destdir imports splices@(first:_) = do
2013-04-13 20:00:36 +00:00
let f = splicedFile first
let dest = (destdir </> f)
lls <- map (++ "\n") . lines <$> readFileStrictAnyEncoding f
createDirectoryIfMissing True (parentDir dest)
let newcontent = concat $ addimports $ expand lls splices
oldcontent <- catchMaybeIO $ readFileStrictAnyEncoding dest
when (oldcontent /= Just newcontent) $ do
putStrLn $ "splicing " ++ f
2013-12-27 04:10:04 +00:00
withFile dest WriteMode $ \h -> do
fileEncoding h
hPutStr h newcontent
hClose h
2013-04-13 20:00:36 +00:00
where
expand lls [] = lls
expand lls (s:rest)
| isExpressionSplice s = expand (expandExpressionSplice s lls) rest
| otherwise = expand (expandDeclarationSplice s lls) rest
2013-04-13 20:00:36 +00:00
addimports lls = case imports of
Nothing -> lls
Just v ->
let (start, end) = break ("import " `isPrefixOf`) lls
in if null end
then start
else concat
[ start
, [v]
, end
]
{- Declaration splices are expanded to replace their whole line. -}
expandDeclarationSplice :: Splice -> [String] -> [String]
expandDeclarationSplice s lls = concat [before, [splice], end]
where
cs = spliceStart s
ce = spliceEnd s
(before, rest) = splitAt (coordLine cs - 1) lls
(_oldlines, end) = splitAt (1 + coordLine (offsetCoord ce cs)) rest
splice = mangleCode $ splicedCode s
{- Expression splices are expanded within their line. -}
expandExpressionSplice :: Splice -> [String] -> [String]
2013-12-27 04:05:15 +00:00
expandExpressionSplice sp lls = concat [before, spliced:padding, end]
2013-04-13 20:00:36 +00:00
where
2013-12-27 04:05:15 +00:00
cs = spliceStart sp
ce = spliceEnd sp
2013-04-13 20:00:36 +00:00
(before, rest) = splitAt (coordLine cs - 1) lls
(oldlines, end) = splitAt (1 + coordLine (offsetCoord ce cs)) rest
2013-04-15 18:23:39 +00:00
(splicestart, padding, spliceend) = case map expandtabs oldlines of
ss:r
| null r -> (ss, [], ss)
| otherwise -> (ss, take (length r) (repeat []), last r)
_ -> ([], [], [])
spliced = concat
2013-04-13 22:08:32 +00:00
[ joinsplice $ deqqstart $ take (coordColumn cs - 1) splicestart
2013-12-27 04:05:15 +00:00
, addindent (findindent splicestart) (mangleCode $ splicedCode sp)
2013-04-15 18:23:39 +00:00
, deqqend $ drop (coordColumn ce) spliceend
2013-04-13 20:00:36 +00:00
]
{- coordinates assume tabs are expanded to 8 spaces -}
expandtabs = replace "\t" (take 8 $ repeat ' ')
{- splicing leaves $() quasiquote behind; remove it -}
deqqstart s = case reverse s of
2013-12-27 04:05:15 +00:00
('(':'$':restq) -> reverse restq
2013-04-13 20:00:36 +00:00
_ -> s
deqqend (')':s) = s
deqqend s = s
2013-04-13 22:08:32 +00:00
{- Prepare the code that comes just before the splice so
- the splice will combine with it appropriately. -}
joinsplice s
-- all indentation? Skip it, we'll use the splice's indentation
| all isSpace s = ""
-- function definition needs no preparation
-- ie: foo = $(splice)
| "=" `isSuffixOf` s' = s
2013-04-15 21:42:30 +00:00
-- nor does lambda definition or case expression
2013-04-15 18:50:05 +00:00
| "->" `isSuffixOf` s' = s
-- nor does a let .. in declaration
| "in" `isSuffixOf` s' = s
2013-04-13 22:08:32 +00:00
-- already have a $ to set off the splice
-- ie: foo $ $(splice)
| "$" `isSuffixOf` s' = s
-- need to add a $ to set off the splice
-- ie: bar $(splice)
| otherwise = s ++ " $ "
where
s' = filter (not . isSpace) s
2013-04-13 20:00:36 +00:00
findindent = length . takeWhile isSpace
addindent n = unlines . map (i ++) . lines
where
i = take n $ repeat ' '
{- Tweaks code output by GHC in splices to actually build. Yipes. -}
mangleCode :: String -> String
mangleCode = flip_colon
2013-09-22 07:22:05 +00:00
. remove_unnecessary_type_signatures
. lambdaparenhack
. lambdaparens
2013-04-18 18:17:12 +00:00
. declaration_parens
. case_layout
. case_layout_multiline
. yesod_url_render_hack
. text_builder_hack
2013-04-16 19:10:23 +00:00
. nested_instances
. boxed_fileembed
. collapse_multiline_strings
2013-04-16 19:10:23 +00:00
. remove_package_version
2013-04-17 04:45:55 +00:00
. emptylambda
2013-04-13 21:15:05 +00:00
where
2013-04-18 18:17:12 +00:00
{- Lambdas are often output without parens around them.
- This breaks when the lambda is immediately applied to a
- parameter.
-
- For example:
-
- renderRoute (StaticR sub_a1nUH)
- = \ (a_a1nUI, b_a1nUJ)
- -> (((pack "static") : a_a1nUI),
- b_a1nUJ)
- (renderRoute sub_a1nUH)
-
- There are sometimes many lines of lambda code that need to be
- parenthesised. Approach: find the "->" and scan down the
- column to the first non-whitespace. This is assumed
- to be the expression after the lambda.
-
- Runs recursively on the body of the lambda, to handle nested
- lambdas.
2013-04-18 18:17:12 +00:00
-}
lambdaparens = parsecAndReplace $ do
-- skip lambdas inside tuples or parens
prefix <- noneOf "(, \n"
preindent <- many1 $ oneOf " \n"
2013-12-27 04:05:15 +00:00
void $ string "\\ "
2013-04-18 18:17:12 +00:00
lambdaparams <- restofline
2013-09-22 19:36:56 +00:00
continuedlambdaparams <- many $ try $ do
2013-12-27 04:05:15 +00:00
indent1 <- many1 $ char ' '
2013-09-22 15:41:26 +00:00
p <- satisfy isLetter
aram <- many $ satisfy isAlphaNum <|> oneOf "_"
2013-12-27 04:05:15 +00:00
void newline
return $ indent1 ++ p:aram ++ "\n"
indent1 <- many1 $ char ' '
void $ string "-> "
2013-04-18 18:17:12 +00:00
firstline <- restofline
lambdalines <- many $ try $ do
2013-12-27 04:05:15 +00:00
void $ string indent1
void $ char ' '
2013-04-18 18:17:12 +00:00
l <- restofline
2013-12-27 04:05:15 +00:00
return $ indent1 ++ " " ++ l
return $ concat
[ prefix:preindent
2013-09-22 19:36:56 +00:00
, "(\\ " ++ lambdaparams ++ "\n"
, concat continuedlambdaparams
2013-12-27 04:05:15 +00:00
, indent1 ++ "-> "
, lambdaparens $ intercalate "\n" (firstline:lambdalines)
, ")\n"
]
{- Hack to add missing parens in a specific case in yesod
- static route code.
-
- StaticR
- yesod_dispatch_env_a4iDV
- (\ p_a4iE2 r_a4iE3
2013-12-18 21:39:09 +00:00
- -> r_a4iE3
- {Network.Wai.pathInfo = p_a4iE2}
- xrest_a4iDT req_a4iDW)) }
-
- Need to add another paren around the lambda, and close it
- before its parameters. lambdaparens misses this one because
- there is already one paren present.
-
2013-12-18 21:39:09 +00:00
- Note that the { } may be on the same line, or wrapped to next.
-
- FIXME: This is a hack. lambdaparens could just always add a
- layer of parens even when a lambda seems to be in parent.
-}
lambdaparenhack = parsecAndReplace $ do
2013-12-27 04:05:15 +00:00
indent1 <- many1 $ char ' '
staticr <- string "StaticR"
2013-12-27 04:05:15 +00:00
void newline
void $ string indent1
yesod_dispatch_env <- restofline
2013-12-27 04:05:15 +00:00
void $ string indent1
lambdaprefix <- string "(\\ "
l1 <- restofline
2013-12-27 04:05:15 +00:00
void $ string indent1
lambdaarrow <- string " ->"
l2 <- restofline
2013-12-18 21:39:09 +00:00
l3 <- if '{' `elem` l2 && '}' `elem` l2
then return ""
else do
2013-12-27 04:05:15 +00:00
void $ string indent1
2013-12-18 21:39:09 +00:00
restofline
return $ unlines
2013-12-27 04:05:15 +00:00
[ indent1 ++ staticr
, indent1 ++ yesod_dispatch_env
, indent1 ++ "(" ++ lambdaprefix ++ l1
, indent1 ++ lambdaarrow ++ l2 ++ l3 ++ ")"
]
2013-04-18 18:17:12 +00:00
restofline = manyTill (noneOf "\n") newline
{- For some reason, GHC sometimes doesn't like the multiline
- strings it creates. It seems to get hung up on \{ at the
- start of a new line sometimes, wanting it to not be escaped.
-
- To work around what is likely a GHC bug, just collapse
- multiline strings. -}
collapse_multiline_strings = parsecAndReplace $ do
2013-12-27 04:05:15 +00:00
void $ string "\\\n"
void $ many1 $ oneOf " \t"
void $ string "\\"
return "\\n"
2013-04-13 21:15:05 +00:00
{- GHC outputs splices using explicit braces rather than layout.
- For a case expression, it does something weird:
-
- case foo of {
- xxx -> blah
- yyy -> blah };
-
- This is not legal Haskell; the statements in the case must be
- separated by ';'
-
- To fix, we could just put a semicolon at the start of every line
- containing " -> " ... Except that lambdas also contain that.
- But we can get around that: GHC outputs lambas like this:
-
- \ foo
- -> bar
-
- Or like this:
-
- \ foo -> bar
-
- So, we can put the semicolon at the start of every line
- containing " -> " unless there's a "\ " first, or it's
- all whitespace up until it.
-}
case_layout = parsecAndReplace $ do
2013-12-27 04:05:15 +00:00
void newline
indent1 <- many1 $ char ' '
prefix <- manyTill (noneOf "\n") (try (string "-> "))
2013-04-17 04:45:55 +00:00
if length prefix > 10
then unexpected "too long a prefix"
else if "\\ " `isInfixOf` prefix
then unexpected "lambda expression"
else if null prefix
then unexpected "second line of lambda"
2013-12-27 04:05:15 +00:00
else return $ "\n" ++ indent1 ++ "; " ++ prefix ++ " -> "
{- Sometimes cases themselves span multiple lines:
-
- Nothing
- -> foo
-
- -- This is not yet handled!
- ComplexConstructor var var
- var var
- -> foo
-}
case_layout_multiline = parsecAndReplace $ do
2013-12-27 04:05:15 +00:00
void newline
indent1 <- many1 $ char ' '
2013-04-18 18:17:12 +00:00
firstline <- restofline
2013-12-27 04:05:15 +00:00
void $ string indent1
indent2 <- many1 $ char ' '
2013-12-27 04:05:15 +00:00
void $ string "-> "
if "\\ " `isInfixOf` firstline
then unexpected "lambda expression"
2013-12-27 04:05:15 +00:00
else return $ "\n" ++ indent1 ++ "; " ++ firstline ++ "\n"
++ indent1 ++ indent2 ++ "-> "
2013-04-17 04:45:55 +00:00
{- (foo, \ -> bar) is not valid haskell, GHC.
- Change to (foo, bar)
-
- (Does this ever happen outside a tuple? Only saw
- it inside them..
-}
emptylambda = replace ", \\ -> " ", "
{- GHC may output this:
-
- instance RenderRoute WebApp where
- data instance Route WebApp
- ^^^^^^^^
- The marked word should not be there.
-
- FIXME: This is a yesod-specific hack, it should look for the
- outer instance.
-}
nested_instances = replace " data instance Route" " data Route"
2013-04-16 19:10:23 +00:00
{- GHC does not properly parenthesise generated data type
- declarations. -}
declaration_parens = replace "StaticR Route Static" "StaticR (Route Static)"
2013-09-22 07:22:05 +00:00
{- A type signature is sometimes given for an entire lambda,
- which is not properly parenthesized or laid out. This is a
- hack to remove one specific case where this happens and the
- signature is easily inferred, so is just removed.
-}
remove_unnecessary_type_signatures = parsecAndReplace $ do
2013-12-27 04:05:15 +00:00
void $ string " ::"
void newline
void $ many1 $ char ' '
void $ string "Text.Css.Block Text.Css.Resolved"
void newline
2013-09-22 07:22:05 +00:00
return ""
2013-04-13 21:15:05 +00:00
{- GHC may add full package and version qualifications for
- symbols from unimported modules. We don't want these.
-
- Examples:
- "blaze-html-0.4.3.1:Text.Blaze.Internal.preEscapedText"
- "ghc-prim:GHC.Types.:"
-}
remove_package_version = parsecAndReplace $
mangleSymbol <$> qualifiedSymbol
2013-04-13 21:15:05 +00:00
mangleSymbol "GHC.Types." = ""
mangleSymbol "GHC.Tuple." = ""
mangleSymbol s = s
2013-04-13 21:15:05 +00:00
qualifiedSymbol :: Parser String
qualifiedSymbol = do
2013-12-27 04:05:15 +00:00
s <- hstoken
void $ char ':'
2013-04-17 04:45:55 +00:00
if length s < 5
then unexpected "too short to be a namespace"
else do
t <- hstoken
case t of
(c:r) | isUpper c && "." `isInfixOf` r -> return t
_ -> unexpected "not a module qualified symbol"
2013-04-13 21:15:05 +00:00
2013-12-27 04:05:15 +00:00
hstoken :: Parser String
hstoken = do
2013-04-17 04:45:55 +00:00
t <- satisfy isLetter
oken <- many $ satisfy isAlphaNum <|> oneOf "-.'"
return $ t:oken
2013-04-13 21:15:05 +00:00
{- This works when it's "GHC.Types.:", but we strip
- that above, so have to fix up after it here.
- The ; is added by case_layout. -}
flip_colon = replace "; : _ " "; _ : "
{- Embedded files use unsafe packing, which is problimatic
- for several reasons, including that GHC sometimes omits trailing
- newlines in the file content, which leads to the wrong byte
- count. Also, GHC sometimes outputs unicode characters, which
- are not legal in unboxed strings.
-
- Avoid problems by converting:
- GHC.IO.unsafePerformIO
- (Data.ByteString.Unsafe.unsafePackAddressLen
- lllll
- "blabblah"#)),
- to:
- Data.ByteString.Char8.pack "blabblah"),
-
- Note that the string is often multiline. This only works if
- collapse_multiline_strings has run first.
-}
boxed_fileembed :: String -> String
boxed_fileembed = parsecAndReplace $ do
i <- indent
void $ string "GHC.IO.unsafePerformIO"
void newline
void indent
void $ string "(Data.ByteString.Unsafe.unsafePackAddressLen"
void newline
void indent
void number
void newline
void indent
void $ char '"'
s <- restOfLine
let s' = take (length s - 5) s
if "\"#))," `isSuffixOf` s
then return (i ++ "Data.ByteString.Char8.pack \"" ++ s' ++ "\"),\n")
else fail "not an unboxed string"
{- This works around a problem in the expanded template haskell for Yesod
- type-safe url rendering.
-
- It generates code like this:
-
- (toHtml
- (\ u_a2ehE -> urender_a2ehD u_a2ehE []
- (CloseAlert aid)))));
-
- Where urender_a2ehD is the function returned by getUrlRenderParams.
- But, that function that only takes 2 params, not 3.
- And toHtml doesn't take a parameter at all!
-
- So, this modifes the code, to look like this:
-
- (toHtml
- (flip urender_a2ehD []
- (CloseAlert aid)))));
-
- FIXME: Investigate and fix this properly.
-}
yesod_url_render_hack :: String -> String
yesod_url_render_hack = parsecAndReplace $ do
2013-12-27 04:05:15 +00:00
void $ string "(toHtml"
void whitespace
void $ string "(\\"
void whitespace
wtf <- hstoken
void whitespace
void $ string "->"
void whitespace
renderer <- hstoken
void whitespace
void $ string wtf
void whitespace
return $ "(toHtml (flip " ++ renderer ++ " "
where
whitespace :: Parser String
whitespace = many $ oneOf " \t\r\n"
2013-12-27 04:05:15 +00:00
hstoken :: Parser String
hstoken = many1 $ satisfy isAlphaNum <|> oneOf "_"
{- Use exported symbol. -}
text_builder_hack :: String -> String
text_builder_hack = replace "Data.Text.Lazy.Builder.Internal.fromText" "Data.Text.Lazy.Builder.fromText"
{- Given a Parser that finds strings it wants to modify,
- and returns the modified string, does a mass
- find and replace throughout the input string.
- Rather slow, but crazy powerful. -}
parsecAndReplace :: Parser String -> String -> String
parsecAndReplace p s = case parse find "" s of
2013-12-27 04:05:15 +00:00
Left _e -> s
Right l -> concatMap (either return id) l
where
find :: Parser [Either Char String]
find = many $ try (Right <$> p) <|> (Left <$> anyChar)
2013-04-13 20:00:36 +00:00
main :: IO ()
main = go =<< getArgs
where
go (destdir:log:header:[]) = run destdir log (Just header)
go (destdir:log:[]) = run destdir log Nothing
go _ = error "usage: EvilSplicer destdir logfile [headerfile]"
run destdir log mheader = do
r <- parseFromFile splicesExtractor log
case r of
Left e -> error $ show e
Right splices -> do
let groups = groupBy (\a b -> splicedFile a == splicedFile b) splices
imports <- maybe (return Nothing) (catchMaybeIO . readFile) mheader
mapM_ (applySplices destdir imports) groups