packages feed

asciidoc 0.1 → 0.1.0.1

raw patch · 8 files changed

+242/−41 lines, 8 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

Files

CHANGELOG.md view
@@ -1,6 +1,24 @@ # Revision history for asciidoc-hs +## 0.1.0.1 -- 2026-02-01++  * Fix character escaping issue (#3). Unconstrained forms of+    delimited constructions weren't being allowed after `++`.++  * Fix some footnote parsing issues (#2).++  * Fix parsing of document attributes in the body of the document (#1).+    Previously only those in the header were handled.++  * Change handling of doc attributes. Collect them in state so that+    we can handle attributes defined in the body of the document.++  * Friendlier error message than "endOfInput" on unexpected content+    at the end.++  * Move regression tests to test/regression.+ ## 0.1 -- 2025-11-30 -* Initial release.+  * Initial release. 
asciidoc.cabal view
@@ -1,6 +1,6 @@ cabal-version:      3.4 name:               asciidoc-version:            0.1+version:            0.1.0.1 synopsis:           AsciiDoc parser. description:        A parser for AsciiDoc syntax. license:            BSD-3-Clause@@ -12,6 +12,7 @@ build-type:         Simple extra-doc-files:    CHANGELOG.md extra-source-files: test/asciidoctor/**/*.test+                    test/regression/*.test                     test/feature/**/*.test                     test/feature/include/**/*.adoc 
src/AsciiDoc/Parse.hs view
@@ -19,7 +19,7 @@ import qualified Data.Text as T import qualified Data.Text.Read as TR import Data.Text (Text)-import Data.List (foldl', intersperse)+import Data.List (foldl', intersperse, isPrefixOf) import qualified Data.Attoparsec.Text as A import System.FilePath import Control.Applicative@@ -109,11 +109,15 @@   deriving (Functor, Applicative, Alternative, Monad, MonadPlus,             MonadFail, MonadReader ParserConfig, MonadState ParserState) -newtype ParserState = ParserState+data ParserState = ParserState                      { counterMap :: M.Map Text (CounterType, Int)+                     , docAttrs :: M.Map Text Text                      }         deriving (Show) +defaultDocAttrs :: M.Map Text Text+defaultDocAttrs = M.insert "sectids" "" mempty+ data ParserConfig = ParserConfig                     { filePath :: FilePath                     , blockContexts :: [BlockContext]@@ -129,7 +133,9 @@                                  , blockContexts = []                                  , hardBreaks = False                                  })-                    (ParserState { counterMap = mempty })+                    (ParserState { counterMap = mempty+                                 , docAttrs = defaultDocAttrs+                                 })                     p  parse' :: ParserConfig -> ParserState@@ -137,7 +143,10 @@ parse' cfg st p t =   go $ A.parse (evalStateT ( runReaderT (unP p) cfg ) st) t  where-  go (A.Fail i _ msg) = Left $ ParseError (T.length t - T.length i) msg+  go (A.Fail i _ msg) = Left $ ParseError (T.length t - T.length i)+                             $ if "endOfInput" `isPrefixOf` msg+                                  then "Unexpected " <> show (T.take 20 i)+                                  else msg   go (A.Partial continue) = go (continue "")   go (A.Done _i r) = Right r @@ -238,24 +247,23 @@ pDocument :: P Document pDocument = do   meta <- pDocumentHeader-  let minSectionLevel = case M.lookup "doctype" (docAttributes meta) of+  attr' <- gets docAttrs+  let minSectionLevel = case M.lookup "doctype" attr' of                           Just "book" -> 0                           _ -> 1-  bs <- (case M.lookup "hardbreaks-option" (docAttributes meta) of+  bs <- (case M.lookup "hardbreaks-option" attr' of             Just "" -> withHardBreaks             _ -> id) $         withBlockContext (SectionContext (minSectionLevel - 1)) (many pBlock)   skipWhile isSpace   endOfInput-  pure $ Document { docMeta = meta , docBlocks = bs }+  attr <- gets docAttrs+  pure $ Document { docMeta = meta{ docAttributes = attr } , docBlocks = bs }  pDocumentHeader :: P Meta pDocumentHeader = do-  let handleAttr m (Left k) = M.delete k m-      handleAttr m (Right (k,v)) = M.insert k v m-  let defaultDocAttrs = M.insert "sectids" "" mempty   skipBlankLines-  topattrs <- foldl' handleAttr defaultDocAttrs <$> many pDocAttribute+  skipMany pDocAttribute   skipBlankLines   (title, titleAttr) <- option ([], Nothing) $ do     (_,titleAttr) <- pTitlesAndAttributes@@ -269,12 +277,13 @@   revision <- if null title                  then pure Nothing                  else optional pDocumentRevision-  attrs <- foldl' handleAttr topattrs <$> many pDocAttribute+  skipMany pDocAttribute   pure $ Meta{ docTitle = title              , docTitleAttributes = titleAttr              , docAuthors = authors              , docRevision = revision-             , docAttributes = attrs }+             , docAttributes = mempty }+              -- docAttributes this gets added at the end from docAttrs in state  pDocumentTitle :: P [Inline] pDocumentTitle = do@@ -321,18 +330,18 @@ pLine = takeWhile (not . isEndOfLine) <* (endOfLine <|> endOfInput)  --- Left key unsets key--- Right (key, val) sets key-pDocAttribute :: P (Either Text (Text, Text))+pDocAttribute :: P () pDocAttribute = do   vchar ':'   unset <- option False $ True <$ vchar '!'   k <- pDocAttributeName   vchar ':'   v <- pLineWithEscapes-  pure $ if unset-            then Left k-            else Right (k,v)+  modify $ \s ->+    s{ docAttrs =+         if unset+            then M.delete k (docAttrs s)+            else M.insert k v (docAttrs s) }  pDocAttributeName :: P Text pDocAttributeName = do@@ -380,8 +389,16 @@       pure x  parseBlocks :: Text -> P [Block]-parseBlocks = parseWith (many pBlock) . (<> "\n") . T.strip+parseBlocks = parseWith pBlocks . (<> "\n") . T.strip +pBlocks :: P [Block]+pBlocks = do+  bs <- many pBlock+  skipBlankLines+  skipMany pDocAttribute+  skipBlankLines+  pure bs+ parseAsciidoc :: Text -> P Document parseAsciidoc = parseWith pDocument . (<> "\n") . T.strip @@ -402,11 +419,13 @@ pBlock = do   contexts <- asks blockContexts   skipBlankLines+  skipMany pDocAttribute+  skipBlankLines   (mbtitle, attr) <- pTitlesAndAttributes+  skipMany (pCommentBlock attr)   case contexts of     ListContext{} : _ -> skipWhile (== ' ')     _ -> pure ()-  skipMany (pCommentBlock attr)   let hardbreaks =        case attr of           Attr _ kvs@@ -1326,7 +1345,7 @@ pInline :: [Char] -> P Inline pInline prevChars = do   let maybeUnconstrained = case prevChars of-                              (d:_) -> isSpace d || isPunctuation d+                              (d:_) -> isSpace d || isPunctuation d || d == '+'                               [] -> True   let inMatched = pInMatched maybeUnconstrained   skipMany pLineComment@@ -1411,7 +1430,7 @@     mbc <- peekChar     case mbc of       Nothing -> pure ()-      Just c -> guard $ isSpace c || isPunctuation c+      Just c -> guard $ isSpace c || isPunctuation c || c == '+'   Inline attr <$> toInlineType (T.pack cs)  pInlineAnchor :: P Inline@@ -1532,12 +1551,11 @@                     then pure [Inline mempty (Str target)]                     else parseInlines description)   , ("footnote", \target -> do-      attr <- pAttributes-      let (contents, attr') = extractDescription attr-          fnid = if target == mempty+      ils <- pBracketedText >>= parseInlines+      let fnid = if target == mempty                     then Nothing                     else Just (FootnoteId target)-      Inline attr' . Footnote fnid <$> parseInlines contents)+      pure $ Inline mempty (Footnote fnid ils))   , ("footnoteref", \_ -> do       (Attr ps kvs) <- pAttributes       (target, contents) <- case ps of@@ -1580,6 +1598,8 @@   vchar '[' *>     (mconcat <$> many          (T.pack <$> some ((vchar '\\' *> char ']') <|>+                           (vchar '+' *> vchar '+' *> char ']'+                             <* vchar '+' <* vchar '+') <|>                  satisfy (\c -> c /= ']' && not (isEndOfLine c)) <|>                  (' ' <$ (vchar '\\' <* endOfLine)))           <|> ((\x -> "[" <> x <> "]") <$> pBracketedText)))
test/Main.hs view
@@ -20,9 +20,11 @@ main = do   asciidoctorTests <- goldenTests "asciidoctor"   featureTests <- goldenTests "feature"+  regressionTests <- goldenTests "regression"   defaultMain $ testGroup "Tests"     [ testGroup "Asciidoctor" asciidoctorTests     , testGroup "Feature" featureTests+    , testGroup "Regression" regressionTests     , testGroup "Generic"        [ foldInlineTest        , foldBlockTest@@ -33,18 +35,21 @@  goldenTests :: FilePath -> IO [TestTree] goldenTests fp = do-  files <- findTestFiles ("test" </> fp)-  pure $ map (\(category, fs) ->-          testGroup category (map toGoldenTest fs)) files+  (toplevel, groups) <- findTestFiles ("test" </> fp)+  pure $ map toGoldenTest toplevel +++         map (\(category, fs) ->+          testGroup category (map toGoldenTest fs)) groups  -- Find all .test files in a directory-findTestFiles :: FilePath -> IO [(FilePath, [FilePath])]+findTestFiles :: FilePath -> IO ([FilePath], [(FilePath, [FilePath])]) findTestFiles baseDir = do-  categories <- listDirectory baseDir+  fs <- listDirectory baseDir   let go f = do         xs <- map ((baseDir </> f) </>) <$> listDirectory (baseDir </> f)         return (f, sort $ filter ((== ".test") . takeExtension) xs)-  mapM go categories+  categories <- mapM go (filter (null . takeExtension) fs)+  let toplevel = map (baseDir </>) $ filter ((== ".test") . takeExtension) fs+  return (toplevel, categories)  toGoldenTest :: FilePath -> TestTree toGoldenTest fp =
test/asciidoctor/outline/numbered.test view
@@ -42,12 +42,7 @@           Attr           ( [] , fromList [ ( "id" , "_unnumbered_section" ) ] )           Nothing-          (Section-             (Level 1)-             [ Inline mempty (Str "Unnumbered Section") ]-             [ Block-                 mempty Nothing (Paragraph [ Inline mempty (Str ":numbered:") ])-             ])+          (Section (Level 1) [ Inline mempty (Str "Unnumbered Section") ] [])       , Block           Attr           ( [] , fromList [ ( "id" , "_section_2" ) ] )
+ test/regression/issue_1.test view
@@ -0,0 +1,23 @@+= Test++:doc_attr: bar+doc_attr = {doc_attr}+>>>+Document+  { docMeta =+      Meta+        { docTitle = [ Inline mempty (Str "Test") ]+        , docTitleAttributes = Nothing+        , docAuthors = []+        , docRevision = Nothing+        , docAttributes =+            fromList [ ( "doc_attr" , "bar" ) , ( "sectids" , "" ) ]+        }+  , docBlocks =+      [ Block+          mempty+          Nothing+          (Paragraph+             [ Inline mempty (Str "doc_attr = ") , Inline mempty (Str "bar") ])+      ]+  }
+ test/regression/issue_2.test view
@@ -0,0 +1,60 @@+Textfootnote:[Commas break foonotes, do you see?].++Other textfootnote:[This is the same for closing ++[++brackets++]++,+isn’t it?].++And anotherfootnote:[Some dots do as well. But not all of them.], and a+final onefootnote:[S. Jackson.].++>>>+Document+  { docMeta =+      Meta+        { docTitle = []+        , docTitleAttributes = Nothing+        , docAuthors = []+        , docRevision = Nothing+        , docAttributes = fromList [ ( "sectids" , "" ) ]+        }+  , docBlocks =+      [ Block+          mempty+          Nothing+          (Paragraph+             [ Inline mempty (Str "Text")+             , Inline+                 mempty+                 (Footnote+                    Nothing+                    [ Inline mempty (Str "Commas break foonotes, do you see?") ])+             , Inline mempty (Str ".")+             ])+      , Block+          mempty+          Nothing+          (Paragraph+             [ Inline+                 mempty (Str "Other textfootnote:[This is the same for closing ")+             , Inline mempty (Str "[")+             , Inline mempty (Str "brackets")+             , Inline mempty (Str "]")+             , Inline mempty (Str ",\nisn\8217t it?].")+             ])+      , Block+          mempty+          Nothing+          (Paragraph+             [ Inline mempty (Str "And another")+             , Inline+                 mempty+                 (Footnote+                    Nothing+                    [ Inline mempty (Str "Some dots do as well. But not all of them.")+                    ])+             , Inline mempty (Str ", and a\nfinal one")+             , Inline+                 mempty (Footnote Nothing [ Inline mempty (Str "S. Jackson.") ])+             , Inline mempty (Str ".")+             ])+      ]+  }
+ test/regression/issue_3.test view
@@ -0,0 +1,79 @@+Words _italicised_ in brackets are ++[++_broken_++]++.++But this [_works_], ++[++__this__++]++ too, and ++[++this _also_ works.++]++++Words in *bold* in brackets are ++{++*broken*++}++.++But this {*works*}, ++{++**this**++}++ too, and ++{++this *also* works.++}++++>>>+Document+  { docMeta =+      Meta+        { docTitle = []+        , docTitleAttributes = Nothing+        , docAuthors = []+        , docRevision = Nothing+        , docAttributes = fromList [ ( "sectids" , "" ) ]+        }+  , docBlocks =+      [ Block+          mempty+          Nothing+          (Paragraph+             [ Inline mempty (Str "Words ")+             , Inline mempty (Italic [ Inline mempty (Str "italicised") ])+             , Inline mempty (Str " in brackets are ")+             , Inline mempty (Str "[")+             , Inline mempty (Italic [ Inline mempty (Str "broken") ])+             , Inline mempty (Str "]")+             , Inline mempty (Str ".")+             ])+      , Block+          mempty+          Nothing+          (Paragraph+             [ Inline mempty (Str "But this [")+             , Inline mempty (Italic [ Inline mempty (Str "works") ])+             , Inline mempty (Str "], ")+             , Inline mempty (Str "[")+             , Inline mempty (Italic [ Inline mempty (Str "this") ])+             , Inline mempty (Str "]")+             , Inline mempty (Str " too, and ")+             , Inline mempty (Str "[")+             , Inline mempty (Str "this ")+             , Inline mempty (Italic [ Inline mempty (Str "also") ])+             , Inline mempty (Str " works.")+             , Inline mempty (Str "]")+             ])+      , Block+          mempty+          Nothing+          (Paragraph+             [ Inline mempty (Str "Words in ")+             , Inline mempty (Bold [ Inline mempty (Str "bold") ])+             , Inline mempty (Str " in brackets are ")+             , Inline mempty (Str "{")+             , Inline mempty (Bold [ Inline mempty (Str "broken") ])+             , Inline mempty (Str "}")+             , Inline mempty (Str ".")+             ])+      , Block+          mempty+          Nothing+          (Paragraph+             [ Inline mempty (Str "But this {")+             , Inline mempty (Bold [ Inline mempty (Str "works") ])+             , Inline mempty (Str "}, ")+             , Inline mempty (Str "{")+             , Inline mempty (Bold [ Inline mempty (Str "this") ])+             , Inline mempty (Str "}")+             , Inline mempty (Str " too, and ")+             , Inline mempty (Str "{")+             , Inline mempty (Str "this ")+             , Inline mempty (Bold [ Inline mempty (Str "also") ])+             , Inline mempty (Str " works.")+             , Inline mempty (Str "}")+             ])+      ]+  }