{-# LANGUAGE UnicodeSyntax, LambdaCase, FlexibleContexts, TemplateHaskell #-}
import Control.Lens
import Control.Monad.IO.Class
import Control.Monad.State.Strict
import System.Environment
import System.Exit
import System.Process
import qualified DMenu
data Opts = Opts
{ _optsBrowser :: String
, _optsEngines :: [(String, String)] -- ^ [(EngineName, EngineURLPrefix)]
}
makeLenses ''Opts
pairs :: [a] → Either String [(a, a)]
pairs = \case
[] → Right []
x:y:xs → ((x,y):) <$> pairs xs
_ → Left "Found search engine name without URL prefix."
parseEngines :: String -> Either String [(String, String)]
parseEngines es = pairs relevantLines
where
isEmptyLine = all (`elem` [' ', '\t'])
relevantLines = filter (not . isEmptyLine) (lines es)
-- | Parse the command line arguments
readArgs
:: [String] -- ^ Arguments from 'getArgs'
-> IO Opts
readArgs args =
execStateT (go $ words $ unwords args) (Opts "chromium -new-window" [])
where
go []
= pure ()
go (a:as)
| a == "--"
= pure () -- All arguments after "--" are passed to dmenu later.
| a `elem` ["-b", "--browser"]
, browser : as' ← as
= do optsBrowser .= browser; go as'
| a `elem` ["-e", "--engine"]
, (name : urlPrefix : as') ← as
= do optsEngines %= (++[(name, urlPrefix)]); go as'
| a `elem` ["-E", "--engine-file"]
, (filePath : as') ← as
= liftIO (parseEngines <$> readFile filePath) >>= \case
Left err → liftIO $ putStrLn $ "Failed to parse config file `"
++ filePath ++ "`: " ++ err
Right es → do optsEngines %= (++es); go as'
| a `elem` ["-h", "--help"]
= liftIO $ do putStrLn usage; exitFailure
| a == ""
= go as
go _
= liftIO $ do putStrLn usage; exitFailure
main :: IO ()
main = do
opts ← readArgs =<< getArgs
let cfg1 = do DMenu.prompt .= "search with"; DMenu.forwardExtraArgs
let cfg2 = do DMenu.prompt .= "search for"; DMenu.forwardExtraArgs
DMenu.selectWith cfg1 fst (opts^.optsEngines) >>= \case
Right (_, urlPrefix) →
DMenu.select cfg2 [] >>= \case
Right query →
callCommand $ (opts^.optsBrowser) ++ " " ++ show (urlPrefix ++ query)
_→ pure ()
_ → pure ()
usage :: String
usage = unlines
[ "USAGE"
, " dmenu-search [OPTIONS] [-- DMENUOPTIONS]"
, ""
, " Let's the user choose a search engine and enter a search string by"
, " spawning two subsequent dmenu processes, and opens the resulting"
, " URL in a browser."
, ""
, " All arguments, after the first `--` argument, are directly passed to dmenu."
, ""
, "OPTIONS"
, " -b, --browser CMD"
, " Shell command to open url in browser. Default: `chromium -new-window`"
, ""
, " -e, --engine NAME URLPREFIX"
, " Add a search engine, e.g."
, ""
, " -e google https://www.google.com/search?q="
, " -e github https://github.com/search?q="
, ""
, " -E, --engine-file PATH"
, " Add search engines from a file."
, ""
, " The following shows example content of an engine file:"
, ""
, " google"
, " https://www.google.com/search?q="
, ""
, " github"
, " https://github.com/search?q="
, ""
, " -h, --help"
, " Display this message."
]