packages feed

scripths 0.5.4.1 → 0.5.4.2

raw patch · 10 files changed

+205/−110 lines, 10 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- ScriptHs.Render: classify :: Text -> [Text] -> Kind
+ ScriptHs.Lex: isSymbolChar :: Char -> Bool
+ ScriptHs.Lex: maskLiterals :: Text -> Text
+ ScriptHs.Lex: maskNested :: Text -> Text
+ ScriptHs.Lex: stripLineComment :: Text -> Text
+ ScriptHs.Parser: instance GHC.Base.Monoid ScriptHs.Parser.CabalMeta
+ ScriptHs.Parser: instance GHC.Base.Semigroup ScriptHs.Parser.CabalMeta
+ ScriptHs.Render: dedup :: [Text] -> [Text]
+ ScriptHs.Render: languagePragma :: Text -> Text

Files

CHANGELOG.md view
@@ -1,9 +1,19 @@ # Revision history for scripths  +## 0.5.4.2 -- 2026-08-13+* fixes heuristic to parse binds.+* declarations separated by a single blank line and/or comment lines merge into one `:{ … :}` block, so mutually recursive definitions with commented section headers load together. A double blank line still splits blocks.+ ## 0.5.3.1 -- 2026-06-28 * Merge consecutive blank lines. +## 0.5.3.0 -- 2026-06-23+* **Line-numbered errors**: every rendered block carries a `{-# LINE #-}`+  pragma with its original source line, so GHC diagnostics point at the real+  line in your script or notebook cell instead of a position inside the+  generated GHCi input.+ ## 0.5.2.0 -- 2026-06-16 * **Styling with `RenderOptions`.** You can now have the output quoted vs unquoted   and the code shown or not (code not shown is if you want to export the results only).@@ -11,8 +21,8 @@ * **Blank-line fix**: whitespace-only prose between two code fences no longer   becomes a spurious empty prose segment. -## 0.4.1.0 -- 2056-05-31-* **Custom-prelude support**: auto print (our hook to rout evething but strings to shw)+## 0.4.1.0 -- 2026-05-31+* **Custom-prelude support**: auto print (our hook to route everything but strings to show)   now doesn't assume a prelude. * **Cleaner error rendering** (notebooks): don't leak internals when printing errors. * **UTF-8 output**: captured notebook output is decoded as UTF-8.@@ -33,7 +43,7 @@ * **`scripths --version` / `-v`** prints the scripths version.  ## 0.4.0.1 -- 2026-05-30-* Parse pandoc markdown code fences as haskel code fences.+* Parse pandoc markdown code fences as Haskell code fences.  ## 0.4.0.0 -- 2026-05-29 @@ -101,7 +111,7 @@  ## 0.1.0.1 -- 2026-02-24 -* Fix code new ine behaviour for code fencing.+* Fix code newline behaviour for code fencing.  ## 0.1.0.0 -- YYYY-mm-dd 
README.md view
@@ -24,11 +24,13 @@ ## CLI Usage  ```-scripths [-o FILE | --output=FILE] [-i | --in-place] [-p DIR | --package DIR]... [--no-local-project] [-h | --help] [-v | --version] <script>+scripths [-o FILE | --output=FILE] [-i | --in-place] [-p DIR | --package DIR]... [--no-local-project] [--code-style=display|remove] [--output-style=quoted|raw] [-h | --help] [-v | --version] <script> ```  When `-o` / `--output` is provided for Markdown files, the result is written to that path. Otherwise it is printed to stdout. With `-i` / `--in-place` the notebook is rewritten in place: any previously rendered output is stripped and replaced, and re-running is idempotent (it does not accumulate blank lines). `-i` is only valid for `.md` / `.markdown` notebooks and cannot be combined with `-o`. +Rendered notebooks can be styled: `--code-style=remove` omits the code fences from the output (results only, e.g. for exporting a report), and `--output-style=raw` emits captured output verbatim instead of as a block quote. The defaults are `display` and `quoted`.+ The file extension determines the mode. `.ghci` / `hs` files are parsed and executed as a standalone GHCi script. `.md` / `.markdown` files are processed as a notebook with captured output. Run `scripths --help` for the full option and directive list, or `scripths --version` to print the version.  ## Version tag@@ -126,7 +128,7 @@ -- cabal: source-repository-package: https://github.com/owner/repo v1.2.3 ``` -The recognised keys are `build-depends`, `default-extensions`, `ghc-options`, `packages` (extra local package directories, relative to the script), and `source-repository-package`. An unrecognised key is reported as a warning.+The recognised keys are `build-depends`, `default-extensions`, `ghc-options`, `packages` (extra local package directories, relative to the script), `source-repository-package`, `extra-lib-dirs`, and `extra-include-dirs`. An unrecognised key is reported as a warning.  `OverloadedStrings` is enabled in every scripths repl by default, so string literals work directly as `Text` / `ByteString`; add any further extensions with `default-extensions`. 
app/Main.hs view
@@ -138,7 +138,7 @@         , "  -h, --help                show this help"         , "  -v, --version             show the scripths version"         , "  --code-style=display|remove    control how code fences should appear after processing (default: display)"-        , "  --output-style=quoted|raw      control how evaluted code is outputed (default: quoted)"+        , "  --output-style=quoted|raw      control how evaluated output is rendered (default: quoted)"         , ""         , "Files may carry a first-line version tag recording the scripths that wrote"         , "them ('-- scripths: X' in scripts, '<!-- scripths: X -->' in notebooks); a"@@ -150,6 +150,8 @@         , "  -- cabal: ghc-options: -Wall"         , "  -- cabal: packages: ../sibling-pkg        (extra local package dirs)"         , "  -- cabal: source-repository-package: <git-url> <ref> [subdir]"+        , "  -- cabal: extra-lib-dirs: /opt/lib         (native library search paths)"+        , "  -- cabal: extra-include-dirs: /opt/include (C header search paths)"         , ""         , "Each code block should end in a single bare expression to be auto-printed;"         , "a block ending in a '<-' bind prints nothing — repeat the bound name on a"
scripths.cabal view
@@ -1,6 +1,6 @@ cabal-version:      3.0 name:               scripths-version:            0.5.4.1+version:            0.5.4.2 synopsis:           GHCi scripts for standalone execution and Markdown documentation. description:        GHCi scripts for standalone execution (with dependency resolution) and Markdown documentation (produces inline output). homepage:           https://www.datahaskell.org/@@ -25,6 +25,7 @@     import:           warnings     exposed-modules:  ScriptHs.CLI.Types,                       ScriptHs.Compiled,+                      ScriptHs.Lex,                       ScriptHs.Markdown,                       ScriptHs.Notebook,                       ScriptHs.Parser,
src/ScriptHs/Compiled.hs view
@@ -28,6 +28,8 @@ import ScriptHs.Render (     Kind (..),     Piece (..),+    dedup,+    languagePragma,     linePragma,     lineText,     numberedPieces,@@ -159,14 +161,3 @@         , k `elem` [KComment, KDeclaration, KTHSplice]         ]     declLines = T.lines . unRewriteSplice . T.intercalate "\n" . map lineText--languagePragma :: Text -> Text-languagePragma ext = "{-# LANGUAGE " <> ext <> " #-}"--dedup :: [Text] -> [Text]-dedup = go []-  where-    go _ [] = []-    go seen (x : xs)-        | x `elem` seen = go seen xs-        | otherwise = x : go (x : seen) xs
+ src/ScriptHs/Lex.hs view
@@ -0,0 +1,74 @@+{-# LANGUAGE OverloadedStrings #-}++{- | Length-preserving lexical masking for single source lines. The+classifiers in "ScriptHs.Render" decide what a line is from tokens at the+statement level; masking literal contents, bracketed text, and trailing+comments first lets each check stay a plain "Data.Text" search.+-}+module ScriptHs.Lex (+    maskLiterals,+    maskNested,+    stripLineComment,+    isSymbolChar,+) where++import Data.Text (Text)+import qualified Data.Text as T++{- | Blank out string and character literals (delimiters included) with+spaces. A single quote counts as a literal only when one closes it right+after the (possibly escaped) character; otherwise it is a prime in an+identifier like @xs'@.+-}+maskLiterals :: Text -> Text+maskLiterals = T.pack . go . T.unpack+  where+    go [] = []+    go ('"' : cs) = ' ' : inString cs+    go ('\'' : '\\' : c : cs)+        | (esc, '\'' : rest) <- break (== '\'') cs =+            blank (3 + length esc + 1) ++ go rest+        | otherwise = '\'' : go ('\\' : c : cs)+    go ('\'' : c : '\'' : cs)+        | c /= '\\' = blank 3 ++ go cs+    go (c : cs) = c : go cs+    inString [] = []+    inString ('\\' : _ : cs) = ' ' : ' ' : inString cs+    inString ('"' : cs) = ' ' : go cs+    inString (_ : cs) = ' ' : inString cs+    blank n = replicate n ' '++{- | 'maskLiterals', then blank out everything inside (and including)+brackets, leaving only statement-level source visible.+-}+maskNested :: Text -> Text+maskNested = T.pack . go (0 :: Int) . T.unpack . maskLiterals+  where+    go _ [] = []+    go d (c : cs)+        | c `elem` ("([{" :: String) = ' ' : go (d + 1) cs+        | c `elem` (")]}" :: String) = ' ' : go (max 0 (d - 1)) cs+        | d > 0 = ' ' : go d cs+        | otherwise = c : go d cs++{- | Drop a trailing @--@ line comment (and anything after it). Honouring+maximal munch, a dash run extended by other symbol characters (@-->@, @--|@)+is an operator, not a comment; a @--@ inside a literal is text.+-}+stripLineComment :: Text -> Text+stripLineComment t = maybe t (\i -> T.take i t) (commentIndex 0 (maskLiterals t))+  where+    commentIndex i s = case T.uncons s of+        Nothing -> Nothing+        Just (c, rest)+            | isSymbolChar c ->+                let run = T.takeWhile isSymbolChar s+                    n = T.length run+                 in if n >= 2 && T.all (== '-') run+                        then Just i+                        else commentIndex (i + n) (T.drop n s)+            | otherwise -> commentIndex (i + 1) rest++-- | Characters that can extend a Haskell operator lexeme (maximal munch).+isSymbolChar :: Char -> Bool+isSymbolChar c = c `elem` ("!#$%&*+./<=>?@\\^|-~:" :: String)
src/ScriptHs/Parser.hs view
@@ -88,6 +88,22 @@     }     deriving (Show, Eq) +instance Semigroup CabalMeta where+    a <> b =+        CabalMeta+            { metaDeps = metaDeps a <> metaDeps b+            , metaExts = metaExts a <> metaExts b+            , metaGhcOptions = metaGhcOptions a <> metaGhcOptions b+            , metaExtraLibDirs = metaExtraLibDirs a <> metaExtraLibDirs b+            , metaExtraIncludeDirs = metaExtraIncludeDirs a <> metaExtraIncludeDirs b+            , metaPackages = metaPackages a <> metaPackages b+            , metaSourceRepos = metaSourceRepos a <> metaSourceRepos b+            , metaUnknownKeys = metaUnknownKeys a <> metaUnknownKeys b+            }++instance Monoid CabalMeta where+    mempty = CabalMeta [] [] [] [] [] [] [] []+ {- | A git @source-repository-package@ pin, declared with  @@@ -178,18 +194,9 @@     | RawCompile CompileDirective     | RawCode Line +-- | Field-wise concatenation of every directive's contribution. mergeMetas :: [CabalMeta] -> CabalMeta-mergeMetas ms =-    CabalMeta-        { metaDeps = concatMap metaDeps ms-        , metaExts = concatMap metaExts ms-        , metaGhcOptions = concatMap metaGhcOptions ms-        , metaExtraLibDirs = concatMap metaExtraLibDirs ms-        , metaExtraIncludeDirs = concatMap metaExtraIncludeDirs ms-        , metaPackages = concatMap metaPackages ms-        , metaSourceRepos = concatMap metaSourceRepos ms-        , metaUnknownKeys = concatMap metaUnknownKeys ms-        }+mergeMetas = mconcat  parseLine :: Text -> RawLine parseLine line@@ -230,17 +237,7 @@                 other -> emptyCabal{metaUnknownKeys = [other]}         _ -> Nothing   where-    emptyCabal =-        CabalMeta-            { metaDeps = []-            , metaExts = []-            , metaGhcOptions = []-            , metaExtraLibDirs = []-            , metaExtraIncludeDirs = []-            , metaPackages = []-            , metaSourceRepos = []-            , metaUnknownKeys = []-            }+    emptyCabal = mempty :: CabalMeta  {- | Parse a @source-repository-package@ value: whitespace-separated @\<location\> \<ref\> [subdir]@ (not comma-separated like the other directives).
src/ScriptHs/Render.hs view
@@ -32,10 +32,15 @@     Piece (..),     toPieces,     mergePieces,-    classify,-    bindStatementBody,     lineText,     unRewriteSplice,++    -- * Shared rendering helpers+    dedup,+    languagePragma,++    -- * Internals exposed for testing+    bindStatementBody, ) where  import Data.Bifunctor (first, second)@@ -44,6 +49,7 @@ import Data.Maybe import Data.Text (Text) import qualified Data.Text as T+import ScriptHs.Lex (isSymbolChar, maskNested, stripLineComment) import ScriptHs.Parser (CabalMeta (..), Line (..), SourceRepoPin (..))  data Block@@ -58,8 +64,9 @@ * A run of @--@ comment lines forms a 'KComment' unit that attaches   forward to the next non-comment unit. * A type signature, value binding, or clause-head-with-guards forms a-  'KDeclaration' unit; consecutive declarations merge into a single-  @:{ … :}@ block.+  'KDeclaration' unit; declarations separated by at most a single blank+  line and\/or comment lines merge into a single @:{ … :}@ block (a+  double blank line splits). * A monadic bind (@<-@ on the lead line) forms a 'KIOBind' unit; each   such unit is its own block. * A Template Haskell splice (@$(…)@ or the rewritten @_ = (); …@ form)@@ -81,6 +88,7 @@ toGhciScriptTagged tag =     T.unlines . concatMap (renderBlockTagged tag) . numberedBlocks +-- | The statement-level classification of a logical unit of lines. data Kind     = KComment     | KDeclaration@@ -89,6 +97,7 @@     | KTHSplice     deriving (Show, Eq) +-- | One element of a parsed line stream: a classified unit or a bare line. data Piece     = PBlank     | PGhciCommand Text@@ -97,6 +106,7 @@     | PUnit Kind [Line]     deriving (Show) +-- | Group lines into logical units (lead + continuations) and classify each. toPieces :: [Line] -> [Piece] toPieces [] = [] toPieces (Blank : rest) = PBlank : toPieces rest@@ -150,6 +160,7 @@ isBlankLine Blank = True isBlankLine _ = False +-- | The 'Kind' of a unit, from its lead line and continuation lines. classify :: Text -> [Text] -> Kind classify leadText contTexts     | isTHSplice leadText = KTHSplice@@ -164,7 +175,7 @@  {- | A statement binds monadically when @<-@ separates a pattern from an expression at the STATEMENT level. Defined through 'bindStatementBody' so the-classifier and the extractor can never disagree about which arrow binds.+classifier and the body extraction can never disagree about which arrow binds. -} isIOBindLead :: Text -> Bool isIOBindLead = isJust . bindStatementBody@@ -172,34 +183,23 @@ {- | The expression a monadic bind runs, with its pattern dropped: the text after the statement-level @<-@, or 'Nothing' when the line binds nothing. An arrow nested inside brackets belongs to a list comprehension or an inline-@do@, and one inside a literal is text; neither binds a name a caller can use.+@do@, one inside a literal or comment is text, and one extended by symbol+characters (@<->@) is an operator; none binds a name. -} bindStatementBody :: Text -> Maybe Text-bindStatementBody = scan (0 :: Int)+bindStatementBody t = body <$> findArrow 0   where-    scan depth t = case T.uncons t of-        Nothing -> Nothing-        Just ('"', rest) -> scan depth (skipString rest)-        Just ('\'', rest) -> scan depth (skipChar rest)-        Just ('<', rest)-            | depth == 0-            , Just body <- T.stripPrefix "-" rest ->-                Just (T.strip body)-        Just (c, rest)-            | c `elem` ("([{" :: String) -> scan (depth + 1) rest-            | c `elem` (")]}" :: String) -> scan (depth - 1) rest-            | otherwise -> scan depth rest-    skipString t = case T.uncons t of-        Nothing -> t-        Just ('\\', rest) -> skipString (T.drop 1 rest)-        Just ('"', rest) -> rest-        Just (_, rest) -> skipString rest-    -- A quote opens a character literal only when one closes it right after-    -- the character; otherwise it is a prime in an identifier like @xs'@.-    skipChar t = case T.uncons t of-        Just ('\\', rest) -> T.drop 1 (T.dropWhile (/= '\'') rest)-        Just (_, rest) | "'" `T.isPrefixOf` rest -> T.drop 1 rest-        _ -> t+    stripped = stripLineComment t+    masked = maskNested stripped+    body i = T.strip (T.drop (i + 2) stripped)+    findArrow i = case T.breakOn "<-" (T.drop i masked) of+        (_, "") -> Nothing+        (pre, _) ->+            let j = i + T.length pre+             in if standaloneArrow j then Just j else findArrow (j + 2)+    standaloneArrow j = not (symbolAt (j - 1)) && not (symbolAt (j + 2))+    symbolAt k =+        k >= 0 && k < T.length masked && isSymbolChar (T.index masked k)  isDeclaration :: Text -> [Text] -> Bool isDeclaration leadText contTexts =@@ -295,51 +295,40 @@                , "infixr"                ] -{- | A standalone @=@ at bracket depth 0 outside string literals — the-discriminator between a value binding and an expression whose string content-happens to contain @\" = \"@ (a labelled print) or a record update's field-assignment.+{- | A standalone @=@ at bracket depth 0 outside literals and comments — the+discriminator between a value binding and an expression whose nested text+happens to contain an @=@ (a labelled print, a record update's field+assignment, a trailing comment). -} hasTopLevelEquals :: Text -> Bool-hasTopLevelEquals = go (0 :: Int) . T.unpack+hasTopLevelEquals t =+    " = " `T.isInfixOf` masked || " =" `T.isSuffixOf` T.stripEnd masked   where-    go 0 (' ' : '=' : rest)-        | null rest || head rest == ' ' = True-    go d ('\\' : _ : cs) = go d cs-    go d ('"' : cs) = go d (dropString cs)-    go d (c : cs)-        | c `elem` ("([{" :: String) = go (d + 1) cs-        | c `elem` (")]}" :: String) = go (max 0 (d - 1)) cs-        | otherwise = go d cs-    go _ [] = False-    dropString ('\\' : _ : cs) = dropString cs-    dropString ('"' : cs) = cs-    dropString (_ : cs) = dropString cs-    dropString [] = []+    masked = maskNested (stripLineComment t)  isCommentText :: Text -> Bool isCommentText t = "--" `T.isPrefixOf` T.stripStart t  {- | Normalize a piece stream: attach each comment unit forward onto the-following non-comment unit, and merge runs of adjacent declarations into a-single unit. Shared by 'toGhciScript' (block wrapping) and 'toModule'-(bucketing) so both see identical grouping.+following non-comment unit, and merge declarations separated by at most a+single blank line and\/or comment lines into a single unit. Shared by+'toGhciScript' (block wrapping) and 'toModule' (bucketing) so both see+identical grouping. Defined through 'mergeNumberedPieces' so the two can+never diverge. -} mergePieces :: [Piece] -> [Piece]-mergePieces (PUnit KComment l1 : PUnit k l2 : rest)-    | k /= KComment = mergePieces (PUnit k (l1 ++ l2) : rest)-mergePieces (PUnit KDeclaration l1 : rest)-    | Just (l2, rest') <- declContinuation rest =-        mergePieces (PUnit KDeclaration (l1 ++ l2) : rest')-mergePieces (p : rest) = p : mergePieces rest-mergePieces [] = []+mergePieces = map snd . mergeNumberedPieces . zip [0 ..] -declContinuation :: [Piece] -> Maybe ([Line], [Piece])+{- | The declaration unit continuing the current one across at most a single+blank line and\/or comment runs; the gap lines are kept in the merged unit so+line counts (and any LINE pragma) stay true.+-}+declContinuation :: [(Int, Piece)] -> Maybe ([Line], [(Int, Piece)]) declContinuation = go False   where-    go _ (PUnit KDeclaration l : r) = Just (l, r)-    go False (PBlank : r) = first (Blank :) <$> go True r-    go _ (PUnit KComment lc : r) = first (lc ++) <$> go False r+    go _ ((_, PUnit KDeclaration l) : r) = Just (l, r)+    go False ((_, PBlank) : r) = first (Blank :) <$> go True r+    go _ ((_, PUnit KComment lc) : r) = first (lc ++) <$> go False r     go _ _ = Nothing  piecesToBlocks :: [Piece] -> [Block]@@ -368,16 +357,15 @@     pieceLen (PUnit _ ls) = length ls     pieceLen _ = 1 -{- | 'mergePieces' carrying each piece's first source line; a merge keeps the-earliest line so a tagged block points at where the unit began.+{- | The one merge implementation, carrying each piece's first source line; a+merge keeps the earliest line so a tagged block points at where the unit began. -} mergeNumberedPieces :: [(Int, Piece)] -> [(Int, Piece)] mergeNumberedPieces ((i, PUnit KComment l1) : (_, PUnit k l2) : rest)     | k /= KComment = mergeNumberedPieces ((i, PUnit k (l1 ++ l2)) : rest) mergeNumberedPieces ((i, PUnit KDeclaration l1) : rest)-    | Just (l2, rest') <- declContinuation (map snd rest) =-        mergeNumberedPieces-            ((i, PUnit KDeclaration (l1 ++ l2)) : drop (length rest - length rest') rest)+    | Just (l2, rest') <- declContinuation rest =+        mergeNumberedPieces ((i, PUnit KDeclaration (l1 ++ l2)) : rest') mergeNumberedPieces (p : rest) = p : mergeNumberedPieces rest mergeNumberedPieces [] = [] @@ -415,6 +403,7 @@ linePragma :: Int -> Text -> Text linePragma n tag = "{-# LINE " <> T.pack (show n) <> " \"" <> tag <> "\" #-}" +-- | A line's raw text (empty for 'Blank'). lineText :: Line -> Text lineText Blank = "" lineText (GhciCommand t) = t
test/Test/Render.hs view
@@ -70,6 +70,14 @@                     @?= Just KAction             , testCase "arrow inside a string literal is an action" $                 kindOf "putStrLn \"x <- y\"" @?= Just KAction+            , testCase "arrow behind an escaped quote is still in the string" $+                kindOf "putStrLn \"he said \\\"x <- y\\\"\"" @?= Just KAction+            , testCase "arrow inside a trailing comment is not a bind" $+                kindOf "putStrLn \"done\" -- then: x <- readLn" @?= Just KAction+            , testCase "an operator containing <- is not a bind" $+                kindOf "v1 <-> v2" @?= Just KAction+            , testCase "defining an operator containing <- is not a bind" $+                kindOf "(<->) = undefined" @?= Just KAction             ]         , testGroup             "bindStatementBody: the expression a bind runs"@@ -82,6 +90,10 @@                     @?= Just "pure [y | y <- [1, 2]]"             , testCase "a non-bind has no body" $                 bindStatementBody "print [(x, y) | x <- xs]" @?= Nothing+            , testCase "drops a trailing comment from the body" $+                bindStatementBody "x <- readLn -- setup" @?= Just "readLn"+            , testCase "an operator containing <- has no body" $+                bindStatementBody "v1 <-> v2" @?= Nothing             , -- The classifier and the extractor must never disagree: a line               -- classified KIOBind is exactly one with a statement body.               testCase "agrees with the classifier" $@@ -367,6 +379,23 @@                             , HaskellLine "  \"value\""                             ]                 length (splitBlocks result) @?= 1+            , testCase "bracket in a char literal does not hide a guard's binding" $ do+                let result =+                        toGhciScript+                            [ HaskellLine "describe c"+                            , HaskellLine "  | c == '(' = \"open\""+                            , Blank+                            , HaskellLine "isParen c = describe c /= \"\""+                            ]+                length (splitBlocks result) @?= 1+            , testCase "'=' inside a trailing comment is not a binding" $ do+                let result =+                        toGhciScript+                            [ HaskellLine "y = 2"+                            , Blank+                            , HaskellLine "print y -- expected = 2"+                            ]+                length (splitBlocks result) @?= 2             ]         , testGroup             "Mixed block splitting"@@ -662,7 +691,7 @@             assertBool "indents print 1" (T.isInfixOf "    print 1" txt)         , testCase "cabal-script header includes base + Wno-unused-imports" $ do             let hdr =-                    renderCabalScriptHeader (CabalMeta ["dataframe", "text"] [] [] [] [] [] [] [])+                    renderCabalScriptHeader (mempty{metaDeps = ["dataframe", "text"]})             assertBool "opens block" (T.isInfixOf "{- cabal:" hdr)             assertBool                 "base + deps"
test/Test/Run.hs view
@@ -17,7 +17,7 @@  )  emptyMeta :: CabalMeta-emptyMeta = CabalMeta [] [] [] [] [] [] [] []+emptyMeta = mempty  runTests :: TestTree runTests =