the-snip (empty) → 0.0.0.1
raw patch · 11 files changed
+728/−0 lines, 11 filesdep +attoparsecdep +basedep +bytestringsetup-changed
Dependencies added: attoparsec, base, bytestring, hspec, optparse-simple, path, path-io, rio, text, the-snip, unix
Files
- LICENSE +30/−0
- README.md +109/−0
- Setup.hs +2/−0
- app/Main.hs +115/−0
- src/Import.hs +8/−0
- src/Run.hs +183/−0
- src/Types.hs +31/−0
- src/Util.hs +11/−0
- test/Spec.hs +1/−0
- test/UtilSpec.hs +14/−0
- the-snip.cabal +224/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Robert Fischer (c) 2022++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Author name here nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE Copyright (c) 2022 Subtle Medical - All Rights Reserved HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE Copyright (c) 2022 Subtle Medical - All Rights Reserved+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,109 @@+# The Snip++## Summary++Given a text file with demarcated regions, generate distinct output files containing the+content of those demarcated regions. These demarcated regions, called "snippets", may be +interleaved, nested, or overlapping.++### Installation++```+which stack || ( curl -sSL https://get.haskellstack.org/ | sh )+stack install the-snip # Ensure the output dir is on your $PATH+```++### Motivation++This program supports generating documentation. Often, a code snippet is worth thousands of words. Documentation like technical+instruction that requires having code, but linters and type-checkers can't process inline code samples, and nothing ensures the+code stays up-to-date with other dependent samples.++This program addresses the problem by being able to generate the snippets directly from text files. Those files are then available+for processing by Pygments, LaTeX, or whatever else.++## Usage++### Simple++1. Create a snippet by adding two lines into the file. One line must contain the literal string "`<<<@snip`" and may start with any other content. This is the "Start Marker". The other line must contain the literal string "`@snip>>>`" and may start with any other content. This is the "End Marker". The program discards all content before the literal strings, so feel free to start the line with a comment declaration and/or a description of the snippet.+1. Run `snip` and pass in the file name as the argument.+1. Read the snippet out of the newly-created output directory. The snippet directory for an input file has the same as the input file but with `.snip.d` appended. The snippet file name will be `1.snip.<FILEEXT>`, where `<FILEEXT>` is the file extension of the input file. For example, if you're processing Haskell files with a `.hs` file extension, the snippet file name will be `1.snip.hs`.++### Directory++If an argument is a directory, then the input files will be the immediate contents of that directory.++### Recursive++If the program is running with the `-r`/`--recurse` flag and an argument is a directory, then the input files will be the contents of that directory and all its subdirectories.++### Named Snippets++By default, the name of a snippet is the 1-based order of the snippet's start in the file. If you want to explicitly name the snippet file,+then pass in the snippet file name after the literal string of the start marker. For instance, this start marker will start a snippet named "foo":++```+# This is the start of the fooing section. <<<@snip foo+```++This snippet will be in the output directory and named `foo.snip.<FILEEXT>`.++The snippet name must be parseable as a relative file path ([details here](https://hackage.haskell.org/package/path-0.9.2/docs/Path-Posix.html#v:parseRelFile)) and may not include whitespace. Whitespace or the end of the line terminates the snippet name. The parser ignores any content on the line after the snippet name, so this start marker will start the same snippet file:++```+# <<<@snip foo This is the start of the fooing section.+```++### Interleaved and Nested Snippets++By default, an end marker terminates all started snippets. If the start marker uses named snippets, though, the end marker may specify which+snippet to end. For instance, this end marker will end the snippet named "foo":++```+# This is the end of the fooing section. @snip>>> foo+```++Analogous to start markers, the parser also ignores content after the snippet name, so this end marker will end the same snippet:++```+# @snip>>> foo This is the end of the fooing section.+```++Using specific end markers, snippets may start and end anywhere in the file. The parser will disregard lines that are markers for other snippets.++### Whitespace Trimming++If the program is running with the `-t`/`--trim` flag, then each line of the snippet has its common leading whitespace trimmed. +The common leading whitespace for a snippet is the longest sequence of tabs or spaces which start every non-empty line. For instance, this code:++```+ This is my code.+ It is very good code.++ It runs very well and does useful things.+ Yay, my code!+```++will trim down to:++```+This is my code.+ It is very good code.++ It runs very well and does useful things.+ Yay, my code!+```++This is nice for code samples extracted from nested sections of the code.++### Caveats++* The file system must be POSIX-compliant.+* Input files must be parseable as UTF8-encoded text files.+* The snippets are UTF8-encoded text files.+* The program must be able to read from all input files.+* The program must be able to freely create subdirectories, write files, and overwrite files in the output directory.+* Start and end markers may not be on the same line.+* There is an implicit catch-all end marker at EOF.+* Explicit snippet names must be unique within the file and may not be positive integers.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ app/Main.hs view
@@ -0,0 +1,115 @@+{-# LANGUAGE TemplateHaskell #-}+module Main (main) where++import Import+import Run+import Options.Applicative.Simple+import qualified Paths_the_snip+import RIO.Set qualified as S+import System.Posix.Files (getFileStatus, isDirectory, isRegularFile)+import Path.Posix+import Path.IO++description :: String+description = + "Within text files, use " <> show snipStartT <> + " on a line to denote that a snippet should start" <>+ " and " <> show snipEndT <> " to show that the snippet " <>+ " should end."++optionsParser :: Parser Options+optionsParser = Options+ <$> recursiveParser+ <*> many pathParser+ <*> optional baseDirParser+ <*> optional outputDirParser+ <*> trimParser+ <*> verboseParser+ where+ pathParser :: Parser FilePath+ pathParser = strArgument + ( metavar "PATH" + <> help "Path to input files and directories. May be specified multiple times."+ )+ trimParser = switch+ ( short 't'+ <> long "trim"+ <> help "Trim common whitespace"+ )+ verboseParser = switch+ ( short 'v'+ <> long "verbose"+ <> help "Be verbose in logging"+ )+ recursiveParser = switch+ ( short 'r'+ <> long "recurse"+ <> help "Recurse into subdirectories. If no path is given, recurse within the base directory."+ )+ baseDirParser = option someDirReadM+ ( short 'b'+ <> long "basedir"+ <> metavar "DIR"+ <> help "Base input directory. Defaults to the current working directory."+ )+ outputDirParser = option someDirReadM+ ( short 'o'+ <> long "outdir"+ <> metavar "DIR"+ <> help "Root directory for output. Will be created if it does not already exist. Defaults to the current working directory."+ )+ someDirReadM = maybeReader parseSomeDir++main :: IO ()+main = do+ (Options{..}, ()) <- simpleOptions+ $(simpleVersion Paths_the_snip.version)+ "Extract demarkated snippets from text files"+ description+ optionsParser+ empty+ baseDir <- optToAbsDir optsBaseDir+ logOpts <- logOptionsHandle stderr optsVerbose+ withLogFunc logOpts $ \logFunc -> do+ app <- App logFunc optsTrim baseDir+ <$> mkFiles baseDir optsRecursive optsPaths+ <*> optToAbsDir optsOutputDir+ runRIO app run++optToAbsDir :: Maybe (SomeBase Dir) -> IO (Path Abs Dir)+optToAbsDir = \case+ Nothing -> getCurrentDir+ Just (Abs absDir) -> return absDir+ Just (Rel relDir) -> (</> relDir) <$> getCurrentDir++mkFiles :: Path Abs Dir -> Bool -> [FilePath] -> IO (Set (Path Rel File))+mkFiles baseDir False [] = S.fromList . snd <$> listDirRel baseDir+mkFiles baseDir True [] = S.fromList . snd <$> listDirRecurRel baseDir+mkFiles baseDir recur (filepath:rest) = do+ restAsync <- async $+ if null rest then+ return S.empty+ else+ mkFiles baseDir recur rest+ fstat <- getFileStatus filepath+ if isDirectory fstat then do+ dir <- parseSomeDir filepath <&> + (\case+ (Abs absDir) -> absDir+ (Rel relDir) -> baseDir </> relDir+ )+ absFiles <- snd <$>+ if recur then+ listDirRecur dir+ else+ listDir dir+ relFiles <- sequence $ stripProperPrefix baseDir <$> absFiles+ S.union (S.fromList relFiles) <$> wait restAsync+ else if isRegularFile fstat then do+ file <- parseSomeFile filepath >>= \case+ Abs absFile -> stripProperPrefix baseDir absFile+ Rel relFile -> return relFile+ S.insert file <$> wait restAsync+ else+ fail $ "Found neither dir nor file at provided path " <> show filepath+
+ src/Import.hs view
@@ -0,0 +1,8 @@+{-# LANGUAGE NoImplicitPrelude #-}+module Import+ ( module RIO+ , module Types+ ) where++import RIO+import Types
+ src/Run.hs view
@@ -0,0 +1,183 @@+module Run (run, snipStartT, snipEndT) where++import Import+import Path.Posix+import Path.IO+import Data.Attoparsec.Text as Parser+import RIO.Text qualified as T+import RIO.List qualified as L++data LeadingWS = NoLeadingWS | LeadingSpaces Word | LeadingTabs Word deriving (Show)++data ParsedLine = EmptyLine | StartMarkerLine (Maybe (Path Rel File)) | EndMarkerLine (Maybe (Path Rel File)) | ContentLine (LeadingWS,Text) deriving (Show)++mkContentLine :: Text -> ParsedLine+mkContentLine content = ContentLine $ case T.uncons content of+ Nothing -> (NoLeadingWS, "")+ Just ('\t', _) -> + let (tabs,rest) = T.span ('\t' ==) content in+ (LeadingTabs (fromIntegral $ T.length tabs), T.copy rest)+ Just (' ', _) -> + let (spaces,rest) = T.span (' ' ==) content in+ (LeadingSpaces (fromIntegral $ T.length spaces), T.copy rest)+ Just _ -> + (NoLeadingWS, content)++parseLine :: Text -> RIO App ParsedLine+parseLine content = + case result of + Left err -> do+ logInfo . display . T.pack $+ "Defaulting back to content line: " <> err+ return $ mkContentLine content+ Right val -> do+ logInfo . display . T.pack $+ "Parsed non-content line: " <> show val+ return val+ where+ result = (`parseOnly` content) $ + parseEmptyLine <|> parseEndMarker <|> parseStartMarker++snipStartT :: Text+snipStartT = "<<<@snip"++parseEmptyLine :: Parser ParsedLine+parseEmptyLine =+ skipSpace >> endOfInput >> return EmptyLine++parseStartMarker :: Parser ParsedLine+parseStartMarker = + parseStartMarkerBeginning >> + (StartMarkerLine <$> (parseLabel <|> return Nothing))+ where+ parseStartMarkerBeginning = + string snipStartT <|>+ (Parser.take 1 >> parseStartMarkerBeginning)++snipEndT :: Text+snipEndT = "@snip>>>"++parseEndMarker :: Parser ParsedLine+parseEndMarker = + parseEndMarkerBeginning >>+ (EndMarkerLine <$> (parseLabel <|> return Nothing))+ where+ parseEndMarkerBeginning = + string snipEndT <|>+ (Parser.take 1 >> parseEndMarkerBeginning)++parseLabel :: Parser (Maybe (Path Rel File))+parseLabel = do+ skipSpace+ lbl <- Parser.takeTill Parser.isHorizontalSpace+ return $ parseRelFile (T.unpack lbl)++run :: RIO App ()+run = ask >>= \App{appFiles} ->+ pooledMapConcurrently_ runFile appFiles++runFile :: Path Rel File -> RIO App ()+runFile relFile = ask >>= \App{appBaseDir,appOutputDir} -> do+ let inFile = appBaseDir </> relFile+ let outDirBase = appOutputDir </> relFile+ outDir <- addExtension ".snips" outDirBase >>= addExtension ".d" >>= fileToDir+ logInfo . display . T.pack $ + "Processing file " <> show inFile <> " into dir " <> show outDir+ processFile inFile outDir+ where+ fileToDir = parseAbsDir . toFilePath++processFile :: Path Abs File -> Path Abs Dir -> RIO App ()+processFile inFile outDir = do+ fileLines <- liftIO $ T.lines <$> readFileUtf8 (toFilePath inFile)+ fileContents <- pooledMapConcurrently parseLine fileLines+ fileEx <- (".snip" <>) <$> fileExtension inFile+ logInfo . display . T.pack $ + "File contents for " <> show inFile <> ": " <> show fileContents+ processContents fileEx outDir 0 fileContents++processContents :: String -> Path Abs Dir -> Word -> [ParsedLine] -> RIO App ()+processContents _ _ _ [] = return ()+processContents fileExt outDir startCount (StartMarkerLine mayLbl:rest) = do+ logInfo . display . T.pack $+ "Saw the start of snippet for " <> show outDir <> ": " <> lbl+ outFile <- (outDir </>) <$> parseRelFile (lbl <> fileExt)+ concurrently_+ (processSnip outFile lbl rest)+ (processContents fileExt outDir (startCount+1) rest)+ where+ lbl = maybe (show startCount) toFilePath mayLbl+processContents fileExt outDir startCount (_:rest) = processContents fileExt outDir startCount rest++processSnip :: Path Abs File -> String -> [ParsedLine] -> RIO App ()+processSnip outFile _ [] =+ logInfo . display . T.pack $+ "No contents for snippet for " <> show outFile+processSnip outFile lbl (EmptyLine:remainingLines) =+ processSnip outFile lbl remainingLines+processSnip outFile lbl remainingLines = do+ contentT <- T.dropWhile ('\n' ==) . T.dropWhileEnd ('\n' ==) . T.unlines <$> extractSnipContent lbl remainingLines+ ensureDir $ parent outFile+ logInfo . display . T.pack $+ "Snippet for " <> show outFile <> ": " <> T.unpack contentT+ writeFileUtf8 (toFilePath outFile) contentT++extractSnipContent :: String -> [ParsedLine] -> RIO App [Text]+extractSnipContent _ [] = return []+extractSnipContent _ [EmptyLine] = return []+extractSnipContent lbl (EmptyLine:rest) = + ("" :) <$> extractSnipContent lbl rest+extractSnipContent _ (EndMarkerLine Nothing:_) = return []+extractSnipContent lbl (EndMarkerLine (Just endLbl):rest)+ | lbl == toFilePath endLbl = return []+ | otherwise = extractSnipContent lbl rest+extractSnipContent lbl (StartMarkerLine _:rest) = extractSnipContent lbl rest+extractSnipContent lbl parsedLines@(ContentLine _:_) = do+ App{appDoTrim} <- ask+ (contentLines appDoTrim <>) <$> extractSnipContent lbl rest+ where+ contentLines doTrim = reduceContent doTrim commonLeadingWS <$> contentSpecs+ reduceContent False _ (lineLeadWS, content) = expandWS lineLeadWS <> content+ reduceContent True comLeadWS (lineLeadWS, content) =+ case (comLeadWS, lineLeadWS) of+ (_,NoLeadingWS) -> content+ (NoLeadingWS,_) -> expandWS lineLeadWS <> content+ (LeadingSpaces _, LeadingTabs _) -> content+ (LeadingTabs _, LeadingSpaces _) -> content+ (LeadingSpaces comCnt, LeadingSpaces lineCnt) ->+ expandWS (LeadingSpaces $ lineCnt - min lineCnt comCnt) <> content+ (LeadingTabs comCnt, LeadingTabs lineCnt) ->+ expandWS (LeadingTabs $ lineCnt - min lineCnt comCnt) <> content+ expandWS = \case+ NoLeadingWS -> ""+ LeadingSpaces cnt -> T.pack $ L.replicate (fromIntegral cnt) ' '+ LeadingTabs cnt -> T.pack $ L.replicate (fromIntegral cnt) '\t'+ commonLeadingWS = fromMaybe NoLeadingWS $+ foldr + (\item memo ->+ case (item,memo) of+ ((NoLeadingWS,content), _) -> + if T.null content then+ memo+ else + Just NoLeadingWS+ (_, Just NoLeadingWS) -> Just NoLeadingWS+ ((lead,_), Nothing) -> Just lead+ ((LeadingTabs lineCnt,_), Just (LeadingTabs comCnt)) ->+ Just (LeadingTabs $ min lineCnt comCnt)+ ((LeadingSpaces lineCnt,_), Just (LeadingSpaces comCnt)) ->+ Just (LeadingSpaces $ min lineCnt comCnt)+ ((LeadingTabs _,_), Just (LeadingSpaces _)) ->+ Just NoLeadingWS+ ((LeadingSpaces _,_), Just (LeadingTabs _)) ->+ Just NoLeadingWS+ )+ Nothing+ contentSpecs+ contentSpecs = catMaybes $ mayContent <$> contentParsedLines+ (contentParsedLines, rest) = L.span isContentLine parsedLines+ mayContent = \case+ EmptyLine -> Just (NoLeadingWS, "")+ ContentLine spec -> Just spec+ _ -> Nothing+ isContentLine = isJust . mayContent
+ src/Types.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE NoImplicitPrelude #-}+module Types+ ( App (..)+ , Options (..)+ ) where++import RIO+import Path++-- | Command line arguments+data Options = Options+ { optsRecursive :: Bool+ , optsPaths :: [FilePath]+ , optsBaseDir :: Maybe (SomeBase Dir)+ , optsOutputDir :: Maybe (SomeBase Dir)+ , optsTrim :: Bool+ , optsVerbose :: Bool+ }++data App = App+ { appLogFunc :: LogFunc+ , appDoTrim :: Bool+ , appBaseDir :: Path Abs Dir+ , appFiles :: Set (Path Rel File)+ , appOutputDir :: Path Abs Dir+ }++instance HasLogFunc App where+ logFuncL :: Lens' App LogFunc+ logFuncL = lens appLogFunc+ (\it new -> it { appLogFunc = new })
+ src/Util.hs view
@@ -0,0 +1,11 @@+{-# LANGUAGE NoImplicitPrelude #-}+-- | Silly utility module, used to demonstrate how to write a test+-- case.+module Util+ ( plus2+ ) where++import RIO++plus2 :: Int -> Int+plus2 = (+ 2)
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+ test/UtilSpec.hs view
@@ -0,0 +1,14 @@+{-# LANGUAGE NoImplicitPrelude #-}+module UtilSpec (spec) where++import Import+import Util+import Test.Hspec+import Test.Hspec.QuickCheck++spec :: Spec+spec = do+ describe "plus2" $ do+ it "basic check" $ plus2 0 `shouldBe` 2+ it "overflow" $ plus2 maxBound `shouldBe` minBound + 1+ prop "minus 2" $ \i -> plus2 i - 2 `shouldBe` i
+ the-snip.cabal view
@@ -0,0 +1,224 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.35.0.+--+-- see: https://github.com/sol/hpack++name: the-snip+version: 0.0.0.1+synopsis: Command line tool for extracting demarcated snippets from text files.+description: Please see the README on Github at <https://github.com/robertfischer/the-snip#readme>+category: Tool+homepage: https://github.com/RobertFischer/the-snip#readme+bug-reports: https://github.com/RobertFischer/the-snip/issues+author: Robert Fischer+maintainer: smokejumperit@gmail.com+copyright: Copyright (c) 2022 Robert Fischer+license: BSD3+license-file: LICENSE+build-type: Simple+extra-source-files:+ README.md++source-repository head+ type: git+ location: https://github.com/RobertFischer/the-snip++library+ exposed-modules:+ Import+ Run+ Types+ Util+ other-modules:+ Paths_the_snip+ hs-source-dirs:+ src+ default-extensions:+ BangPatterns+ BinaryLiterals+ ConstraintKinds+ DataKinds+ DefaultSignatures+ DeriveDataTypeable+ DeriveFoldable+ DeriveFunctor+ DeriveGeneric+ DeriveTraversable+ DerivingStrategies+ DoAndIfThenElse+ EmptyDataDecls+ ExistentialQuantification+ FlexibleContexts+ FlexibleInstances+ FunctionalDependencies+ GADTs+ GeneralizedNewtypeDeriving+ ImportQualifiedPost+ InstanceSigs+ KindSignatures+ LambdaCase+ MultiParamTypeClasses+ MultiWayIf+ NamedFieldPuns+ NoImplicitPrelude+ OverloadedStrings+ PartialTypeSignatures+ PatternGuards+ PolyKinds+ QuasiQuotes+ RankNTypes+ RecordWildCards+ ScopedTypeVariables+ StandaloneDeriving+ TemplateHaskell+ TupleSections+ TypeApplications+ TypeFamilies+ TypeFamilyDependencies+ TypeOperators+ TypeSynonymInstances+ ViewPatterns+ ghc-options: -Wall -Wcompat -Werror=incomplete-patterns -Werror=incomplete-record-updates -Werror=incomplete-uni-patterns -Werror=missing-exported-signatures -Werror=missing-fields -Werror=partial-fields -Werror=unused-do-bind -Werror=warnings-deprecations -Widentities -Wmissed-specializations -Wnoncanonical-monad-instances -Wredundant-constraints -Wno-tabs -Wno-type-defaults -Wno-unused-packages -Wunused-type-patterns -fpedantic-bottoms -feager-blackholing -fexcess-precision -flate-dmd-anal -fregs-iterative -fspecialise-aggressively -flate-specialise -fstatic-argument-transformation+ build-depends:+ attoparsec+ , base >=4.11 && <10+ , bytestring+ , path+ , path-io+ , rio >=0.1.12.0+ , text+ , unix+ default-language: Haskell2010++executable snip+ main-is: Main.hs+ other-modules:+ Paths_the_snip+ hs-source-dirs:+ app+ default-extensions:+ BangPatterns+ BinaryLiterals+ ConstraintKinds+ DataKinds+ DefaultSignatures+ DeriveDataTypeable+ DeriveFoldable+ DeriveFunctor+ DeriveGeneric+ DeriveTraversable+ DerivingStrategies+ DoAndIfThenElse+ EmptyDataDecls+ ExistentialQuantification+ FlexibleContexts+ FlexibleInstances+ FunctionalDependencies+ GADTs+ GeneralizedNewtypeDeriving+ ImportQualifiedPost+ InstanceSigs+ KindSignatures+ LambdaCase+ MultiParamTypeClasses+ MultiWayIf+ NamedFieldPuns+ NoImplicitPrelude+ OverloadedStrings+ PartialTypeSignatures+ PatternGuards+ PolyKinds+ QuasiQuotes+ RankNTypes+ RecordWildCards+ ScopedTypeVariables+ StandaloneDeriving+ TemplateHaskell+ TupleSections+ TypeApplications+ TypeFamilies+ TypeFamilyDependencies+ TypeOperators+ TypeSynonymInstances+ ViewPatterns+ ghc-options: -Wall -Wcompat -Werror=incomplete-patterns -Werror=incomplete-record-updates -Werror=incomplete-uni-patterns -Werror=missing-exported-signatures -Werror=missing-fields -Werror=partial-fields -Werror=unused-do-bind -Werror=warnings-deprecations -Widentities -Wmissed-specializations -Wnoncanonical-monad-instances -Wredundant-constraints -Wno-tabs -Wno-type-defaults -Wno-unused-packages -Wunused-type-patterns -fpedantic-bottoms -feager-blackholing -fexcess-precision -flate-dmd-anal -fregs-iterative -fspecialise-aggressively -flate-specialise -fstatic-argument-transformation -threaded -rtsopts -with-rtsopts=-N+ build-depends:+ attoparsec+ , base >=4.11 && <10+ , bytestring+ , optparse-simple+ , path+ , path-io+ , rio >=0.1.12.0+ , text+ , the-snip+ , unix+ default-language: Haskell2010++test-suite the-snip-test+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ other-modules:+ UtilSpec+ Paths_the_snip+ hs-source-dirs:+ test+ default-extensions:+ BangPatterns+ BinaryLiterals+ ConstraintKinds+ DataKinds+ DefaultSignatures+ DeriveDataTypeable+ DeriveFoldable+ DeriveFunctor+ DeriveGeneric+ DeriveTraversable+ DerivingStrategies+ DoAndIfThenElse+ EmptyDataDecls+ ExistentialQuantification+ FlexibleContexts+ FlexibleInstances+ FunctionalDependencies+ GADTs+ GeneralizedNewtypeDeriving+ ImportQualifiedPost+ InstanceSigs+ KindSignatures+ LambdaCase+ MultiParamTypeClasses+ MultiWayIf+ NamedFieldPuns+ NoImplicitPrelude+ OverloadedStrings+ PartialTypeSignatures+ PatternGuards+ PolyKinds+ QuasiQuotes+ RankNTypes+ RecordWildCards+ ScopedTypeVariables+ StandaloneDeriving+ TemplateHaskell+ TupleSections+ TypeApplications+ TypeFamilies+ TypeFamilyDependencies+ TypeOperators+ TypeSynonymInstances+ ViewPatterns+ ghc-options: -Wall -Wcompat -Werror=incomplete-patterns -Werror=incomplete-record-updates -Werror=incomplete-uni-patterns -Werror=missing-exported-signatures -Werror=missing-fields -Werror=partial-fields -Werror=unused-do-bind -Werror=warnings-deprecations -Widentities -Wmissed-specializations -Wnoncanonical-monad-instances -Wredundant-constraints -Wno-tabs -Wno-type-defaults -Wno-unused-packages -Wunused-type-patterns -fpedantic-bottoms -feager-blackholing -fexcess-precision -flate-dmd-anal -fregs-iterative -fspecialise-aggressively -flate-specialise -fstatic-argument-transformation -threaded -rtsopts -with-rtsopts=-N+ build-depends:+ attoparsec+ , base >=4.11 && <10+ , bytestring+ , hspec+ , path+ , path-io+ , rio >=0.1.12.0+ , text+ , the-snip+ , unix+ default-language: Haskell2010