packages feed

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 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