git.iko.soy/typeable/pandoc
on master help sign in
Implement environment variable interpolation in defaults files.

This allows the syntax `${HOME}` to be used, in fields that expect file paths only. Any environment variable may be interpolated in this way. A warning will be raised for undefined variables. The special variable `USERDATA` is automatically set to the user data directory in force when the defaults file is parsed. (Note: it may be different from the eventual user data directory, if the defaults file or further command line options change that.) Closes #5982. Closes #5977. Closes #6108 (path not taken).

commit6dd7520cc4b3816ae13ec486ce0909b9b881d240 authorJohn MacFarlane <jgm@berkeley.edu> dateFri 5 Mar 2021, 10:44 (6 years ago) parenta832469
3 files changed+147−14
MANUAL.txt+15 src/Text/Pandoc/App.hs+21−8 src/Text/Pandoc/App/Opt.hs+111−6
MANUAL.txt+15
@@ -1672,6 +1672,21 @@ one line:
16721672verbosity: INFO
16731673```
16741674
1675+In fields that expect a file path (or list of file paths), the
1676+following syntax may be used to interpolate environment variables:
1677+
1678+``` yaml
1679+csl: ${HOME}/mycsldir/special.csl
1680+```
1681+
1682+`${USERDATA}` may also be used; this will always resolve to the
1683+user data directory that is current when the defaults file is
1684+parsed, regardless of the setting of the environment
1685+variable `USERDATA`.
1686+
1687+This environment variable interpolation syntax *only* works in
1688+fields that expect file paths.
1689+
16751690Default files can be placed in the `defaults` subdirectory of
16761691the user data directory and used from any directory. For
16771692example, one could create a file specifying defaults for writing
src/Text/Pandoc/App.hs+21−8
@@ -47,10 +47,11 @@ import System.FilePath ( takeBaseName, takeExtension )
4747import System.IO (nativeNewline, stdout)
4848import qualified System.IO as IO (Newline (..))
4949import Text.Pandoc
50+import Text.Pandoc.Builder (setMeta)
5051import Text.Pandoc.MIME (getCharset)
5152import Text.Pandoc.App.FormatHeuristics (formatFromFilePaths)
5253import Text.Pandoc.App.Opt (Opt (..), LineEnding (..), defaultOpts,
53− IpynbOutput (..) )
54+ IpynbOutput (..))
5455import Text.Pandoc.App.CommandLineOptions (parseOptions, parseOptionsFromArgs,
5556 options)
5657import Text.Pandoc.App.OutputSettings (OutputSettings (..), optToOutputSettings)
@@ -60,7 +61,7 @@ import Text.Pandoc.PDF (makePDF)
6061import Text.Pandoc.SelfContained (makeSelfContained)
6162import Text.Pandoc.Shared (eastAsianLineBreakFilter, stripEmptyParagraphs,
6263 headerShift, isURI, tabFilter, uriPathToPath, filterIpynbOutput,
63− defaultUserDataDirs, tshow, findM)
64+ defaultUserDataDir, tshow)
6465import Text.Pandoc.Writers.Shared (lookupMetaString)
6566import Text.Pandoc.Readers.Markdown (yamlToMeta)
6667import qualified Text.Pandoc.UTF8 as UTF8
@@ -71,6 +72,15 @@ import System.Posix.Terminal (queryTerminal)
7172
7273convertWithOpts :: Opt -> IO ()
7374convertWithOpts opts = do
75+ datadir <- case optDataDir opts of
76+ Nothing -> do
77+ d <- defaultUserDataDir
78+ exists <- doesDirectoryExist d
79+ return $ if exists
80+ then Just d
81+ else Nothing
82+ Just _ -> return $ optDataDir opts
83+
7484 let outputFile = fromMaybe "-" (optOutputFile opts)
7585 let filters = optFilters opts
7686 let verbosity = optVerbosity opts
@@ -85,12 +95,6 @@ convertWithOpts opts = do
8595 Just xs | not (optIgnoreArgs opts) -> xs
8696 _ -> ["-"]
8797
88− datadir <- case optDataDir opts of
89− Nothing -> do
90− ds <- defaultUserDataDirs
91− findM doesDirectoryExist ds
92− Just _ -> return $ optDataDir opts
93−
9498 let runIO' :: PandocIO a -> IO a
9599 runIO' f = do
96100 (res, reports) <- runIOorExplode $ do
@@ -275,12 +279,21 @@ convertWithOpts opts = do
275279 report $ Deprecated "pandoc-citeproc filter"
276280 "Use --citeproc instead."
277281
282+ let cslMetadata =
283+ maybe id (setMeta "csl") (optCSL opts) .
284+ (case optBibliography opts of
285+ [] -> id
286+ xs -> setMeta "bibliography" xs) .
287+ maybe id (setMeta "citation-abbreviations")
288+ (optCitationAbbreviations opts) $ mempty
289+
278290 doc <- sourceToDoc sources >>=
279291 ( (if isJust (optExtractMedia opts)
280292 then fillMediaBag
281293 else return)
282294 >=> return . adjustMetadata (metadataFromFile <>)
283295 >=> return . adjustMetadata (<> optMetadata opts)
296+ >=> return . adjustMetadata (<> cslMetadata)
284297 >=> applyTransforms transforms
285298 >=> applyFilters readerOpts filters [T.unpack format]
286299 >=> maybe return extractMedia (optExtractMedia opts)
src/Text/Pandoc/App/Opt.hs+111−6
@@ -32,16 +32,18 @@ import Data.Char (isLower, toLower)
3232import Data.Maybe (fromMaybe)
3333import GHC.Generics hiding (Meta)
3434import Text.Pandoc.Filter (Filter (..))
35−import Text.Pandoc.Logging (Verbosity (WARNING))
35+import Text.Pandoc.Logging (Verbosity (WARNING), LogMessage(..))
3636import Text.Pandoc.Options (TopLevelDivision (TopLevelDefault),
3737 TrackChanges (AcceptChanges),
3838 WrapOption (WrapAuto), HTMLMathMethod (PlainMath),
3939 ReferenceLocation (EndOfDocument),
4040 ObfuscationMethod (NoObfuscation),
4141 CiteMethod (Citeproc))
42−import Text.Pandoc.Class (readFileLazy, fileExists, setVerbosity, PandocMonad)
42+import Text.Pandoc.Class (readFileLazy, fileExists, setVerbosity, report,
43+ PandocMonad(lookupEnv), getUserDataDir)
4344import Text.Pandoc.Error (PandocError (PandocParseError, PandocSomeError))
44−import Text.Pandoc.Shared (camelCaseStrToHyphenated, defaultUserDataDirs, findM, ordNub)
45+import Text.Pandoc.Shared (camelCaseStrToHyphenated, defaultUserDataDir,
46+ findM, ordNub)
4547import qualified Text.Pandoc.Parsing as P
4648import Text.Pandoc.Readers.Metadata (yamlMap)
4749import Text.Pandoc.Class.PandocPure
@@ -176,17 +178,120 @@ instance (PandocMonad m, MonadIO m)
176178 dataDir <- case M.lookup "data-dir" opts of
177179 Nothing -> return Nothing
178180 Just v -> Just . unpack <$> parseYAML v
179− f <- parseOptions $ M.toList m
181+ f <- parseOptions (M.toList m)
180182 case M.lookup "defaults" opts of
181183 Just v -> do
182184 g <- parseDefaults v dataDir
183− return $ g >=> f
184− Nothing -> return f
185+ return $ g >=> f >=> resolveVarsInOpt
186+ Nothing -> return $ f >=> resolveVarsInOpt
185187 where
186188 toText (Scalar _ (SStr s)) = s
187189 toText _ = ""
188190 parseYAML n = failAtNode n "Expected a mapping"
189191
192+resolveVarsInOpt :: (PandocMonad m, MonadIO m) => Opt -> m Opt
193+resolveVarsInOpt
194+ opt@Opt
195+ { optTemplate = oTemplate
196+ , optMetadataFiles = oMetadataFiles
197+ , optOutputFile = oOutputFile
198+ , optInputFiles = oInputFiles
199+ , optSyntaxDefinitions = oSyntaxDefinitions
200+ , optAbbreviations = oAbbreviations
201+ , optReferenceDoc = oReferenceDoc
202+ , optEpubMetadata = oEpubMetadata
203+ , optEpubFonts = oEpubFonts
204+ , optEpubCoverImage = oEpubCoverImage
205+ , optLogFile = oLogFile
206+ , optFilters = oFilters
207+ , optDataDir = oDataDir
208+ , optExtractMedia = oExtractMedia
209+ , optCss = oCss
210+ , optIncludeBeforeBody = oIncludeBeforeBody
211+ , optIncludeAfterBody = oIncludeAfterBody
212+ , optIncludeInHeader = oIncludeInHeader
213+ , optResourcePath = oResourcePath
214+ , optCSL = oCSL
215+ , optBibliography = oBibliography
216+ , optCitationAbbreviations = oCitationAbbreviations
217+ }
218+ = do
219+ oTemplate' <- mapM resolveVars oTemplate
220+ oMetadataFiles' <- mapM resolveVars oMetadataFiles
221+ oOutputFile' <- mapM resolveVars oOutputFile
222+ oInputFiles' <- mapM (mapM resolveVars) oInputFiles
223+ oSyntaxDefinitions' <- mapM resolveVars oSyntaxDefinitions
224+ oAbbreviations' <- mapM resolveVars oAbbreviations
225+ oReferenceDoc' <- mapM resolveVars oReferenceDoc
226+ oEpubMetadata' <- mapM resolveVars oEpubMetadata
227+ oEpubFonts' <- mapM resolveVars oEpubFonts
228+ oEpubCoverImage' <- mapM resolveVars oEpubCoverImage
229+ oLogFile' <- mapM resolveVars oLogFile
230+ oFilters' <- mapM resolveVarsInFilter oFilters
231+ oDataDir' <- mapM resolveVars oDataDir
232+ oExtractMedia' <- mapM resolveVars oExtractMedia
233+ oCss' <- mapM resolveVars oCss
234+ oIncludeBeforeBody' <- mapM resolveVars oIncludeBeforeBody
235+ oIncludeAfterBody' <- mapM resolveVars oIncludeAfterBody
236+ oIncludeInHeader' <- mapM resolveVars oIncludeInHeader
237+ oResourcePath' <- mapM resolveVars oResourcePath
238+ oCSL' <- mapM resolveVars oCSL
239+ oBibliography' <- mapM resolveVars oBibliography
240+ oCitationAbbreviations' <- mapM resolveVars oCitationAbbreviations
241+ return opt{ optTemplate = oTemplate'
242+ , optMetadataFiles = oMetadataFiles'
243+ , optOutputFile = oOutputFile'
244+ , optInputFiles = oInputFiles'
245+ , optSyntaxDefinitions = oSyntaxDefinitions'
246+ , optAbbreviations = oAbbreviations'
247+ , optReferenceDoc = oReferenceDoc'
248+ , optEpubMetadata = oEpubMetadata'
249+ , optEpubFonts = oEpubFonts'
250+ , optEpubCoverImage = oEpubCoverImage'
251+ , optLogFile = oLogFile'
252+ , optFilters = oFilters'
253+ , optDataDir = oDataDir'
254+ , optExtractMedia = oExtractMedia'
255+ , optCss = oCss'
256+ , optIncludeBeforeBody = oIncludeBeforeBody'
257+ , optIncludeAfterBody = oIncludeAfterBody'
258+ , optIncludeInHeader = oIncludeInHeader'
259+ , optResourcePath = oResourcePath'
260+ , optCSL = oCSL'
261+ , optBibliography = oBibliography'
262+ , optCitationAbbreviations = oCitationAbbreviations'
263+ }
264+
265+ where
266+ resolveVars [] = return []
267+ resolveVars ('$':'{':xs) =
268+ let (ys, zs) = break (=='}') xs
269+ in if null zs
270+ then return $ '$':'{':xs
271+ else do
272+ val <- lookupEnv' ys
273+ (val ++) <$> resolveVars (drop 1 zs)
274+ resolveVars (c:cs) = (c:) <$> resolveVars cs
275+ lookupEnv' "USERDATA" = do
276+ mbodatadir <- mapM resolveVars oDataDir
277+ mbdatadir <- getUserDataDir
278+ defdatadir <- liftIO defaultUserDataDir
279+ return $ fromMaybe defdatadir (mbodatadir <|> mbdatadir)
280+ lookupEnv' v = do
281+ mbval <- fmap T.unpack <$> lookupEnv (T.pack v)
282+ case mbval of
283+ Nothing -> do
284+ report $ EnvironmentVariableUndefined (T.pack v)
285+ return mempty
286+ Just x -> return x
287+ resolveVarsInFilter (JSONFilter fp) =
288+ JSONFilter <$> resolveVars fp
289+ resolveVarsInFilter (LuaFilter fp) =
290+ LuaFilter <$> resolveVars fp
291+ resolveVarsInFilter CiteprocFilter = return CiteprocFilter
292+
293+
294+
190295parseDefaults :: (PandocMonad m, MonadIO m)
191296 => Node Pos
192297 -> Maybe FilePath