From df00cf05cbf817b7d8e7de0a4a220dd70f8a9608 Mon Sep 17 00:00:00 2001
From: John MacFarlane <jgm@berkeley.edu>
Date: Fri, 5 Mar 2021 11:56:41 -0800
Subject: [PATCH] Allow `${.}` in defaults files paths...

to refer to the directory where the default file is.
This will make it possible to create moveable
"packages" of resources in a directory.

Closes #5871.
---
 MANUAL.txt                 | 13 +++++++++++++
 src/Text/Pandoc/App/Opt.hs | 13 +++++++++++--
 2 files changed, 24 insertions(+), 2 deletions(-)

diff --git a/MANUAL.txt b/MANUAL.txt
index f06293dd3..8e98ee1eb 100644
--- a/MANUAL.txt
+++ b/MANUAL.txt
@@ -1684,6 +1684,19 @@ user data directory that is current when the defaults file is
 parsed, regardless of the setting of the environment
 variable `USERDATA`.
 
+`${.}` will resolve to the directory containing the default
+file itself.  This allows you to refer to resources contained
+in that directory:
+
+``` yaml
+epub-cover-image: ${.}/cover.jpg
+epub-metadata: ${.}/meta.xml
+resource-path:
+- .             # the working directory from which pandoc is run
+- ${.}/images   # the images subdirectory of the directory
+                # containing this defaults file
+```
+
 This environment variable interpolation syntax *only* works in
 fields that expect file paths.
 
diff --git a/src/Text/Pandoc/App/Opt.hs b/src/Text/Pandoc/App/Opt.hs
index b56b2c377..d09a6afc0 100644
--- a/src/Text/Pandoc/App/Opt.hs
+++ b/src/Text/Pandoc/App/Opt.hs
@@ -27,7 +27,8 @@ module Text.Pandoc.App.Opt (
           ) where
 import Control.Monad.Except (MonadIO, liftIO, throwError, (>=>), foldM)
 import Control.Monad.State.Strict (StateT, modify, gets)
-import System.FilePath ( addExtension, (</>), takeExtension )
+import System.FilePath ( addExtension, (</>), takeExtension, takeDirectory )
+import System.Directory ( canonicalizePath )
 import Data.Char (isLower, toLower)
 import Data.Maybe (fromMaybe)
 import GHC.Generics hiding (Meta)
@@ -189,7 +190,8 @@ instance (PandocMonad m, MonadIO m)
       toText _ = ""
   parseYAML n = failAtNode n "Expected a mapping"
 
-resolveVarsInOpt :: (PandocMonad m, MonadIO m) => Opt -> m Opt
+resolveVarsInOpt :: forall m. (PandocMonad m, MonadIO m)
+                 => Opt -> StateT DefaultsState m Opt
 resolveVarsInOpt
     opt@Opt
     { optTemplate              = oTemplate
@@ -263,6 +265,7 @@ resolveVarsInOpt
                 }
 
  where
+  resolveVars :: FilePath -> StateT DefaultsState m FilePath
   resolveVars [] = return []
   resolveVars ('$':'{':xs) =
     let (ys, zs) = break (=='}') xs
@@ -272,6 +275,12 @@ resolveVarsInOpt
              val <- lookupEnv' ys
              (val ++) <$> resolveVars (drop 1 zs)
   resolveVars (c:cs) = (c:) <$> resolveVars cs
+  lookupEnv' :: String -> StateT DefaultsState m String
+  lookupEnv' "." = do
+    mbCurDefaults <- gets curDefaults
+    maybe (return "")
+          (fmap takeDirectory . liftIO . canonicalizePath)
+          mbCurDefaults
   lookupEnv' "USERDATA" = do
     mbodatadir <- mapM resolveVars oDataDir
     mbdatadir  <- getUserDataDir