packages feed

opn (empty) → 0.1.0

raw patch · 6 files changed

+354/−0 lines, 6 filesdep +basedep +directorydep +filepathbuild-type:Customsetup-changed

Dependencies added: base, directory, filepath, ini, network-uri, optparse-applicative, process, text, unordered-containers

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2014, Anders Claesson++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Anders Claesson nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,73 @@+```+  ____  ____  ____+ / __ \/ __ \/ __ \+/ /_/ / /_/ / / / /+\____/ .___/_/ /_/+    /_/            +```++The `opn` command lets you open files and URLs with associated+programs. It's intended to "just work" and to be trivial to+configure. Its configuration resides in `~/.opnconfig`; here's an+example:++```+[browser]+browser: chromium++[associations]+mupdf:    .pdf+emacs:    .lhs .hs .py .c+chromium: .png .jpg .html .txt+mpv:      .avi .mpg .mp4+djview:   .djvu+```++With this configuration, PDFs would open in `mupdf`; Haskell, Python and+C source files in `emacs`, etc.  If asked to open a file with no+matching extension, or a file without an extension, `opn` tries to be a+bit smart. If, for example, one runs++```+opn foo.h+```++then the header file `foo.h` would be opened in `emacs`. The reason is+that such files have mime type `text/x-c`, and, as files with the+extension `'.c'` shares this mime type, `opn` "guesses" that `'.h'` files+also should be opened with `emacs`.++Both `[browser]` and `[associations]` sections must be present and+nonempty in `~/.opnconfig`. The `[browser]` section should in fact+always have exactly one key, namely `browser`. So a (close to) minimal+configuration looks something like this:++```+[browser]+browser: chromium++[associations]+chromium: html+```++The `browser` is used for URLs and as a fallback, so with this+configuration all files and URLs would open in `chromium`.++Talking of `chromium`: on Linux `chromium` opens downloads using+`xdg-open`. This works pretty well if you are using a full desktop+environment like GNOME, KDE or Xfce, but less so if you are running a+light window manager such as i3 or xmonad.  We can, however, exploit a+behavior of `xdg-open` to effectively replace it with `opn`. As a+fallback, when no supported desktop session is running, `xdg-open` uses+`$BROWSER`. Thus putting++```+export BROWSER=opn+```++in your `.bashrc`, or something similar for your favorite shell, will+"trick" `chromium` and `xdg-open` into using `opn`. This is my own main+use for `opn`.++For further info see the man page:+<https://github.com/akc/opn/blob/master/opn.md>.
+ Setup.hs view
@@ -0,0 +1,8 @@+import Distribution.Simple+import System.Process+import System.Exit++main = defaultMainWithHooks $ simpleUserHooks { postBuild = makeManPage }++makeManPage _ _ _ _ =+    runCommand "make opn.1" >>= waitForProcess >>= exitWith
+ opn.1 view
@@ -0,0 +1,95 @@+.TH "OPN" "1" "24 Aug 2014" "Opn User Manual" "Version 0.1.0"+.SH NAME+.PP+opn \- open files and URLs with associated programs+.SH SYNOPSIS+.PP+opn (\-\-version | [\-\-dry\-run] PATHS...)+.SH DESCRIPTION+.PP+The opn command lets you open files and URLs with associated programs.+It\[aq]s intended to "just work" and to be trivial to configure.+.PP+The configuration resides in \f[C]~/.opnconfig\f[]; here\[aq]s an+example:+.IP+.nf+\f[C]+[browser]+browser:\ chromium++[associations]+mupdf:\ \ \ \ .pdf+emacs:\ \ \ \ .lhs\ .hs\ .py\ .c+chromium:\ .png\ .jpg\ .html\ .txt+mpv:\ \ \ \ \ \ .avi\ .mpg\ .mp4+djview:\ \ \ .djvu+\f[]+.fi+.PP+Both \f[C][browser]\f[] and \f[C][associations]\f[] sections must be+present and nonempty.+The \f[C][browser]\f[] section should in fact always have exactly one+key, namely \f[C]browser\f[].+Comments must start at the beginning of the line and start with+\f[C]\[aq];\[aq]\f[] or \f[C]\[aq]#\[aq]\f[].+.PP+If asked to open a file with no matching extension, or a file without an+extension, \f[C]opn\f[] tries to be a bit smart.+For example, a header file, say \f[C]foo.h\f[], would be opened in+\f[C]emacs\f[].+The reason is that header files have mime type \f[C]text/x\-c\f[], the+same type as \f[C]\[aq].c\[aq]\f[] files, and thus \f[C]opn\f[]+"guesses" that \f[C]\[aq].h\[aq]\f[] files also should be opened with+\f[C]emacs\f[].+.SH OPTIONS+.TP+.B \-\-dry\-run+Display the command, or commands, that would be executed, then exit.+.RS+.RE+.TP+.B \-\-version+Display version information.+.RS+.RE+.TP+.B \-\-help+Display a short help message.+.RS+.RE+.SH EXAMPLES+.PP+Open \f[C]opn.hs\f[] in the application associated with the extension+\f[C]\[aq].hs\[aq]\f[]:+.IP+.nf+\f[C]+opn\ opn.hs+\f[]+.fi+.PP+Open \f[C]http://akc.is/\f[] in the browser specified under the+\f[C][browser]\f[] section:+.IP+.nf+\f[C]+opn\ http://akc.is/+\f[]+.fi+.PP+For each file in the current directory display the manner in which it+would be opened:+.IP+.nf+\f[C]+opn\ \-\-dry\-run\ *+\f[]+.fi+.SH SEE ALSO+.PP+The source code to opn can be found at <https://github.com/akc/opn>.+See also the \f[C]README\f[] on that page.+.SH AUTHOR+.PP+Anders Claesson <http://akc.is>
+ opn.cabal view
@@ -0,0 +1,28 @@+name:                opn+version:             0.1.0+synopsis:            Open files or URLs using associated programs.++description:         The `opn` command lets you open files and URLs with+                     associated programs. It's intended to "just work" and to+                     be trivial to configure.++homepage:            http://github.com/akc/opn+license:             BSD3+license-file:        LICENSE+author:              Anders Claesson+maintainer:          anders.claesson@gmail.com+Category:            Application, Console+Build-type:          Custom+Extra-Source-Files:  README.md opn.1+cabal-version:       >=1.8++source-repository head+  type:                git+  location:            git://github.com/akc/opn.git++executable opn+  main-is:             opn.hs+  ghc-options:         -Wall+  build-depends:       base ==4.6.*, ini, text, process,+                       unordered-containers, optparse-applicative,+                       network-uri, directory, filepath
+ opn.hs view
@@ -0,0 +1,120 @@+{-# LANGUAGE OverloadedStrings #-}+-- |+-- Copyright   : Anders Claesson 2014+-- Maintainer  : Anders Claesson <anders.claesson@gmail.com>+-- License     : BSD-3+--++import           Data.Maybe (mapMaybe, fromMaybe)+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.0"++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