git.iko.soy/typeable/pandoc
on master help sign in
Support shortcut reference links in markdown writer
b9f77edKonstantin Zudov
@@ -2785,6 +2785,11 @@ in several respects:
2785 we must either disallow lazy wrapping or require a blank line between
2785 we must either disallow lazy wrapping or require a blank line between
2786 list items.
2786 list items.
2787
2787
2788+#### Extension: `shortcut_reference_links` ####
2789+
2790+Allows to use shortcut reference links: `[foo]` instead of `[foo][]`. Writer
2791+would shortcut links unless doing so might cause problems.
2792+
2788Markdown variants
2793Markdown variants
2789-----------------
2794-----------------
2790
2795
src/Text/Pandoc/Options.hs+7−1
@@ -109,6 +109,7 @@ data Extension =
109 | Ext_implicit_header_references -- ^ Implicit reference links for headers
109 | Ext_implicit_header_references -- ^ Implicit reference links for headers
110 | Ext_line_blocks -- ^ RST style line blocks
110 | Ext_line_blocks -- ^ RST style line blocks
111 | Ext_epub_html_exts -- ^ Recognise the EPUB extended version of HTML
111 | Ext_epub_html_exts -- ^ Recognise the EPUB extended version of HTML
112+ | Ext_shortcut_reference_links -- ^ Shortcut reference links
112 deriving (Show, Read, Enum, Eq, Ord, Bounded)
113 deriving (Show, Read, Enum, Eq, Ord, Bounded)
113
114
114pandocExtensions :: Set Extension
115pandocExtensions :: Set Extension
@@ -151,6 +152,7 @@ pandocExtensions = Set.fromList
151 , Ext_header_attributes
152 , Ext_header_attributes
152 , Ext_implicit_header_references
153 , Ext_implicit_header_references
153 , Ext_line_blocks
154 , Ext_line_blocks
155+ , Ext_shortcut_reference_links
154 ]
156 ]
155
157
156phpMarkdownExtraExtensions :: Set Extension
158phpMarkdownExtraExtensions :: Set Extension
@@ -164,6 +166,7 @@ phpMarkdownExtraExtensions = Set.fromList
164 , Ext_intraword_underscores
166 , Ext_intraword_underscores
165 , Ext_header_attributes
167 , Ext_header_attributes
166 , Ext_abbreviations
168 , Ext_abbreviations
169+ , Ext_shortcut_reference_links
167 ]
170 ]
168
171
169githubMarkdownExtensions :: Set Extension
172githubMarkdownExtensions :: Set Extension
@@ -180,6 +183,7 @@ githubMarkdownExtensions = Set.fromList
180 , Ext_strikeout
183 , Ext_strikeout
181 , Ext_hard_line_breaks
184 , Ext_hard_line_breaks
182 , Ext_lists_without_preceding_blankline
185 , Ext_lists_without_preceding_blankline
186+ , Ext_shortcut_reference_links
183 ]
187 ]
184
188
185multimarkdownExtensions :: Set Extension
189multimarkdownExtensions :: Set Extension
@@ -202,7 +206,9 @@ multimarkdownExtensions = Set.fromList
202
206
203strictExtensions :: Set Extension
207strictExtensions :: Set Extension
204strictExtensions = Set.fromList
208strictExtensions = Set.fromList
205− [ Ext_raw_html ]
209+ [ Ext_raw_html
210+ , Ext_shortcut_reference_links
211+ ]
206
212
207data ReaderOptions = ReaderOptions{
213data ReaderOptions = ReaderOptions{
208 readerExtensions :: Set Extension -- ^ Syntax extensions
214 readerExtensions :: Set Extension -- ^ Syntax extensions
src/Text/Pandoc/Readers/Markdown.hs+1
@@ -1679,6 +1679,7 @@ referenceLink constructor (lab, raw) = do
1679 lookAhead (try (spnl >> normalCite >> return (mempty, "")))
1679 lookAhead (try (spnl >> normalCite >> return (mempty, "")))
1680 <|>
1680 <|>
1681 try (spnl >> reference)
1681 try (spnl >> reference)
1682+ when (raw' == "") $ guardEnabled Ext_shortcut_reference_links
1682 let labIsRef = raw' == "" || raw' == "[]"
1683 let labIsRef = raw' == "" || raw' == "[]"
1683 let key = toKey $ if labIsRef then raw else raw'
1684 let key = toKey $ if labIsRef then raw else raw'
1684 parsedRaw <- parseFromString (mconcat <$> many inline) raw'
1685 parsedRaw <- parseFromString (mconcat <$> many inline) raw'
src/Text/Pandoc/Writers/Markdown.hs+55−29
@@ -57,14 +57,15 @@ import qualified Data.Text as T
57
57
58type Notes = [[Block]]
58type Notes = [[Block]]
59type Refs = [([Inline], Target)]
59type Refs = [([Inline], Target)]
60−data WriterState = WriterState { stNotes :: Notes
60+data WriterState = WriterState { stNotes :: Notes
61− , stRefs :: Refs
61+ , stRefs :: Refs
62+ , stRefShortcutable :: Bool
62− , stInList :: Bool
63+ , stInList :: Bool
63− , stIds :: [String]
64+ , stIds :: [String]
64− , stPlain :: Bool }
65+ , stPlain :: Bool }
65instance Default WriterState
66instance Default WriterState
66− where def = WriterState{ stNotes = [], stRefs = [], stInList = False,
67+ where def = WriterState{ stNotes = [], stRefs = [], stRefShortcutable = True,
67− stIds = [], stPlain = False }
68+ stInList = False, stIds = [], stPlain = False }
68
69
69-- | Convert Pandoc to Markdown.
70-- | Convert Pandoc to Markdown.
70writeMarkdown :: WriterOptions -> Pandoc -> String
71writeMarkdown :: WriterOptions -> Pandoc -> String
@@ -690,27 +691,47 @@ getReference label (src, tit) = do
690inlineListToMarkdown :: WriterOptions -> [Inline] -> State WriterState Doc
691inlineListToMarkdown :: WriterOptions -> [Inline] -> State WriterState Doc
691inlineListToMarkdown opts lst = do
692inlineListToMarkdown opts lst = do
692 inlist <- gets stInList
693 inlist <- gets stInList
693− mapM (inlineToMarkdown opts)
694− (if inlist then avoidBadWraps lst else lst) >>= return . cat
694+ go (if inlist then avoidBadWrapsInList lst else lst)
695− where avoidBadWraps [] = []
695+ where go [] = return empty
696+ go (i:is) = case i of
697+ (Link _ _) -> case is of
698+ -- If a link is followed by another link or '[' we don't shortcut
699+ (Link _ _):_ -> unshortcutable
700+ Space:(Link _ _):_ -> unshortcutable
701+ Space:(Str('[':_)):_ -> unshortcutable
702+ Space:(RawInline _ ('[':_)):_ -> unshortcutable
703+ Space:(Cite _ _):_ -> unshortcutable
704+ (Cite _ _):_ -> unshortcutable
705+ Str ('[':_):_ -> unshortcutable
706+ (RawInline _ ('[':_)):_ -> unshortcutable
707+ (RawInline _ (' ':'[':_)):_ -> unshortcutable
708+ _ -> shortcutable
709+ _ -> shortcutable
710+ where shortcutable = liftM2 (<>) (inlineToMarkdown opts i) (go is)
711+ unshortcutable = do
712+ iMark <- withState (\s -> s { stRefShortcutable = False })
713+ (inlineToMarkdown opts i)
714+ modify (\s -> s {stRefShortcutable = True })
715+ fmap (iMark <>) (go is)
716+
717+avoidBadWrapsInList :: [Inline] -> [Inline]
718+avoidBadWrapsInList [] = []
696− avoidBadWraps (Space:Str ('>':cs):xs) =
719+avoidBadWrapsInList (Space:Str ('>':cs):xs) =
697− Str (' ':'>':cs) : avoidBadWraps xs
720+ Str (' ':'>':cs) : avoidBadWrapsInList xs
698− avoidBadWraps (Space:Str [c]:[])
721+avoidBadWrapsInList (Space:Str [c]:[])
699− | c `elem` "-*+" = Str [' ', c] : []
722+ | c `elem` "-*+" = Str [' ', c] : []
700− avoidBadWraps (Space:Str [c]:Space:xs)
723+avoidBadWrapsInList (Space:Str [c]:Space:xs)
701− | c `elem` "-*+" = Str [' ', c] : Space : avoidBadWraps xs
724+ | c `elem` "-*+" = Str [' ', c] : Space : avoidBadWrapsInList xs
702− avoidBadWraps (Space:Str cs:Space:xs)
725+avoidBadWrapsInList (Space:Str cs:Space:xs)
703− | isOrderedListMarker cs = Str (' ':cs) : Space : avoidBadWraps xs
726+ | isOrderedListMarker cs = Str (' ':cs) : Space : avoidBadWrapsInList xs
704− avoidBadWraps (Space:Str cs:[])
727+avoidBadWrapsInList (Space:Str cs:[])
705− | isOrderedListMarker cs = Str (' ':cs) : []
728+ | isOrderedListMarker cs = Str (' ':cs) : []
706− avoidBadWraps (x:xs) = x : avoidBadWraps xs
729+avoidBadWrapsInList (x:xs) = x : avoidBadWrapsInList xs
730+
731+isOrderedListMarker :: String -> Bool
707− isOrderedListMarker xs = endsWithListPunct xs &&
732+isOrderedListMarker xs = (last xs `elem` ".)") &&
708− isRight (runParserT (anyOrderedListMarker >> eof)
733+ isRight (runParserT (anyOrderedListMarker >> eof)
709− defaultParserState "" xs)
734+ defaultParserState "" xs)
710− endsWithListPunct xs = case reverse xs of
711− '.':_ -> True
712− ')':_ -> True
713− _ -> False
714
735
715isRight :: Either a b -> Bool
736isRight :: Either a b -> Bool
716isRight (Right _) = True
737isRight (Right _) = True
@@ -873,6 +894,9 @@ inlineToMarkdown opts (Link txt (src, tit)) = do
873 [Str s] | escapeURI s == srcSuffix -> True
894 [Str s] | escapeURI s == srcSuffix -> True
874 _ -> False
895 _ -> False
875 let useRefLinks = writerReferenceLinks opts && not useAuto
896 let useRefLinks = writerReferenceLinks opts && not useAuto
897+ shortcutable <- gets stRefShortcutable
898+ let useShortcutRefLinks = shortcutable &&
899+ isEnabled Ext_shortcut_reference_links opts
876 ref <- if useRefLinks then getReference txt (src, tit) else return []
900 ref <- if useRefLinks then getReference txt (src, tit) else return []
877 reftext <- inlineListToMarkdown opts ref
901 reftext <- inlineListToMarkdown opts ref
878 return $ if useAuto
902 return $ if useAuto
@@ -882,7 +906,9 @@ inlineToMarkdown opts (Link txt (src, tit)) = do
882 else if useRefLinks
906 else if useRefLinks
883 then let first = "[" <> linktext <> "]"
907 then let first = "[" <> linktext <> "]"
884 second = if txt == ref
908 second = if txt == ref
885− then "[]"
909+ then if useShortcutRefLinks
910+ then ""
911+ else "[]"
886 else "[" <> reftext <> "]"
912 else "[" <> reftext <> "]"
887 in first <> second
913 in first <> second
888 else if plain
914 else if plain
tests/Tests/Writers/Markdown.hs+90−1
@@ -1,4 +1,5 @@
1{-# LANGUAGE OverloadedStrings #-}
1{-# LANGUAGE OverloadedStrings #-}
2+{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
2module Tests.Writers.Markdown (tests) where
3module Tests.Writers.Markdown (tests) where
3
4
4import Test.Framework
5import Test.Framework
@@ -35,4 +36,92 @@ tests = [ "indented code after list"
35 =: bulletList [ plain "foo" <> bulletList [ plain "bar" ],
36 =: bulletList [ plain "foo" <> bulletList [ plain "bar" ],
36 plain "baz" ]
37 plain "baz" ]
37 =?> "- foo\n - bar\n- baz\n"
38 =?> "- foo\n - bar\n- baz\n"
38− ]
39+ ] ++ [shortcutLinkRefsTests]
40+
41+shortcutLinkRefsTests :: Test
42+shortcutLinkRefsTests =
43+ let infix 4 =:
44+ (=:) :: (ToString a, ToPandoc a)
45+ => String -> (a, String) -> Test
46+ (=:) = test (writeMarkdown (def {writerReferenceLinks = True}) . toPandoc)
47+ in testGroup "Shortcut reference links"
48+ [ "Simple link (shortcutable)"
49+ =: (para (link "/url" "title" "foo"))
50+ =?> "[foo]\n\n [foo]: /url \"title\""
51+ , "Followed by another link (unshortcutable)"
52+ =: (para ((link "/url1" "title1" "first")
53+ <> (link "/url2" "title2" "second")))
54+ =?> unlines [ "[first][][second]"
55+ , ""
56+ , " [first]: /url1 \"title1\""
57+ , " [second]: /url2 \"title2\""
58+ ]
59+ , "Followed by space and another link (unshortcutable)"
60+ =: (para ((link "/url1" "title1" "first") <> " "
61+ <> (link "/url2" "title2" "second")))
62+ =?> unlines [ "[first][] [second]"
63+ , ""
64+ , " [first]: /url1 \"title1\""
65+ , " [second]: /url2 \"title2\""
66+ ]
67+ , "Reference link is used multiple times (unshortcutable)"
68+ =: (para ((link "/url1" "" "foo") <> (link "/url2" "" "foo")
69+ <> (link "/url3" "" "foo")))
70+ =?> unlines [ "[foo][][foo][1][foo][2]"
71+ , ""
72+ , " [foo]: /url1"
73+ , " [1]: /url2"
74+ , " [2]: /url3"
75+ ]
76+ , "Reference link is used multiple times (unshortcutable)"
77+ =: (para ((link "/url1" "" "foo") <> " " <> (link "/url2" "" "foo")
78+ <> " " <> (link "/url3" "" "foo")))
79+ =?> unlines [ "[foo][] [foo][1] [foo][2]"
80+ , ""
81+ , " [foo]: /url1"
82+ , " [1]: /url2"
83+ , " [2]: /url3"
84+ ]
85+ , "Reference link is followed by text in brackets"
86+ =: (para ((link "/url" "" "link") <> "[text in brackets]"))
87+ =?> unlines [ "[link][][text in brackets]"
88+ , ""
89+ , " [link]: /url"
90+ ]
91+ , "Reference link is followed by space and text in brackets"
92+ =: (para ((link "/url" "" "link") <> " [text in brackets]"))
93+ =?> unlines [ "[link][] [text in brackets]"
94+ , ""
95+ , " [link]: /url"
96+ ]
97+ , "Reference link is followed by RawInline"
98+ =: (para ((link "/url" "" "link") <> rawInline "markdown" "[rawText]"))
99+ =?> unlines [ "[link][][rawText]"
100+ , ""
101+ , " [link]: /url"
102+ ]
103+ , "Reference link is followed by space and RawInline"
104+ =: (para ((link "/url" "" "link") <> space <> rawInline "markdown" "[rawText]"))
105+ =?> unlines [ "[link][] [rawText]"
106+ , ""
107+ , " [link]: /url"
108+ ]
109+ , "Reference link is followed by RawInline with space"
110+ =: (para ((link "/url" "" "link") <> rawInline "markdown" " [rawText]"))
111+ =?> unlines [ "[link][] [rawText]"
112+ , ""
113+ , " [link]: /url"
114+ ]
115+ , "Reference link is followed by citation"
116+ =: (para ((link "/url" "" "link") <> cite [Citation "author" [] [] NormalCitation 0 0] (str "[@author]")))
117+ =?> unlines [ "[link][][@author]"
118+ , ""
119+ , " [link]: /url"
120+ ]
121+ , "Reference link is followed by space and citation"
122+ =: (para ((link "/url" "" "link") <> space <> cite [Citation "author" [] [] NormalCitation 0 0] (str "[@author]")))
123+ =?> unlines [ "[link][] [@author]"
124+ , ""
125+ , " [link]: /url"
126+ ]
127+ ]