mirror of
https://github.com/xmonad/xmonad-contrib.git
synced 2025-05-19 11:30:22 -07:00
The links were broken due to: 1. Incorrect quotes (' instead of " for module links and occasionally vice-versa). 2. Changes in the name of the "target" module not reflected in the "source" docs. 3. Typos to begin with. 4. Use of `<foo>` in the docs is rendered as just `foo` with a link to `/foo`. 5. Similarly for `"Foo"` if it starts with a capital letter (and hence could be a module). 6. Markup inside `@` code blocks still being applied. e.g. `@M-<arrow-keys>@` is rendered as `M-arrow-keys` with a spurious hyperlink from arrow-keys to `/arrow-keys`, which is confusing. Three links from XMonad.Util.Run have been removed outright, since they're no longer examples of the usage of 'runProcessWithInput'. WmiiActions has been gone since 2008, while XMonad.Prompt.Directory and XMonad.Layout.WorkspaceDir haven't been using 'runProcessWithInput' since 2020 and 2012, respectively. In some cases the `<foo>` were surrounded with @, especially in the case of key definitions, for consistency. (This wasn't done everywhere, because it looks ugly in the source.) MoreManageHelpers has never been in xmonad-contrib. ManageHelpers seems to fill the expected role. In the case of the module description for X.H.ManageDebug the quotes were simply removed because none of the likely options to make the link work were successful.
538 lines
18 KiB
Haskell
538 lines
18 KiB
Haskell
{-# LANGUAGE InstanceSigs #-}
|
|
{-# LANGUAGE LambdaCase #-}
|
|
{-# LANGUAGE NamedFieldPuns #-}
|
|
{-# LANGUAGE ScopedTypeVariables #-}
|
|
-----------------------------------------------------------------------------
|
|
-- |
|
|
-- Module : XMonad.Util.Run
|
|
-- Description : Several commands, as well as an EDSL, to run external processes.
|
|
-- Copyright : (C) 2007 Spencer Janssen, Andrea Rossato, glasser@mit.edu
|
|
-- 2022 Tony Zorman
|
|
-- License : BSD-style (see LICENSE)
|
|
--
|
|
-- Maintainer : Tony Zorman <soliditsallgood@mailbox.org>
|
|
-- Stability : unstable
|
|
-- Portability : unportable
|
|
--
|
|
-- This module provides several commands to run an external process.
|
|
-- Additionally, it provides an abstraction—particularly geared towards
|
|
-- programs like terminals or Emacs—to specify these processes from
|
|
-- XMonad in a compositional way.
|
|
--
|
|
-- Originally, this module was composed of functions formerly defined in
|
|
-- "XMonad.Util.Dmenu" (by Spencer Janssen), "XMonad.Util.Dzen" (by
|
|
-- glasser\@mit.edu) and @XMonad.Util.RunInXTerm@ (by Andrea Rossato).
|
|
-----------------------------------------------------------------------------
|
|
|
|
module XMonad.Util.Run (
|
|
-- * Usage
|
|
-- $usage
|
|
runProcessWithInput,
|
|
runProcessWithInputAndWait,
|
|
safeSpawn,
|
|
safeSpawnProg,
|
|
unsafeSpawn,
|
|
runInTerm,
|
|
safeRunInTerm,
|
|
seconds,
|
|
spawnPipe,
|
|
spawnPipeWithLocaleEncoding,
|
|
spawnPipeWithUtf8Encoding,
|
|
spawnPipeWithNoEncoding,
|
|
|
|
-- * Compositionally Spawning Processes #EDSL#
|
|
-- $EDSL
|
|
|
|
-- ** Configuration and Running
|
|
ProcessConfig (..),
|
|
Input,
|
|
spawnExternalProcess,
|
|
proc,
|
|
getInput,
|
|
|
|
-- ** Programs
|
|
inEditor,
|
|
inTerm,
|
|
termInDir,
|
|
inProgram,
|
|
|
|
-- ** General Combinators
|
|
(>->),
|
|
(>-$),
|
|
inWorkingDir,
|
|
eval,
|
|
execute,
|
|
executeNoQuote,
|
|
setXClass,
|
|
asString,
|
|
|
|
-- ** Emacs Integration
|
|
EmacsLib (..),
|
|
setFrameName,
|
|
withEmacsLibs,
|
|
inEmacs,
|
|
elispFun,
|
|
asBatch,
|
|
require,
|
|
progn,
|
|
quote,
|
|
findFile,
|
|
list,
|
|
saveExcursion,
|
|
|
|
-- * Re-exports
|
|
hPutStr,
|
|
hPutStrLn,
|
|
) where
|
|
|
|
import XMonad
|
|
import XMonad.Prelude
|
|
import qualified XMonad.Util.ExtensibleConf as XC
|
|
|
|
import Codec.Binary.UTF8.String (encodeString)
|
|
import Control.Concurrent (threadDelay)
|
|
import System.Directory (getDirectoryContents)
|
|
import System.IO
|
|
import System.Posix.IO
|
|
import System.Posix.Process (createSession, executeFile, forkProcess)
|
|
import System.Process (runInteractiveProcess)
|
|
|
|
{- $usage
|
|
|
|
You can use this module by importing it in your @xmonad.hs@
|
|
|
|
> import XMonad.Util.Run
|
|
|
|
It then all depends on what you want to do:
|
|
|
|
- If you want to compositionally spawn programs, see [the relevant
|
|
extended documentation](#g:EDSL).
|
|
|
|
- For an example usage of 'runInTerm' see "XMonad.Prompt.Ssh".
|
|
|
|
- For an example usage of 'runProcessWithInput' see
|
|
"XMonad.Util.Dmenu", or "XMonad.Prompt.Shell".
|
|
|
|
- For an example usage of 'runProcessWithInputAndWait' see
|
|
"XMonad.Util.Dzen".
|
|
-}
|
|
|
|
-- | Returns the output.
|
|
runProcessWithInput :: MonadIO m => FilePath -> [String] -> String -> m String
|
|
runProcessWithInput cmd args input = io $ do
|
|
(pin, pout, perr, _) <- runInteractiveProcess (encodeString cmd)
|
|
(map encodeString args) Nothing Nothing
|
|
hPutStr pin input
|
|
hClose pin
|
|
output <- hGetContents pout
|
|
when (output == output) $ return ()
|
|
hClose pout
|
|
hClose perr
|
|
-- no need to waitForProcess, we ignore SIGCHLD
|
|
return output
|
|
|
|
-- | Wait is in μ (microseconds)
|
|
runProcessWithInputAndWait :: MonadIO m => FilePath -> [String] -> String -> Int -> m ()
|
|
runProcessWithInputAndWait cmd args input timeout = io $ do
|
|
_ <- xfork $ do
|
|
(pin, pout, perr, _) <- runInteractiveProcess (encodeString cmd)
|
|
(map encodeString args) Nothing Nothing
|
|
hPutStr pin input
|
|
hFlush pin
|
|
threadDelay timeout
|
|
hClose pin
|
|
hClose pout
|
|
hClose perr
|
|
-- no need to waitForProcess, we ignore SIGCHLD
|
|
return ()
|
|
return ()
|
|
|
|
-- | Multiplies by ONE MILLION, for functions that take microseconds.
|
|
--
|
|
-- Use like:
|
|
--
|
|
-- > (5.5 `seconds`)
|
|
--
|
|
-- In GHC 7 and later, you must either enable the PostfixOperators extension
|
|
-- (by adding
|
|
--
|
|
-- > {-# LANGUAGE PostfixOperators #-}
|
|
--
|
|
-- to the top of your file) or use seconds in prefix form:
|
|
--
|
|
-- > seconds 5.5
|
|
seconds :: Rational -> Int
|
|
seconds = fromEnum . (* 1000000)
|
|
|
|
{- | 'safeSpawn' bypasses 'spawn', because spawn passes
|
|
strings to \/bin\/sh to be interpreted as shell commands. This is
|
|
often what one wants, but in many cases the passed string will contain
|
|
shell metacharacters which one does not want interpreted as such (URLs
|
|
particularly often have shell metacharacters like \'&\' in them). In
|
|
this case, it is more useful to specify a file or program to be run
|
|
and a string to give it as an argument so as to bypass the shell and
|
|
be certain the program will receive the string as you typed it.
|
|
|
|
Examples:
|
|
|
|
> , ((modm, xK_Print), unsafeSpawn "import -window root $HOME/xwd-$(date +%s)$$.png")
|
|
> , ((modm, xK_d ), safeSpawn "firefox" [])
|
|
|
|
Note that the unsafeSpawn example must be unsafe and not safe because
|
|
it makes use of shell interpretation by relying on @$HOME@ and
|
|
interpolation, whereas the safeSpawn example can be safe because
|
|
Firefox doesn't need any arguments if it is just being started. -}
|
|
safeSpawn :: MonadIO m => FilePath -> [String] -> m ()
|
|
safeSpawn prog args = io $ void_ $ forkProcess $ do
|
|
uninstallSignalHandlers
|
|
_ <- createSession
|
|
executeFile (encodeString prog) True (map encodeString args) Nothing
|
|
where void_ = (>> return ()) -- TODO: replace with Control.Monad.void / void not in ghc6 apparently
|
|
|
|
-- | Simplified 'safeSpawn'; only takes a program (and no arguments):
|
|
--
|
|
-- > , ((modm, xK_d ), safeSpawnProg "firefox")
|
|
safeSpawnProg :: MonadIO m => FilePath -> m ()
|
|
safeSpawnProg = flip safeSpawn []
|
|
|
|
-- | An alias for 'spawn'; the name emphasizes that one is calling out to a
|
|
-- Turing-complete interpreter which may do things one dislikes; for details, see 'safeSpawn'.
|
|
unsafeSpawn :: MonadIO m => String -> m ()
|
|
unsafeSpawn = spawn
|
|
|
|
-- | Open a terminal emulator. The terminal emulator is specified in the default configuration as xterm by default. It is then
|
|
-- asked to pass the shell a command with certain options. This is unsafe in the sense of 'unsafeSpawn'
|
|
unsafeRunInTerm, runInTerm :: String -> String -> X ()
|
|
unsafeRunInTerm options command = asks (terminal . config) >>= \t -> unsafeSpawn $ t ++ " " ++ options ++ " -e " ++ command
|
|
runInTerm = unsafeRunInTerm
|
|
|
|
-- | Run a given program in the preferred terminal emulator; see 'runInTerm'. This makes use of 'safeSpawn'.
|
|
safeRunInTerm :: String -> String -> X ()
|
|
safeRunInTerm options command = asks (terminal . config) >>= \t -> safeSpawn t [options, " -e " ++ command]
|
|
|
|
-- | Launch an external application through the system shell and
|
|
-- return a 'Handle' to its standard input. Note that the 'Handle'
|
|
-- is a text 'Handle' using the current locale encoding.
|
|
spawnPipe :: MonadIO m => String -> m Handle
|
|
spawnPipe = spawnPipeWithLocaleEncoding
|
|
|
|
-- | Same as 'spawnPipe'.
|
|
spawnPipeWithLocaleEncoding :: MonadIO m => String -> m Handle
|
|
spawnPipeWithLocaleEncoding = spawnPipe' localeEncoding
|
|
|
|
-- | Same as 'spawnPipe', but forces the UTF-8 encoding regardless of locale.
|
|
spawnPipeWithUtf8Encoding :: MonadIO m => String -> m Handle
|
|
spawnPipeWithUtf8Encoding = spawnPipe' utf8
|
|
|
|
-- | Same as 'spawnPipe', but forces the 'char8' encoding, so unicode strings
|
|
-- need 'Codec.Binary.UTF8.String.encodeString'. Should never be needed, but
|
|
-- some X functions return already encoded Strings, so it may possibly be
|
|
-- useful for someone.
|
|
spawnPipeWithNoEncoding :: MonadIO m => String -> m Handle
|
|
spawnPipeWithNoEncoding = spawnPipe' char8
|
|
|
|
spawnPipe' :: MonadIO m => TextEncoding -> String -> m Handle
|
|
spawnPipe' encoding x = io $ do
|
|
(rd, wr) <- createPipe
|
|
setFdOption wr CloseOnExec True
|
|
h <- fdToHandle wr
|
|
hSetEncoding h encoding
|
|
hSetBuffering h LineBuffering
|
|
_ <- xfork $ do
|
|
_ <- dupTo rd stdInput
|
|
executeFile "/bin/sh" False ["-c", encodeString x] Nothing
|
|
closeFd rd
|
|
return h
|
|
|
|
{- $EDSL
|
|
|
|
To use the provided EDSL, you must first add the 'spawnExternalProcess'
|
|
combinator to your xmonad configuration, like so:
|
|
|
|
> main = xmonad $ … $ spawnExternalProcess def $ … $ def
|
|
|
|
See 'ProcessConfig' for a list of all default configuration options, in
|
|
case you'd like to change them—especially if you want to make use of the
|
|
Emacs integration.
|
|
|
|
After that, the real fun begins! The format for spawning these
|
|
processes is always the same: a call to 'proc', its argument being a
|
|
bunch of function calls, separated by the pipe operator '(>->)'. You
|
|
can just bind the resulting function to a key; no additional plumbing
|
|
required. For example, using "XMonad.Util.EZConfig" syntax and with
|
|
@terminal = "alacritty"@ in you XMonad configuration, spawning a @ghci@
|
|
session with a special class name, "calculator", would look like
|
|
|
|
> ("M-y", proc $ inTerm >-> setXClass "calculator" >-> execute "ghci")
|
|
|
|
which would translate, more or less, to @\/usr\/bin\/sh -c "alacritty
|
|
--class calculator -e ghci"@. The usefulness of this notation becomes
|
|
apparent with more complicated examples:
|
|
|
|
> proc $ inEmacs
|
|
> >-> withEmacsLibs [OwnFile "mailboxes"]
|
|
> >-> execute (elispFun "notmuch")
|
|
> >-> setFrameName "mail"
|
|
|
|
This is equivalent to spawning
|
|
|
|
> emacs -l /home/slot/.config/emacs/lisp/mailboxes.el
|
|
> -e '(notmuch)'
|
|
> -F '(quote (name . "mail"))'
|
|
|
|
Notice how we did not have to specify the whole path to @mailboxes.el@,
|
|
since we had set the correct 'emacsLispDir' upon starting xmonad. This
|
|
becomes especially relevant when running Emacs in batch mode, where one
|
|
has to include [M,Non-GNU]ELPA packages in the call, whose exact names
|
|
may change at any time. Then the following
|
|
|
|
> do url <- getSelection -- from XMonad.Util.XSelection
|
|
> proc $ inEmacs
|
|
> >-> withEmacsLibs [ElpaLib "dash", ElpaLib "s", OwnFile "arXiv-citation"]
|
|
> >-> asBatch
|
|
> >-> execute (elispFun $ "arXiv-citation" <> asString url)
|
|
|
|
becomes
|
|
|
|
> emacs -L /home/slot/.config/emacs/elpa/dash-20220417.2250
|
|
> -L /home/slot/.config/emacs/elpa/s-20210616.619
|
|
> -l /home/slot/.config/emacs/lisp/arXiv-citation.el
|
|
> --batch
|
|
> -e '(arXiv-citation "<url-in-the-primary-selection>")'
|
|
|
|
which would be quite bothersome to type indeed!
|
|
|
|
A blog post going into some more detail and also explaining how to
|
|
integrate this new language with the "XMonad.Util.NamedScratchpad"
|
|
module is available
|
|
<https://tony-zorman.com/posts/2022-05-25-calling-emacs-from-xmonad.html here>.
|
|
-}
|
|
|
|
-----------------------------------------------------------------------
|
|
-- Types and whatnot
|
|
|
|
-- | Additional information that might be useful when spawning external
|
|
-- programs.
|
|
data ProcessConfig = ProcessConfig
|
|
{ editor :: !String
|
|
-- ^ Default editor. Defaults to @"emacsclient -c -a ''"@.
|
|
, emacsLispDir :: !FilePath
|
|
-- ^ Directory for your custom Emacs lisp files. Probably
|
|
-- @user-emacs-directory@ or @user-emacs-directory/lisp@. Defaults
|
|
-- to @"~\/.config\/emacs\/lisp\/"@
|
|
, emacsElpaDir :: !FilePath
|
|
-- ^ Directory for all packages from [M,Non-GNU]ELPA; probably
|
|
-- @user-emacs-directory/elpa@. Defaults to
|
|
-- @"~\/.config\/emacs\/elpa"@.
|
|
, emacs :: !String
|
|
-- ^ /Standalone/ Emacs executable; this should not be @emacsclient@
|
|
-- since, for example, the client does not support @--batch@ mode.
|
|
-- Defaults to @"emacs"@.
|
|
}
|
|
|
|
-- | Given a 'ProcessConfig', remember it for spawning external
|
|
-- processes later on.
|
|
spawnExternalProcess :: ProcessConfig -> XConfig l -> XConfig l
|
|
spawnExternalProcess = XC.modifyDef . const
|
|
|
|
instance Default ProcessConfig where
|
|
def :: ProcessConfig
|
|
def = ProcessConfig
|
|
{ editor = "emacsclient -c -a ''"
|
|
, emacsLispDir = "~/.config/emacs/lisp/"
|
|
, emacsElpaDir = "~/.config/emacs/elpa/"
|
|
, emacs = "emacs"
|
|
}
|
|
|
|
-- | Convenient type alias.
|
|
type Input = ShowS
|
|
|
|
-----------------------------------------------------------------------
|
|
-- Combinators
|
|
|
|
-- | Combine inputs together.
|
|
(>->) :: X Input -> X Input -> X Input
|
|
(>->) = (<>)
|
|
infixr 3 >->
|
|
|
|
-- | Combine an input with an ordinary string.
|
|
(>-$) :: X Input -> X String -> X Input
|
|
(>-$) xi xs = xi >-> fmap mkDList xs
|
|
infixr 3 >-$
|
|
|
|
-- | Spawn a completed input.
|
|
proc :: X Input -> X ()
|
|
proc xi = spawn =<< getInput xi
|
|
|
|
-- | Get the completed input string.
|
|
getInput :: X Input -> X String
|
|
getInput xi = xi <&> ($ "")
|
|
|
|
-- | Use the 'editor'.
|
|
inEditor :: X Input
|
|
inEditor = XC.withDef $ \ProcessConfig{editor} -> pure $ mkDList editor
|
|
|
|
-- | Use the 'XMonad.Core.XConfig.terminal'.
|
|
inTerm :: X Input
|
|
inTerm = asks $ mkDList . terminal . config
|
|
|
|
-- | Execute the argument. Current /thing/ must support a @-e@ option.
|
|
-- For programs such as Emacs, 'eval' may be the safer option; while
|
|
-- @emacsclient@ supports @-e@, the @emacs@ executable itself does not.
|
|
--
|
|
-- Note that this function always wraps its argument in single quotes;
|
|
-- see 'executeNoQuote' for an alternative.
|
|
execute :: String -> X Input
|
|
execute this = pure ((" -e " <> tryQuote this) <>)
|
|
|
|
-- | Like 'execute', but doesn't wrap its argument in single quotes.
|
|
executeNoQuote :: String -> X Input
|
|
executeNoQuote this = pure ((" -e " <> this) <>)
|
|
|
|
-- | Eval(uate) the argument. Current /thing/ must support a @--eval@
|
|
-- option.
|
|
eval :: String -> X Input
|
|
eval this = pure ((" --eval " <> tryQuote this) <>)
|
|
|
|
-- | Use 'emacs'.
|
|
inEmacs :: X Input
|
|
inEmacs = XC.withDef $ \ProcessConfig{emacs} -> pure $ mkDList emacs
|
|
|
|
-- | Use the given program.
|
|
inProgram :: String -> X Input
|
|
inProgram = pure . mkDList
|
|
|
|
-- | Spawn /thing/ in the current working directory. /thing/ must
|
|
-- support a @--working-directory@ option.
|
|
inWorkingDir :: X Input
|
|
inWorkingDir = pure (" --working-directory " <>)
|
|
|
|
-- | Set a frame name for the @emacsclient@.
|
|
--
|
|
-- Note that this uses the @-F@ option to set the
|
|
-- <https://www.gnu.org/software/emacs/manual/html_node/emacs/Frame-Parameters.html frame parameters>
|
|
-- alist, which the @emacs@ executable does not support.
|
|
setFrameName :: String -> X Input
|
|
setFrameName n = pure ((" -F '(quote (name . \"" <> n <> "\"))' ") <>)
|
|
|
|
-- | Set the appropriate X class for a window. This will more often
|
|
-- than not actually be the
|
|
-- <https://tronche.com/gui/x/icccm/sec-4.html#WM_CLASS instance name>.
|
|
setXClass :: String -> X Input
|
|
setXClass = pure . mkDList . (" --class " <>)
|
|
|
|
-- | Spawn the 'XMonad.Core.XConfig.terminal' in some directory; it must
|
|
-- support the @--working-directory@ option.
|
|
termInDir :: X Input
|
|
termInDir = inTerm >-> inWorkingDir
|
|
|
|
-----------------------------------------------------------------------
|
|
-- Emacs
|
|
|
|
-- | Transform the given input into an elisp function; i.e., surround it
|
|
-- with parentheses.
|
|
--
|
|
-- >>> elispFun "arxiv-citation URL"
|
|
-- " '( arxiv-citation URL )' "
|
|
elispFun :: String -> String
|
|
elispFun f = " '( " <> f <> " )' "
|
|
|
|
-- | Treat an argument as a string; i.e., wrap it with quotes.
|
|
--
|
|
-- >>> asString "string"
|
|
-- " \"string\" "
|
|
asString :: String -> String
|
|
asString s = " \"" <> s <> "\" "
|
|
|
|
-- | Wrap the given commands in a @progn@. The given commands need not
|
|
-- be wrapped in parentheses (but can); this will be done by the
|
|
-- function. For example:
|
|
--
|
|
-- >>> progn [require "this-lib", "function-from-this-lib arg", "(other-function arg2)"]
|
|
-- "(progn (require (quote this-lib)) (function-from-this-lib arg) (other-function arg2))"
|
|
progn :: [String] -> String
|
|
progn = inParens . ("progn " <>) . unwords . map inParens
|
|
|
|
-- | Require a package.
|
|
--
|
|
-- >>> require "arxiv-citation"
|
|
-- "(require (quote arxiv-citation))"
|
|
require :: String -> String
|
|
require = inParens . ("require " <>) . quote
|
|
|
|
-- | Quote a symbol.
|
|
--
|
|
-- >>> quote "new-process"
|
|
-- "(quote new-process)"
|
|
quote :: String -> String
|
|
quote = inParens . ("quote " <>)
|
|
|
|
-- | Call @find-file@.
|
|
--
|
|
-- >>> findFile "/path/to/file"
|
|
-- "(find-file \"/path/to/file\" )"
|
|
findFile :: String -> String
|
|
findFile = inParens . ("find-file" <>) . asString
|
|
|
|
-- | Make a list of the given inputs.
|
|
--
|
|
-- >>> list ["foo", "bar", "baz", "qux"]
|
|
-- "(list foo bar baz qux)"
|
|
list :: [String] -> String
|
|
list = inParens . ("list " <>) . unwords
|
|
|
|
-- | Like 'progn', but with @save-excursion@.
|
|
--
|
|
-- >>> saveExcursion [require "this-lib", "function-from-this-lib arg", "(other-function arg2)"]
|
|
-- "(save-excursion (require (quote this-lib)) (function-from-this-lib arg) (other-function arg2))"
|
|
saveExcursion :: [String] -> String
|
|
saveExcursion = inParens . ("save-excursion " <>) . unwords . map inParens
|
|
|
|
-----------------------------------------------------------------------
|
|
-- Batch mode
|
|
|
|
-- | Tell Emacs to enable batch-mode.
|
|
asBatch :: X Input
|
|
asBatch = pure (" --batch " <>)
|
|
|
|
-- | An Emacs library.
|
|
data EmacsLib
|
|
= OwnFile !String
|
|
-- ^ A /file/ from 'emacsLispDir'.
|
|
| ElpaLib !String
|
|
-- ^ A /directory/ in 'emacsElpaDir'.
|
|
| Special !String
|
|
-- ^ Special /files/; these will not be looked up somewhere, but
|
|
-- forwarded verbatim (as a path).
|
|
|
|
-- | Load some Emacs libraries. This is useful when executing scripts
|
|
-- in batch mode.
|
|
withEmacsLibs :: [EmacsLib] -> X Input
|
|
withEmacsLibs libs = XC.withDef $ \ProcessConfig{emacsLispDir, emacsElpaDir} -> do
|
|
lispDir <- mkAbsolutePath emacsLispDir
|
|
elpaDir <- mkAbsolutePath emacsElpaDir
|
|
lisp <- liftIO $ getDirectoryContents lispDir
|
|
elpa <- liftIO $ getDirectoryContents elpaDir
|
|
|
|
let getLib :: EmacsLib -> Maybe String = \case
|
|
OwnFile f -> (("-l " <> lispDir) <>) <$> find (f `isPrefixOf`) lisp
|
|
ElpaLib d -> (("-L " <> elpaDir) <>) <$> find ((d <> "-") `isPrefixOf`) elpa
|
|
Special f -> Just $ " -l " <> f
|
|
pure . mkDList . unwords . mapMaybe getLib $ libs
|
|
|
|
-----------------------------------------------------------------------
|
|
-- Util
|
|
|
|
mkDList :: String -> ShowS
|
|
mkDList = (<>) . (<> " ")
|
|
|
|
inParens :: String -> String
|
|
inParens s = case s of
|
|
'(' : _ -> s
|
|
_ -> "(" <> s <> ")"
|
|
|
|
tryQuote :: String -> String
|
|
tryQuote s = case dropWhile (== ' ') s of
|
|
'\'' : _ -> s
|
|
_ -> "'" <> s <> "'"
|