-
Notifications
You must be signed in to change notification settings - Fork 1
/
opn.hs
121 lines (107 loc) · 4.16 KB
/
opn.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
{-# LANGUAGE OverloadedStrings #-}
-- |
-- Copyright : Anders Claesson 2014
-- Maintainer : Anders Claesson <[email protected]>
-- License : BSD-3
--
import Data.Maybe (mapMaybe, fromMaybe)
import Data.Monoid ((<>))
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as M
import Data.Ini (Ini(..), parseIni)
import Options.Applicative
import Control.Monad (forM_, void, unless)
import System.IO (hPutStrLn, stderr)
import System.Process (runProcess, readProcess)
import System.FilePath (takeExtension, (</>))
import System.Directory (doesFileExist, getHomeDirectory)
import Network.URI (isAbsoluteURI)
import Opn.Guess (guess)
type Command = Text
type Browser = Command
type PathOrURL = String
type Homedir = String
type Extension = Text
type ECMap = HashMap Extension Command
type Config = (Browser, ECMap)
data Opts = Opts
{ dryrun :: Bool
, paths :: [String]
}
name :: String
name = "opn 0.1.3"
optsParser :: Parser (Maybe Opts)
optsParser = hiddenHelp <*> versionOpt <|> (Just <$> (Opts
<$> switch (long "dry-run" <> dryrunHelp)
<*> some (argument str (metavar "PATHS..."))))
where
dryrunHelp = help "Display command(s) that would be executed, then exit."
hiddenHelp = abortOption ShowHelpText $ hidden <> short 'h' <> long "help"
versionOpt = flag' Nothing (long "version")
lookupCommand :: ECMap -> Extension -> Maybe Command
lookupCommand m ext = T.strip <$> M.lookup ext m
getExtensions :: FilePath -> IO [Extension]
getExtensions fpath =
(ext ++) <$> guess <$> mimeType
where
ext = case takeExtension fpath of { [] -> []; e -> [T.pack e]}
mimeType = getValue <$> runFileCmd
getValue = T.pack . drop 1 . reverse . takeWhile (/= ':') . drop 1 . reverse
runFileCmd = readProcess "file" ["--mime-type", "-L", fpath] ""
getCommand :: Config -> PathOrURL -> IO (Maybe Command)
getCommand (browser, m) s = doesFileExist s >>= getCmd
where
getCmd True = do
exts <- getExtensions s
case mapMaybe (lookupCommand m) exts of
[] -> return (Just browser)
(cmd:_) -> return (Just cmd)
getCmd False =
if isAbsoluteURI s
then return (Just browser)
else do
hPutStrLn stderr (show s ++
" is neither a file-path nor an absolute URL; skipping.")
return Nothing
readIni :: Homedir -> IO Ini
readIni home = do
opnconfigExists <- doesFileExist opnconfig
unless opnconfigExists $ do
hPutStrLn stderr (opnconfig ++ " does not exist; creating a stub.")
T.writeFile opnconfig
"[browser]\nbrowser: chromium\n\n[associations]\nchromium: html\n"
(either error id . parseIni . adjust) <$> T.readFile opnconfig
where
opnconfig = home </> ".opnconfig"
adjust = T.unlines . map T.stripStart . T.lines
mkConfig :: Ini -> Config
mkConfig (Ini ini) = (browser, d)
where
d = M.fromList $ M.toList cmdMap >>= \(c,es) -> [(e,c) | e <- T.words es]
browser = let s = "browser" in fromMaybe (errKey s) $ M.lookup s (getSec s)
cmdMap = getSec "associations"
getSec s = fromMaybe (errSec s) $ M.lookup s ini
errSec s = error $ "Couldn't find required section " ++ show s
errKey s = error $ "Couldn't find required key " ++ show s
readConfig :: Homedir -> IO Config
readConfig = fmap mkConfig . readIni
run :: Command -> [String] -> IO ()
run cmd args = void $
runProcess (T.unpack cmd) args Nothing Nothing Nothing Nothing Nothing
opn :: Maybe Opts -> IO ()
opn Nothing = putStrLn name
opn (Just opts) = do
conf <- readConfig =<< getHomeDirectory
forM_ (paths opts) $ \path ->
getCommand conf path >>= \c ->
case c of
Just cmd -> run' cmd [path]
Nothing -> return ()
where
run' = if dryrun opts then \c [s] -> T.putStr c >> putStrLn (' ':s) else run
main :: IO ()
main =
customExecParser (prefs showHelpOnError) (info optsParser fullDesc) >>= opn