scripths 0.5.3.0 → 0.5.3.1
raw patch · 4 files changed
+171/−9 lines, 4 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
Files
- CHANGELOG.md +3/−0
- scripths.cabal +1/−1
- src/ScriptHs/Render.hs +36/−6
- test/Test/Render.hs +131/−2
CHANGELOG.md view
@@ -1,6 +1,9 @@ # Revision history for scripths +## 0.5.3.1 -- 2026-06-28+* Merge consecutive blank lines.+ ## 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).
scripths.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: scripths-version: 0.5.3.0+version: 0.5.3.1 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/
src/ScriptHs/Render.hs view
@@ -36,7 +36,7 @@ unRewriteSplice, ) where -import Data.Bifunctor (second)+import Data.Bifunctor (first, second) import Data.Char (isAsciiLower, isAsciiUpper, isDigit) import Data.List (intercalate) import Data.Maybe@@ -257,8 +257,27 @@ , "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.+-} hasTopLevelEquals :: Text -> Bool-hasTopLevelEquals t = " = " `T.isInfixOf` t || T.isSuffixOf " =" t+hasTopLevelEquals = go (0 :: Int) . T.unpack+ 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 [] = [] isCommentText :: Text -> Bool isCommentText t = "--" `T.isPrefixOf` T.stripStart t@@ -271,11 +290,20 @@ mergePieces :: [Piece] -> [Piece] mergePieces (PUnit KComment l1 : PUnit k l2 : rest) | k /= KComment = mergePieces (PUnit k (l1 ++ l2) : rest)-mergePieces (PUnit KDeclaration l1 : PUnit KDeclaration l2 : rest) =- mergePieces (PUnit KDeclaration (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 [] = [] +declContinuation :: [Piece] -> Maybe ([Line], [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 _ _ = Nothing+ piecesToBlocks :: [Piece] -> [Block] piecesToBlocks = map pieceToBlock . mergePieces @@ -308,8 +336,10 @@ 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) : (_, PUnit KDeclaration l2) : rest) =- mergeNumberedPieces ((i, PUnit KDeclaration (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) mergeNumberedPieces (p : rest) = p : mergeNumberedPieces rest mergeNumberedPieces [] = []
test/Test/Render.hs view
@@ -200,8 +200,120 @@ , HaskellLine "y = 2" ] length (splitBlocks result) @?= 2+ , testCase "type sig + single blank + binding stay in one block" $ do+ let result =+ toGhciScript+ [ HaskellLine "f :: Int -> Int"+ , Blank+ , HaskellLine "f x = x + 1"+ ]+ length (splitBlocks result) @?= 1+ , testCase "two value bindings + single blank stay in one block" $ do+ let result =+ toGhciScript+ [ HaskellLine "x = 1"+ , Blank+ , HaskellLine "y = 2"+ ]+ length (splitBlocks result) @?= 1+ , testCase "data decl + single blank + instance stay in one block" $ do+ let result =+ toGhciScript+ [ HaskellLine "data User = User { name :: String }"+ , Blank+ , HaskellLine "instance Show User where"+ , HaskellLine " show u = name u"+ ]+ length (splitBlocks result) @?= 1+ , testCase "decl + blank + comment + decl stay in one block" $ do+ -- Mutually recursive decls separated by a commented paragraph+ -- must share a block or the forward reference fails in GHCi.+ let result =+ toGhciScript+ [ HaskellLine "parseFactor = parens parseExpr"+ , Blank+ , HaskellLine "-- Level 1: Addition"+ , HaskellLine "parseExpr = chainl1 parseFactor op"+ ]+ length (splitBlocks result) @?= 1+ , testCase "decl + comment + decl (no blank) stay in one block" $ do+ let result =+ toGhciScript+ [ HaskellLine "f = g + 1"+ , HaskellLine "-- helper"+ , HaskellLine "g = 2"+ ]+ length (splitBlocks result) @?= 1+ , testCase "blank/comment interleave between decls stays merged" $ do+ let result =+ toGhciScript+ [ HaskellLine "numberParser = lexeme digits"+ , Blank+ , HaskellLine "-- 2. Expression Parsing"+ , Blank+ , HaskellLine "-- Level 3: Factors"+ , HaskellLine "parseFactor = numberParser"+ ]+ length (splitBlocks result) @?= 1+ , testCase "double blank before a comment + decl still breaks" $ do+ let result =+ toGhciScript+ [ HaskellLine "x = 1"+ , Blank+ , Blank+ , HaskellLine "-- fresh section"+ , HaskellLine "y = 2"+ ]+ length (splitBlocks result) @?= 2+ , testCase "merged gap lines are preserved inside the block" $ do+ -- The blank and comment lines stay in the merged block so a+ -- LINE pragma keeps every diagnostic on its original line.+ let result =+ toGhciScript+ [ HaskellLine "f :: Int"+ , Blank+ , HaskellLine "-- note"+ , HaskellLine "f = 2"+ ]+ splitBlocks result @?= [["f :: Int", "", "-- note", "f = 2"]] ] , testGroup+ "Equals inside literals is not a binding"+ [ testCase "print with ' = ' inside a string stays an action" $ do+ -- Measured live: labelled prints classified as declarations+ -- merge into one block and GHCi juxtaposes them into garbage.+ let result =+ toGhciScript+ [ HaskellLine+ "print $ \"2 + 3 * 4 = \" ++ show (evalExpr \"2 + 3 * 4\")"+ , HaskellLine+ "print $ \"(2 + 3) * 4 = \" ++ show (evalExpr \"(2 + 3) * 4\")"+ ]+ length (splitBlocks result) @?= 2+ , testCase "record update ' = ' inside braces stays an action" $ do+ let result =+ toGhciScript+ [ HaskellLine "print cfg { retries = 3 }"+ , HaskellLine "print cfg { retries = 4 }"+ ]+ length (splitBlocks result) @?= 2+ , testCase "a real binding whose RHS is a string still merges" $ do+ let result =+ toGhciScript+ [ HaskellLine "label = \"x = y\""+ , Blank+ , HaskellLine "caption = label ++ \"!\""+ ]+ length (splitBlocks result) @?= 1+ , testCase "trailing '=' at depth 0 is still a binding head" $ do+ let result =+ toGhciScript+ [ HaskellLine "longDef ="+ , HaskellLine " \"value\""+ ]+ length (splitBlocks result) @?= 1+ ]+ , testGroup "Mixed block splitting" [ testCase "pure definitions stay grouped" $ do let result =@@ -300,14 +412,17 @@ , HaskellLine "do" , HaskellLine " let testPrimes = primesUpTo 10" ]+ -- The three commented declaration sections merge into ONE+ -- block (they may be mutually recursive); the trailing do+ -- action stays its own block. let blocks = splitBlocks result assertBool- ( "expected 4 blocks (one per logical section), got "+ ( "expected 2 blocks (merged decls + do action), got " ++ show (length blocks) ++ ": " ++ show blocks )- (length blocks == 4)+ (length blocks == 2) ] , testGroup "Comments"@@ -385,6 +500,20 @@ ] length (splitBlocks result) @?= 1 linePragmasIn result @?= ["{-# LINE 5 \"cell\" #-}"]+ , testCase "decls merged across a blank + comment gap keep one true pragma" $ do+ let result =+ toGhciScriptTagged+ "cell"+ [ (5, HaskellLine "f = g + 1")+ , (6, Blank)+ , (7, HaskellLine "-- helper")+ , (8, HaskellLine "g = 2")+ ]+ length (splitBlocks result) @?= 1+ linePragmasIn result @?= ["{-# LINE 5 \"cell\" #-}"]+ -- Gap lines preserved: g's diagnostic must still map to line 8.+ splitBlocks result+ @?= [["{-# LINE 5 \"cell\" #-}", "f = g + 1", "", "-- helper", "g = 2"]] , testCase "comment attaching forward uses the comment's line number" $ do let result = toGhciScriptTagged