git.iko.soy/typeable/pandoc
on master help sign in
Org: support task_lists extension
a3beed9Albert Krewinkel
src/Text/Pandoc/Extensions.hs+2
@@ -381,6 +381,7 @@ getDefaultExtensions "commonmark_x" = extensionsFromList
381 ]
381 ]
382getDefaultExtensions "org" = extensionsFromList
382getDefaultExtensions "org" = extensionsFromList
383 [Ext_citations,
383 [Ext_citations,
384+ Ext_task_lists,
384 Ext_auto_identifiers]
385 Ext_auto_identifiers]
385getDefaultExtensions "html" = extensionsFromList
386getDefaultExtensions "html" = extensionsFromList
386 [Ext_auto_identifiers,
387 [Ext_auto_identifiers,
@@ -515,6 +516,7 @@ getAllExtensions f = universalExtensions <> getAll f
515 extensionsFromList
516 extensionsFromList
516 [ Ext_citations
517 [ Ext_citations
517 , Ext_smart
518 , Ext_smart
519+ , Ext_task_lists
518 ]
520 ]
519 getAll "html" = autoIdExtensions <>
521 getAll "html" = autoIdExtensions <>
520 extensionsFromList
522 extensionsFromList
src/Text/Pandoc/Readers/Org/Blocks.hs+39−2
@@ -1,6 +1,7 @@
1{-# LANGUAGE FlexibleContexts #-}
1{-# LANGUAGE FlexibleContexts #-}
2−{-# LANGUAGE RecordWildCards #-}
2+{-# LANGUAGE LambdaCase #-}
3{-# LANGUAGE OverloadedStrings #-}
3{-# LANGUAGE OverloadedStrings #-}
4+{-# LANGUAGE RecordWildCards #-}
4{- |
5{- |
5 Module : Text.Pandoc.Readers.Org.Blocks
6 Module : Text.Pandoc.Readers.Org.Blocks
6 Copyright : Copyright (C) 2014-2021 Albert Krewinkel
7 Copyright : Copyright (C) 2014-2021 Albert Krewinkel
@@ -850,16 +851,52 @@ definitionListItem parseIndentedMarker = try $ do
850 definitionMarker =
851 definitionMarker =
851 spaceChar *> string "::" <* (spaceChar <|> lookAhead newline)
852 spaceChar *> string "::" <* (spaceChar <|> lookAhead newline)
852
853
854+-- | Checkbox for tasks.
855+data Checkbox
856+ = UncheckedBox
857+ | CheckedBox
858+ | SemicheckedBox
859+
860+-- | Parses a checkbox in a plain list.
861+checkbox :: PandocMonad m
862+ => OrgParser m Checkbox
863+checkbox = do
864+ guardEnabled Ext_task_lists
865+ try (char '[' *> status <* char ']') <?> "checkbox"
866+ where
867+ status = choice
868+ [ UncheckedBox <$ char ' '
869+ , CheckedBox <$ char 'X'
870+ , SemicheckedBox <$ char '-'
871+ ]
872+
873+checkboxToInlines :: Checkbox -> Inline
874+checkboxToInlines = B.Str . \case
875+ UncheckedBox -> "☐"
876+ SemicheckedBox -> "☐"
877+ CheckedBox -> "☒"
878+
853-- | parse raw text for one list item
879-- | parse raw text for one list item
854listItem :: PandocMonad m
880listItem :: PandocMonad m
855 => OrgParser m Int
881 => OrgParser m Int
856 -> OrgParser m (F Blocks)
882 -> OrgParser m (F Blocks)
857listItem parseIndentedMarker = try . withContext ListItemState $ do
883listItem parseIndentedMarker = try . withContext ListItemState $ do
858 markerLength <- try parseIndentedMarker
884 markerLength <- try parseIndentedMarker
885+ box <- optionMaybe checkbox
859 firstLine <- anyLineNewline
886 firstLine <- anyLineNewline
860 blank <- option "" ("\n" <$ blankline)
887 blank <- option "" ("\n" <$ blankline)
861 rest <- T.concat <$> many (listContinuation markerLength)
888 rest <- T.concat <$> many (listContinuation markerLength)
862− parseFromString blocks $ firstLine <> blank <> rest
889+ contents <- parseFromString blocks $ firstLine <> blank <> rest
890+ return (maybe id (prependInlines . checkboxToInlines) box <$> contents)
891+
892+-- | Prepend inlines to blocks, adding them to the first paragraph or
893+-- creating a new Plain element if necessary.
894+prependInlines :: Inline -> Blocks -> Blocks
895+prependInlines inlns = B.fromList . prepend . B.toList
896+ where
897+ prepend (Plain is : bs) = Plain (inlns : Space : is) : bs
898+ prepend (Para is : bs) = Para (inlns : Space : is) : bs
899+ prepend bs = Plain [inlns, Space] : bs
863
900
864-- continuation of a list item - indented and separated by blankline or endline.
901-- continuation of a list item - indented and separated by blankline or endline.
865-- Note: nested lists are parsed as continuations.
902-- Note: nested lists are parsed as continuations.
src/Text/Pandoc/Writers/Org.hs+13−3
@@ -213,25 +213,35 @@ blockToOrg (DefinitionList items) = do
213-- | Convert bullet list item (list of blocks) to Org.
213-- | Convert bullet list item (list of blocks) to Org.
214bulletListItemToOrg :: PandocMonad m => [Block] -> Org m (Doc Text)
214bulletListItemToOrg :: PandocMonad m => [Block] -> Org m (Doc Text)
215bulletListItemToOrg items = do
215bulletListItemToOrg items = do
216+ exts <- gets $ writerExtensions . stOptions
216− contents <- blockListToOrg items
217+ contents <- blockListToOrg (taskListItemToOrg exts items)
217 return $ hang 2 "- " contents $$
218 return $ hang 2 "- " contents $$
218 if endsWithPlain items
219 if endsWithPlain items
219 then cr
220 then cr
220 else blankline
221 else blankline
221
222
222−
223-- | Convert ordered list item (a list of blocks) to Org.
223-- | Convert ordered list item (a list of blocks) to Org.
224orderedListItemToOrg :: PandocMonad m
224orderedListItemToOrg :: PandocMonad m
225 => Text -- ^ marker for list item
225 => Text -- ^ marker for list item
226 -> [Block] -- ^ list item (list of blocks)
226 -> [Block] -- ^ list item (list of blocks)
227 -> Org m (Doc Text)
227 -> Org m (Doc Text)
228orderedListItemToOrg marker items = do
228orderedListItemToOrg marker items = do
229+ exts <- gets $ writerExtensions . stOptions
229− contents <- blockListToOrg items
230+ contents <- blockListToOrg (taskListItemToOrg exts items)
230 return $ hang (T.length marker + 1) (literal marker <> space) contents $$
231 return $ hang (T.length marker + 1) (literal marker <> space) contents $$
231 if endsWithPlain items
232 if endsWithPlain items
232 then cr
233 then cr
233 else blankline
234 else blankline
234
235
236+-- | Convert a list item containing text starting with @U+2610 BALLOT BOX@
237+-- or @U+2612 BALLOT BOX WITH X@ to org checkbox syntax (e.g. @[X]@).
238+taskListItemToOrg :: Extensions -> [Block] -> [Block]
239+taskListItemToOrg = handleTaskListItem toOrg
240+ where
241+ toOrg (Str "☐" : Space : is) = Str "[ ]" : Space : is
242+ toOrg (Str "☒" : Space : is) = Str "[X]" : Space : is
243+ toOrg is = is
244+
235-- | Convert definition list item (label, list of blocks) to Org.
245-- | Convert definition list item (label, list of blocks) to Org.
236definitionListItemToOrg :: PandocMonad m
246definitionListItemToOrg :: PandocMonad m
237 => ([Inline], [[Block]]) -> Org m (Doc Text)
247 => ([Inline], [[Block]]) -> Org m (Doc Text)
test/Tests/Readers/Org/Block/List.hs+13
@@ -118,6 +118,19 @@ tests =
118 ] =?>
118 ] =?>
119 bulletList [ plain "", plain "" ]
119 bulletList [ plain "", plain "" ]
120
120
121+ , "Task list" =:
122+ T.unlines [ "- [ ] nope"
123+ , "- [X] yup"
124+ , "- [-] started"
125+ , " 1. [X] sure"
126+ , " 2. [ ] nuh-uh"
127+ ] =?>
128+ bulletList [ plain "☐ nope", plain "☒ yup"
129+ , mconcat [ plain "☐ started"
130+ , orderedList [plain "☒ sure", plain "☐ nuh-uh"]
131+ ]
132+ ]
133+
121 , "Simple Ordered List" =:
134 , "Simple Ordered List" =:
122 ("1. Item1\n" <>
135 ("1. Item1\n" <>
123 "2. Item2\n") =?>
136 "2. Item2\n") =?>
test/Tests/Writers/Org.hs+46−11
@@ -1,6 +1,7 @@
1{-# LANGUAGE OverloadedStrings #-}
1{-# LANGUAGE OverloadedStrings #-}
2module Tests.Writers.Org (tests) where
2module Tests.Writers.Org (tests) where
3
3
4+import Data.Text as T
4import Test.Tasty
5import Test.Tasty
5import Tests.Helpers
6import Tests.Helpers
6import Text.Pandoc
7import Text.Pandoc
@@ -9,17 +10,51 @@ import Text.Pandoc.Builder
9
10
10infix 4 =:
11infix 4 =:
11(=:) :: (ToString a, ToPandoc a)
12(=:) :: (ToString a, ToPandoc a)
12− => String -> (a, String) -> TestTree
13+ => String -> (a, Text) -> TestTree
14+(=:) = test org
15+
16+defopts :: WriterOptions
17+defopts = def
18+ { writerExtensions = getDefaultExtensions "org"
19+ }
20+
21+org :: (ToPandoc a) => a -> Text
22+org = orgWithOpts defopts
23+
24+orgWithOpts :: (ToPandoc a) => WriterOptions -> a -> Text
13−(=:) = test (purely (writeOrg def . toPandoc))
25+orgWithOpts opts x = purely (writeOrg opts) $ toPandoc x
26+
14
27
15tests :: [TestTree]
28tests :: [TestTree]
29+tests =
16−tests = [ testGroup "links"
30+ [ testGroup "links"
17− -- See http://orgmode.org/manual/Internal-links.html#Internal-links
31+ -- See http://orgmode.org/manual/Internal-links.html#Internal-links
18− [ "simple link"
32+ [ "simple link"
19− =: link "/url" "" "foo"
33+ =: link "/url" "" "foo"
20− =?> "[[/url][foo]]"
34+ =?> "[[/url][foo]]"
21− , "internal link to anchor"
35+ , "internal link to anchor"
22− =: link "#my-custom-id" "" "#my-custom-id"
36+ =: link "#my-custom-id" "" "#my-custom-id"
23− =?> "[[#my-custom-id]]"
37+ =?> "[[#my-custom-id]]"
38+ ]
39+
40+ , testGroup "lists"
41+ [ "bullet task list"
42+ =: bulletList [plain "☐ a", plain "☒ b"]
43+ =?> T.unlines
44+ [ "- [ ] a"
45+ , "- [X] b"
46+ ]
47+ , "ordered task list"
48+ =: orderedList [plain ("☐" <> space <> "a"), plain "☒ b"]
49+ =?> T.unlines
50+ [ "1. [ ] a"
51+ , "2. [X] b"
52+ ]
53+ , test (orgWithOpts def) "bullet without task_lists" $
54+ bulletList [plain "☐ a", plain "☒ b"]
55+ =?> T.unlines
56+ [ "- ☐ a"
57+ , "- ☒ b"
24 ]
58 ]
59+ ]
25− ]
60+ ]