git.iko.soy/typeable/pandoc
on master help sign in
Merge pull request #1989 from zudov/shortcut_ref_link_pr

Support shortcut reference links in markdown writer

commit0deb7c507d85c1f17b31670f617b2f5d2029a16c authorJohn MacFarlane <jgm@berkeley.edu> dateSun 15 Mar 2015, 11:58 (12 years ago) parent967c135b9f77ed
5 files changed+158−31
README+5 src/Text/Pandoc/Options.hs+7−1 src/Text/Pandoc/Readers/Markdown.hs+1 src/Text/Pandoc/Writers/Markdown.hs+55−29 tests/Tests/Writers/Markdown.hs+90−1
README+5
@@ -2790,6 +2790,11 @@ in several respects:
27902790 we must either disallow lazy wrapping or require a blank line between
27912791 list items.
27922792
2793+#### Extension: `shortcut_reference_links` ####
2794+
2795+Allows to use shortcut reference links: `[foo]` instead of `[foo][]`. Writer
2796+would shortcut links unless doing so might cause problems.
2797+
27932798Markdown variants
27942799-----------------
27952800
src/Text/Pandoc/Options.hs+7−1
@@ -109,6 +109,7 @@ data Extension =
109109 | Ext_implicit_header_references -- ^ Implicit reference links for headers
110110 | Ext_line_blocks -- ^ RST style line blocks
111111 | Ext_epub_html_exts -- ^ Recognise the EPUB extended version of HTML
112+ | Ext_shortcut_reference_links -- ^ Shortcut reference links
112113 deriving (Show, Read, Enum, Eq, Ord, Bounded)
113114
114115pandocExtensions :: Set Extension
@@ -151,6 +152,7 @@ pandocExtensions = Set.fromList
151152 , Ext_header_attributes
152153 , Ext_implicit_header_references
153154 , Ext_line_blocks
155+ , Ext_shortcut_reference_links
154156 ]
155157
156158phpMarkdownExtraExtensions :: Set Extension
@@ -164,6 +166,7 @@ phpMarkdownExtraExtensions = Set.fromList
164166 , Ext_intraword_underscores
165167 , Ext_header_attributes
166168 , Ext_abbreviations
169+ , Ext_shortcut_reference_links
167170 ]
168171
169172githubMarkdownExtensions :: Set Extension
@@ -180,6 +183,7 @@ githubMarkdownExtensions = Set.fromList
180183 , Ext_strikeout
181184 , Ext_hard_line_breaks
182185 , Ext_lists_without_preceding_blankline
186+ , Ext_shortcut_reference_links
183187 ]
184188
185189multimarkdownExtensions :: Set Extension
@@ -202,7 +206,9 @@ multimarkdownExtensions = Set.fromList
202206
203207strictExtensions :: Set Extension
204208strictExtensions = Set.fromList
205− [ Ext_raw_html ]
209+ [ Ext_raw_html
210+ , Ext_shortcut_reference_links
211+ ]
206212
207213data ReaderOptions = ReaderOptions{
208214 readerExtensions :: Set Extension -- ^ Syntax extensions
src/Text/Pandoc/Readers/Markdown.hs+1
@@ -1677,6 +1677,7 @@ referenceLink constructor (lab, raw) = do
16771677 lookAhead (try (spnl >> normalCite >> return (mempty, "")))
16781678 <|>
16791679 try (spnl >> reference)
1680+ when (raw' == "") $ guardEnabled Ext_shortcut_reference_links
16801681 let labIsRef = raw' == "" || raw' == "[]"
16811682 let key = toKey $ if labIsRef then raw else raw'
16821683 parsedRaw <- parseFromString (mconcat <$> many inline) raw'
src/Text/Pandoc/Writers/Markdown.hs+55−29
@@ -57,14 +57,15 @@ import qualified Data.Text as T
5757
5858type Notes = [[Block]]
5959type Refs = [([Inline], Target)]
60−data WriterState = WriterState { stNotes :: Notes
61− , stRefs :: Refs
62− , stInList :: Bool
63− , stIds :: [String]
64− , stPlain :: Bool }
60+data WriterState = WriterState { stNotes :: Notes
61+ , stRefs :: Refs
62+ , stRefShortcutable :: Bool
63+ , stInList :: Bool
64+ , stIds :: [String]
65+ , stPlain :: Bool }
6566instance Default WriterState
66− where def = WriterState{ stNotes = [], stRefs = [], stInList = False,
67− stIds = [], stPlain = False }
67+ where def = WriterState{ stNotes = [], stRefs = [], stRefShortcutable = True,
68+ stInList = False, stIds = [], stPlain = False }
6869
6970-- | Convert Pandoc to Markdown.
7071writeMarkdown :: WriterOptions -> Pandoc -> String
@@ -695,27 +696,47 @@ getReference label (src, tit) = do
695696inlineListToMarkdown :: WriterOptions -> [Inline] -> State WriterState Doc
696697inlineListToMarkdown opts lst = do
697698 inlist <- gets stInList
698− mapM (inlineToMarkdown opts)
699− (if inlist then avoidBadWraps lst else lst) >>= return . cat
700− where avoidBadWraps [] = []
701− avoidBadWraps (Space:Str ('>':cs):xs) =
702− Str (' ':'>':cs) : avoidBadWraps xs
703− avoidBadWraps (Space:Str [c]:[])
704− | c `elem` "-*+" = Str [' ', c] : []
705− avoidBadWraps (Space:Str [c]:Space:xs)
706− | c `elem` "-*+" = Str [' ', c] : Space : avoidBadWraps xs
707− avoidBadWraps (Space:Str cs:Space:xs)
708− | isOrderedListMarker cs = Str (' ':cs) : Space : avoidBadWraps xs
709− avoidBadWraps (Space:Str cs:[])
710− | isOrderedListMarker cs = Str (' ':cs) : []
711− avoidBadWraps (x:xs) = x : avoidBadWraps xs
712− isOrderedListMarker xs = endsWithListPunct xs &&
713− isRight (runParserT (anyOrderedListMarker >> eof)
714− defaultParserState "" xs)
715− endsWithListPunct xs = case reverse xs of
716− '.':_ -> True
717− ')':_ -> True
718− _ -> False
699+ go (if inlist then avoidBadWrapsInList lst else lst)
700+ where go [] = return empty
701+ go (i:is) = case i of
702+ (Link _ _) -> case is of
703+ -- If a link is followed by another link or '[' we don't shortcut
704+ (Link _ _):_ -> unshortcutable
705+ Space:(Link _ _):_ -> unshortcutable
706+ Space:(Str('[':_)):_ -> unshortcutable
707+ Space:(RawInline _ ('[':_)):_ -> unshortcutable
708+ Space:(Cite _ _):_ -> unshortcutable
709+ (Cite _ _):_ -> unshortcutable
710+ Str ('[':_):_ -> unshortcutable
711+ (RawInline _ ('[':_)):_ -> unshortcutable
712+ (RawInline _ (' ':'[':_)):_ -> unshortcutable
713+ _ -> shortcutable
714+ _ -> shortcutable
715+ where shortcutable = liftM2 (<>) (inlineToMarkdown opts i) (go is)
716+ unshortcutable = do
717+ iMark <- withState (\s -> s { stRefShortcutable = False })
718+ (inlineToMarkdown opts i)
719+ modify (\s -> s {stRefShortcutable = True })
720+ fmap (iMark <>) (go is)
721+
722+avoidBadWrapsInList :: [Inline] -> [Inline]
723+avoidBadWrapsInList [] = []
724+avoidBadWrapsInList (Space:Str ('>':cs):xs) =
725+ Str (' ':'>':cs) : avoidBadWrapsInList xs
726+avoidBadWrapsInList (Space:Str [c]:[])
727+ | c `elem` "-*+" = Str [' ', c] : []
728+avoidBadWrapsInList (Space:Str [c]:Space:xs)
729+ | c `elem` "-*+" = Str [' ', c] : Space : avoidBadWrapsInList xs
730+avoidBadWrapsInList (Space:Str cs:Space:xs)
731+ | isOrderedListMarker cs = Str (' ':cs) : Space : avoidBadWrapsInList xs
732+avoidBadWrapsInList (Space:Str cs:[])
733+ | isOrderedListMarker cs = Str (' ':cs) : []
734+avoidBadWrapsInList (x:xs) = x : avoidBadWrapsInList xs
735+
736+isOrderedListMarker :: String -> Bool
737+isOrderedListMarker xs = (last xs `elem` ".)") &&
738+ isRight (runParserT (anyOrderedListMarker >> eof)
739+ defaultParserState "" xs)
719740
720741isRight :: Either a b -> Bool
721742isRight (Right _) = True
@@ -878,6 +899,9 @@ inlineToMarkdown opts (Link txt (src, tit)) = do
878899 [Str s] | escapeURI s == srcSuffix -> True
879900 _ -> False
880901 let useRefLinks = writerReferenceLinks opts && not useAuto
902+ shortcutable <- gets stRefShortcutable
903+ let useShortcutRefLinks = shortcutable &&
904+ isEnabled Ext_shortcut_reference_links opts
881905 ref <- if useRefLinks then getReference txt (src, tit) else return []
882906 reftext <- inlineListToMarkdown opts ref
883907 return $ if useAuto
@@ -887,7 +911,9 @@ inlineToMarkdown opts (Link txt (src, tit)) = do
887911 else if useRefLinks
888912 then let first = "[" <> linktext <> "]"
889913 second = if txt == ref
890− then "[]"
914+ then if useShortcutRefLinks
915+ then ""
916+ else "[]"
891917 else "[" <> reftext <> "]"
892918 in first <> second
893919 else if plain
tests/Tests/Writers/Markdown.hs+90−1
@@ -1,4 +1,5 @@
11{-# LANGUAGE OverloadedStrings #-}
2+{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
23module Tests.Writers.Markdown (tests) where
34
45import Test.Framework
@@ -35,4 +36,92 @@ tests = [ "indented code after list"
3536 =: bulletList [ plain "foo" <> bulletList [ plain "bar" ],
3637 plain "baz" ]
3738 =?> "- 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+ ]