git.iko.soy/typeable/pandoc
on master help sign in
Merge pull request #4604 from mb21/yaml-file

Introduce --metadata-file option

commit6bd8037b8dc3a6e9d820d412d23fff99ec0c21a6 authorJohn MacFarlane <jgm@berkeley.edu> dateSat 15 Sep 2018, 14:32 (8 years ago) parentf736dea5347e94
6 files changed+150−50
MANUAL.txt+13−1 pandoc.cabal+1 src/Text/Pandoc/App.hs+17 src/Text/Pandoc/Readers/Markdown.hs+52−49 test/command/yaml-metadata-blocks.md+63 test/command/yaml-metadata.yaml+4
MANUAL.txt+13−1
@@ -578,6 +578,16 @@ Reader options
578578 printed in some output formats) and metadata values will be escaped
579579 when inserted into the template.
580580
581+`--metadata-file=`*FILE*
582+
583+: Read metadata from the supplied YAML (or JSON) file.
584+ This option can be used with every input format, but string
585+ scalars in the YAML file will always be parsed as Markdown.
586+ Generally, the input will be handled the same as in
587+ [YAML metadata blocks][Extension: `yaml_metadata_block`].
588+ Metadata values specified inside the document, or by using `-M`,
589+ overwrite values specified with this option.
590+
581591`-p`, `--preserve-tabs`
582592
583593: Preserve tabs instead of converting them to spaces (the default).
@@ -3061,7 +3071,9 @@ and pass it to pandoc as an argument, along with your Markdown files:
30613071 pandoc chap1.md chap2.md chap3.md metadata.yaml -s -o book.html
30623072
30633073Just be sure that the YAML file begins with `---` and ends with `---` or
3064−`...`.)
3074+`...`.) Alternatively, you can use the `--metadata-file` option. Using
3075+that approach however, you cannot reference content (like footnotes)
3076+from the main markdown input document.
30653077
30663078Metadata will be taken from the fields of the YAML object and added to any
30673079existing document metadata. Metadata can contain lists and objects (nested
pandoc.cabal+1
@@ -193,6 +193,7 @@ extra-source-files:
193193 test/command/sub-file-chapter-1.tex
194194 test/command/sub-file-chapter-2.tex
195195 test/command/bar.tex
196+ test/command/yaml-metadata.yaml
196197 test/command/3510-subdoc.org
197198 test/command/3510-export.latex
198199 test/command/3510-src.hs
src/Text/Pandoc/App.hs+17
@@ -89,6 +89,7 @@ import Text.Pandoc.Builder (setMeta, deleteMeta)
8989import Text.Pandoc.Filter (Filter (JSONFilter, LuaFilter), applyFilters)
9090import Text.Pandoc.Highlighting (highlightingStyles)
9191import Text.Pandoc.PDF (makePDF)
92+import Text.Pandoc.Readers.Markdown (yamlToMeta)
9293import Text.Pandoc.SelfContained (makeDataURI, makeSelfContained)
9394import Text.Pandoc.Shared (eastAsianLineBreakFilter, stripEmptyParagraphs,
9495 headerShift, isURI, ordNub, safeRead, tabFilter, uriPathToPath)
@@ -399,6 +400,10 @@ convertWithOpts opts = do
399400 ("application/xml", jatsCSL)
400401 return $ ("csl", jatsEncoded) : optMetadata opts
401402 else return $ optMetadata opts
403+ metadataFromFile <-
404+ case optMetadataFile opts of
405+ Nothing -> return mempty
406+ Just file -> readFileLazy file >>= yamlToMeta
402407
403408 case lookup "lang" (optMetadata opts) of
404409 Just l -> case parseBCP47 l of
@@ -491,6 +496,7 @@ convertWithOpts opts = do
491496 ( (if isJust (optExtractMedia opts)
492497 then fillMediaBag
493498 else return)
499+ >=> return . addNonPresentMetadata metadataFromFile
494500 >=> return . addMetadata metadata
495501 >=> applyTransforms transforms
496502 >=> applyFilters readerOpts filters' [format]
@@ -556,6 +562,7 @@ data Opt = Opt
556562 , optTemplate :: Maybe FilePath -- ^ Custom template
557563 , optVariables :: [(String,String)] -- ^ Template variables to set
558564 , optMetadata :: [(String, String)] -- ^ Metadata fields to set
565+ , optMetadataFile :: Maybe FilePath -- ^ Name of YAML metadata file
559566 , optOutputFile :: Maybe FilePath -- ^ Name of output file
560567 , optInputFiles :: [FilePath] -- ^ Names of input files
561568 , optNumberSections :: Bool -- ^ Number sections in LaTeX
@@ -628,6 +635,7 @@ defaultOpts = Opt
628635 , optTemplate = Nothing
629636 , optVariables = []
630637 , optMetadata = []
638+ , optMetadataFile = Nothing
631639 , optOutputFile = Nothing
632640 , optInputFiles = []
633641 , optNumberSections = False
@@ -687,6 +695,9 @@ defaultOpts = Opt
687695 , optStripComments = False
688696 }
689697
698+addNonPresentMetadata :: Text.Pandoc.Meta -> Pandoc -> Pandoc
699+addNonPresentMetadata newmeta (Pandoc meta bs) = Pandoc (meta <> newmeta) bs
700+
690701addMetadata :: [(String, String)] -> Pandoc -> Pandoc
691702addMetadata kvs pdc = foldr addMeta (removeMetaKeys kvs pdc) kvs
692703
@@ -963,6 +974,12 @@ options =
963974 "KEY[:VALUE]")
964975 ""
965976
977+ , Option "" ["metadata-file"]
978+ (ReqArg
979+ (\arg opt -> return opt{ optMetadataFile = Just arg })
980+ "FILE")
981+ ""
982+
966983 , Option "V" ["variable"]
967984 (ReqArg
968985 (\arg opt -> do
src/Text/Pandoc/Readers/Markdown.hs+52−49
@@ -31,11 +31,12 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
3131
3232Conversion of markdown-formatted plain text to 'Pandoc' document.
3333-}
34−module Text.Pandoc.Readers.Markdown ( readMarkdown ) where
34+module Text.Pandoc.Readers.Markdown ( readMarkdown, yamlToMeta ) where
3535
3636import Prelude
3737import Control.Monad
3838import Control.Monad.Except (throwError)
39+import qualified Data.ByteString.Lazy as BS
3940import Data.Char (isAlphaNum, isPunctuation, isSpace, toLower)
4041import Data.List (intercalate, sortBy, transpose, elemIndex)
4142import qualified Data.Map as M
@@ -233,7 +234,6 @@ pandocTitleBlock = try $ do
233234yamlMetaBlock :: PandocMonad m => MarkdownParser m (F Blocks)
234235yamlMetaBlock = try $ do
235236 guardEnabled Ext_yaml_metadata_block
236− pos <- getPosition
237237 string "---"
238238 blankline
239239 notFollowedBy blankline -- if --- is followed by a blank it's an HRULE
@@ -241,47 +241,45 @@ yamlMetaBlock = try $ do
241241 -- by including --- and ..., we allow yaml blocks with just comments:
242242 let rawYaml = unlines ("---" : (rawYamlLines ++ ["..."]))
243243 optional blanklines
244− case YAML.decodeNode' YAML.failsafeSchemaResolver False False
245− (UTF8.fromStringLazy rawYaml) of
246− Right [YAML.Doc (YAML.Mapping _ hashmap)] ->
247− mapM_ (\(key, v) -> do
248− k <- nodeToKey key
249− if ignorable k
250− then return ()
251− else do
252− v' <- yamlToMeta v
253− let k' = T.unpack k
254− updateState $ \st -> st{ stateMeta' =
255− do m <- stateMeta' st
256− -- if there's already a value, leave it unchanged
257− case lookupMeta k' m of
258− Just _ -> return m
259− Nothing -> do
260− v'' <- v'
261− return $ B.setMeta (T.unpack k) v'' m})
262− (M.toList hashmap)
263− Right [] -> return ()
264− Right [YAML.Doc (YAML.Scalar YAML.SNull)] -> return ()
244+ newMetaF <- yamlBsToMeta $ UTF8.fromStringLazy rawYaml
245+ -- Since `<>` is left-biased, existing values are not touched:
246+ updateState $ \st -> st{ stateMeta' = (stateMeta' st) <> newMetaF }
247+ return mempty
248+
249+-- | Read a YAML string and convert it to pandoc metadata.
250+-- String scalars in the YAML are parsed as Markdown.
251+yamlToMeta :: PandocMonad m => BS.ByteString -> m Meta
252+yamlToMeta bstr = do
253+ let parser = do
254+ meta <- yamlBsToMeta bstr
255+ return $ runF meta defaultParserState
256+ parsed <- readWithM parser def ""
257+ case parsed of
258+ Right result -> return result
259+ Left e -> throwError e
260+
261+yamlBsToMeta :: PandocMonad m => BS.ByteString -> MarkdownParser m (F Meta)
262+yamlBsToMeta bstr = do
263+ pos <- getPosition
264+ case YAML.decodeNode' YAML.failsafeSchemaResolver False False bstr of
265+ Right ((YAML.Doc (YAML.Mapping _ o)):_) -> (fmap Meta) <$> yamlMap o
266+ Right [] -> return . return $ mempty
267+ Right [YAML.Doc (YAML.Scalar YAML.SNull)] -> return . return $ mempty
265268 Right _ -> do
266269 logMessage $
267270 CouldNotParseYamlMetadata "not an object"
268271 pos
269− return ()
272+ return . return $ mempty
270273 Left err' -> do
271274 logMessage $ CouldNotParseYamlMetadata
272275 err' pos
273− return ()
274− return mempty
276+ return . return $ mempty
275277
276278nodeToKey :: Monad m => YAML.Node -> m Text
277279nodeToKey (YAML.Scalar (YAML.SStr t)) = return t
278280nodeToKey (YAML.Scalar (YAML.SUnknown _ t)) = return t
279281nodeToKey _ = fail "Non-string key in YAML mapping"
280282
281−-- ignore fields ending with _
282−ignorable :: Text -> Bool
283−ignorable t = (T.pack "_") `T.isSuffixOf` t
284−
285283toMetaValue :: PandocMonad m
286284 => Text -> MarkdownParser m (F MetaValue)
287285toMetaValue x =
@@ -309,9 +307,9 @@ checkBoolean t =
309307 then Just False
310308 else Nothing
311309
312−yamlToMeta :: PandocMonad m
313− => YAML.Node -> MarkdownParser m (F MetaValue)
314−yamlToMeta (YAML.Scalar x) =
310+yamlToMetaValue :: PandocMonad m
311+ => YAML.Node -> MarkdownParser m (F MetaValue)
312+yamlToMetaValue (YAML.Scalar x) =
315313 case x of
316314 YAML.SStr t -> toMetaValue t
317315 YAML.SBool b -> return $ return $ MetaBool b
@@ -322,25 +320,30 @@ yamlToMeta (YAML.Scalar x) =
322320 Just b -> return $ return $ MetaBool b
323321 Nothing -> toMetaValue t
324322 YAML.SNull -> return $ return $ MetaString ""
325−yamlToMeta (YAML.Sequence _ xs) = do
326− xs' <- mapM yamlToMeta xs
323+yamlToMetaValue (YAML.Sequence _ xs) = do
324+ xs' <- mapM yamlToMetaValue xs
327325 return $ do
328326 xs'' <- sequence xs'
329327 return $ B.toMetaValue xs''
330−yamlToMeta (YAML.Mapping _ o) =
331− foldM (\m (key, v) -> do
332− k <- nodeToKey key
333− if ignorable k
334− then return m
335− else do
336− v' <- yamlToMeta v
337− return $ do
338− MetaMap m' <- m
339− v'' <- v'
340− return (MetaMap $ M.insert (T.unpack k) v'' m'))
341− (return $ MetaMap M.empty)
342− (M.toList o)
343−yamlToMeta _ = return $ return $ MetaString ""
328+yamlToMetaValue (YAML.Mapping _ o) = fmap B.toMetaValue <$> yamlMap o
329+yamlToMetaValue _ = return $ return $ MetaString ""
330+
331+yamlMap :: PandocMonad m
332+ => M.Map YAML.Node YAML.Node
333+ -> MarkdownParser m (F (M.Map String MetaValue))
334+yamlMap o = do
335+ kvs <- forM (M.toList o) $ \(key, v) -> do
336+ k <- nodeToKey key
337+ return (k, v)
338+ let kvs' = filter (not . ignorable . fst) kvs
339+ (fmap M.fromList . sequence) <$> mapM toMeta kvs'
340+ where
341+ ignorable t = (T.pack "_") `T.isSuffixOf` t
342+ toMeta (k, v) = do
343+ fv <- yamlToMetaValue v
344+ return $ do
345+ v' <- fv
346+ return (T.unpack k, v')
344347
345348stopLine :: PandocMonad m => MarkdownParser m ()
346349stopLine = try $ (string "---" <|> string "...") >> blankline >> return ()
test/command/yaml-metadata-blocks.mdnew file+63
@@ -0,0 +1,63 @@
1+```
2+% pandoc -s -t native
3+---
4+foobar_: this should be ignored
5+foo:
6+ bar_: as should this
7+---
8+^D
9+Pandoc (Meta {unMeta = fromList [("foo",MetaMap (fromList []))]})
10+[]
11+```
12+```
13+% pandoc -s -t native
14+---
15+# For precedence, see multiple-metadata-blocks.md and vars-and-metadata.md
16+# For Bools, see also 4819.md
17+# For Multiline strings, see yaml-with-chomp.md
18+int: 7
19+float: 1.5
20+scientific: 3.7e-5
21+bool: true
22+more: False
23+nothing: null
24+emtpy: []
25+nested:
26+ int: 8
27+ float: 2.5
28+ bool: true
29+ more: False
30+ nothing: null
31+ emtpy: []
32+ scientific: 3.7e-5
33+---
34+^D
35+Pandoc (Meta {unMeta = fromList [("bool",MetaBool True),("emtpy",MetaList []),("float",MetaInlines [Str "1.5"]),("int",MetaInlines [Str "7"]),("more",MetaBool False),("nested",MetaMap (fromList [("bool",MetaBool True),("emtpy",MetaList []),("float",MetaInlines [Str "2.5"]),("int",MetaInlines [Str "8"]),("more",MetaBool False),("nothing",MetaInlines [Str "null"]),("scientific",MetaInlines [Str "3.7e-5"])])),("nothing",MetaInlines [Str "null"]),("scientific",MetaInlines [Str "3.7e-5"])]})
36+[]
37+```
38+```
39+% pandoc -s -t native
40+---
41+array:
42+ - foo: bar
43+ - bool: True
44+---
45+^D
46+Pandoc (Meta {unMeta = fromList [("array",MetaList [MetaMap (fromList [("foo",MetaInlines [Str "bar"])]),MetaMap (fromList [("bool",MetaBool True)])])]})
47+[]
48+```
49+```
50+% pandoc -s -t native --metadata-file command/yaml-metadata.yaml
51+---
52+title: document
53+---
54+^D
55+Pandoc (Meta {unMeta = fromList [("other",MetaInlines [Emph [Str "markdown"],Space,Str "value"]),("title",MetaInlines [Str "document"])]})
56+[]
57+```
58+```
59+% pandoc -s -t native --metadata-file command/yaml-metadata.yaml -M title=cmdline
60+^D
61+Pandoc (Meta {unMeta = fromList [("other",MetaInlines [Emph [Str "markdown"],Space,Str "value"]),("title",MetaString "cmdline")]})
62+[]
63+```
test/command/yaml-metadata.yamlnew file+4
@@ -0,0 +1,4 @@
1+---
2+title: file
3+other: _markdown_ value
4+---