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 +19/−1
- asciidoc.cabal +2/−1
- src/AsciiDoc/Parse.hs +47/−27
- test/Main.hs +11/−6
- test/asciidoctor/outline/numbered.test +1/−6
- test/regression/issue_1.test +23/−0
- test/regression/issue_2.test +60/−0
- test/regression/issue_3.test +79/−0
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 "}")+ ])+ ]+ }