hunp 0.0 → 0.1
raw patch · 6 files changed
+213/−60 lines, 6 filesdep +monads-fddep +parsecdep +splitdep ~bytestringdep ~directorydep ~filepath
Dependencies added: monads-fd, parsec, split
Dependency ranges changed: bytestring, directory, filepath, pcre-light, process
Files
- CHANGELOG +11/−2
- Hunp.hs +128/−43
- HunpParser.hs +15/−0
- README +47/−8
- VERSION +2/−0
- hunp.cabal +10/−7
CHANGELOG view
@@ -1,4 +1,13 @@-Changelog for hünp+Changelog for hunp +Version 0.1:+* Added user configurability in ~/.hunp/hunp.conf+* Added options -q (quiet), -Q (stfu), -v (print version) and -d (print default+ configuration)+* Fixed the only known bug, which prevented the program from killing its+ subprocesses properly+* Dropped the umlauts in the name, so now it's simply "hunp", yet still+ pronounced "hump"+ Version 0.0:-* First release.+* First release of hünp
Hunp.hs view
@@ -1,54 +1,118 @@-{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE CPP #-} -- Copyright (c) 2009 Deniz Dogan module Main where -import Control.Monad+import Control.Applicative+import Control.Arrow+import Control.Monad.Reader import Data.List import Data.Maybe+import HunpParser+import System.Console.GetOpt import System.Directory import System.Environment import System.Exit import System.FilePath.Posix import System.IO import System.Process+import Text.ParserCombinators.Parsec import Text.Regex.PCRE.Light+import Text.Regex.PCRE.Light.Base import qualified Data.ByteString.Char8 as B +#include "VERSION"++data HunpConf = HC { flags :: [Flag]+ , targets :: [String]+ , rules :: [Rule Regex]+ }++type Hunp a = ReaderT HunpConf IO a++type Rule a = ([a], String)++data Flag = Quiet | STFU | Default | Version+ deriving Eq++-- | The different options available.+options :: [OptDescr Flag]+options = [ Option "q" ["quiet"] (NoArg Quiet) "suppress stdout"+ , Option "Q" ["stfu"] (NoArg STFU) "suppress stdout and stderr"+ , Option "d" ["default"] (NoArg Default) "prints the default config"+ , Option "v" ["version"] (NoArg Version) "prints the version of hunp"+ ]++-- | A string of usage information.+usage :: String+usage = let ls = ["Usage: hunp [options] [files/directories]", ""+ , "Options:"]+ in unlines ls++-- | Reads the configuration file. Tries to use the user's custom+-- configuration file. If that fails, uses the default one.+readConf :: IO [Rule Regex]+readConf = do+ file <- (</> "hunp.conf") <$> getAppUserDataDirectory "hunp"+ exists <- doesFileExist file+ if not exists+ then return defaultConf+ else do p <- readable <$> getPermissions file+ if not p+ then do putErrLn "Cannot read configuration file. Using default configuration."+ return defaultConf+ else do res <- parseFromFile parser file+ case res of+ Left e -> do putErrLn $ "Configuration file parse failed: " ++ show e+ return defaultConf+ Right x -> return $ map compileRegexen x++-- | Prints the given configuration to stdout.+printConfig :: [Rule Regex] -> IO ()+printConfig [] = return ()+printConfig ((r,cmd):rs) = do+ let parts = map (\(Regex _ x) -> B.unpack x) r+ putStrLn $ (concat $ intersperse "," parts) ++ "\t" ++ cmd+ printConfig rs+ main :: IO () main = do args <- getArgs- if null args- then usage- else mapM_ hunp args--usage :: IO ()-usage = let ls = ["Usage:",- " hunp <files/directories>"]- in putErrLn (unlines ls)+ let (fl, tgts, errs) = getOpt Permute options args+ if Default `elem` fl+ then printConfig defaultConf+ else if Version `elem` fl+ then putStrLn hunpVersion+ else if null tgts || not (null errs)+ then putStr $ usageInfo usage options+ else do conf <- readConf+ let c = HC fl tgts conf+ runReaderT (mapM_ hunp tgts) c -- | Given a FilePath, determines whether it is pointing to a file or -- a directory and takes the appropriate action.-hunp :: FilePath -> IO ()+hunp :: FilePath -> Hunp () hunp fp = do- isFile <- doesFileExist fp- isDir <- doesDirectoryExist fp+ isFile <- io $ doesFileExist fp+ isDir <- io $ doesDirectoryExist fp when isFile (hunpFile fp) when isDir (hunpDir fp)- when (not $ isFile || isDir) (putErrLn $ "Could not find " ++ fp ++ ". Skipping.")+ unless (isFile || isDir) (talkE $ "Could not find `" ++ fp ++ "'. Skipping.") -hunpFile :: FilePath -> IO ()+-- | Given a filename, tries to find a matching regular expression for+-- it. If one is found, unpacks it using the corresponding rule.+hunpFile :: FilePath -> Hunp () hunpFile fp = do- let res = lookupWith (any (\x -> match' x (B.pack fp) [])) fileTypes- case res of- Nothing -> putErrLn $ "What am I supposed to do with this file?"- Just (cmd, args) -> do- unpack cmd (map (replaceIt fp) $ words args) >>=- waitForProcess >>=- \x -> putStrLn $ case x of- ExitSuccess -> "Successfully unpacked `" ++ takeFileName fp ++ "'."- _ -> "Something went wrong with `" ++ takeFileName fp ++ "'."+ conf <- asks rules+ case lookupWith (any (\x -> match' x (B.pack fp) [])) conf of+ Nothing -> talkE $ "Unknown file: `" ++ takeFileName fp ++ "'. Skipping."+ Just cmd -> do+ talk $ "Unpacking `" ++ fp ++ "'..."+ unpack (map (replaceIt fp) $ words cmd) >>=+ \x -> case x of+ ExitSuccess -> talk $ "Successfully unpacked `" ++ takeFileName fp ++ "'."+ _ -> talkE $ "Something went wrong while unpacking `" ++ takeFileName fp ++ "'!" -- | Hackish way of performing printf substitution. This was needed -- for some silly reason that I can't be bothered remembering.@@ -60,17 +124,18 @@ -- matching any of the file regexp rules. If it finds any file -- matching any rule, it stops and unpacks the first found file. It -- will never unpack several files in a directory.-hunpDir :: FilePath -> IO ()+hunpDir :: FilePath -> Hunp () hunpDir fp = do- cont <- getDirectoryContents fp- case find (\f -> isJust $ lookupWith (any (\x -> match' x (B.pack f) [])) fileTypes) cont of+ cont <- io $ getDirectoryContents fp+ conf <- asks rules+ case find (\f -> isJust $ lookupWith (any (\x -> match' x (B.pack f) [])) conf) cont of Just x -> hunpFile (fp </> x)- Nothing -> putErrLn $ "Found nothing to unpack in " ++ fp+ Nothing -> talkE $ "Found nothing to unpack in " ++ fp -- | Exactly like 'Text.Regex.PCRE.Light', but returns @True@ if there -- was a match, otherwise @False@. match' :: Regex -> B.ByteString -> [PCREExecOption] -> Bool-match' a b c = isJust $ match a b c+match' a b = isJust . match a b -- | Generalization of 'lookup'. lookupWith :: (a -> Bool) -> [(a, b)] -> Maybe b@@ -78,21 +143,41 @@ lookupWith p ((x, y):xs) | p x = Just y | otherwise = lookupWith p xs -unpack :: String -> [String] -> IO ProcessHandle-unpack cmd args = runProcess cmd args Nothing Nothing Nothing Nothing Nothing+unpack :: [String] -> Hunp ExitCode+unpack (cmd:args) = io $ rawSystem cmd args+unpack _ = error "Error in configuration file." +-- | Prints an error to @stderr@. putErrLn :: String -> IO () putErrLn = hPutStrLn stderr -fileTypes :: [([Regex], (String, String))]-fileTypes = let compileRegexen (xs, y) = (map (flip compile []) xs, y)- ls = [ (["\\.rar$", "\\.r00$"], ("unrar", "x %s"))- , (["\\.tar\\.gz$", "\\.tgz$"], ("tar", "-zxvf %s"))- , (["\\.tar\\.bz2$"], ("tar", "-jxvf %s"))- , (["\\.bz2$"], ("bunzip2", "%s"))- , (["\\.zip$"], ("unzip", "%s"))- , (["\\.arj$"], ("unarj", "x %s"))- , (["\\.7z$"], ("7z", "x %s"))- , (["\\.ace$"], ("unace", "x %s"))- ]- in map compileRegexen ls+defaultConf :: [Rule Regex]+defaultConf =+ let ls = [ (["\\.rar$", "\\.r00$"], "unrar x %s")+ , (["\\.tar\\.gz$", "\\.tgz$"], "tar -zxvf %s")+ , (["\\.tar\\.bz2$"], "tar -jxvf %s")+ , (["\\.bz2$"], "bunzip2 %s")+ , (["\\.zip$"], "unzip %s")+ , (["\\.arj$"], "unarj x %s")+ , (["\\.7z$"], "7z x %s")+ , (["\\.ace$"], "unace x %s")+ ]+ in map compileRegexen ls++compileRegexen :: Rule String -> Rule Regex+compileRegexen = first $ map (\x -> compile (B.pack x) [])++talk :: String -> Hunp ()+talk s = do+ fl <- asks flags+ unless (STFU `elem` fl || Quiet `elem` fl)+ (io $ putStrLn s)++talkE :: String -> Hunp ()+talkE s = do+ fl <- asks flags+ unless (STFU `elem` fl)+ (io $ hPutStrLn stderr s)++io :: MonadIO m => IO a -> m a+io = liftIO
+ HunpParser.hs view
@@ -0,0 +1,15 @@+module HunpParser where++import Control.Applicative ((<$>))+import Data.List.Split (splitOn)+import Text.ParserCombinators.Parsec++parser :: GenParser Char st [([String], String)]+parser = sepEndBy line newline++line :: GenParser Char st ([String], String)+line = do+ regex <- filter (not . null) . splitOn "," <$> anyChar `manyTill` char '\t'+ skipMany $ char '\t'+ action <- many $ noneOf "\n"+ return (regex, action)
README view
@@ -1,12 +1,12 @@ * INTRODUCTION--hünp (written in all lower-case, not necessarily with the umlaut and-strangely enough pronounced like "hump") is a small tool which-automagically extracts anything you pass to it, the way you want it.-It recognizes a bunch of different file extensions, such as rar, gz,-zip, etc.+hunp (written in all lower-case, pronounced like "hump", previously written with+an umlaut (hünp)) is a small tool which automagically extracts anything you pass+to it, the way you want it. It recognizes a bunch of different file extensions,+such as rar, gz, zip, etc. and can easily be customized using your own+configuration file. * USAGE+Here are a few examples which should give you the idea... $ hunp ~/download/lollerskates.tar.gz Found ~/download/lollerskates.tar.gz@@ -18,6 +18,45 @@ Calling "unrar x ~/download/Lollerskates.S01E03/lol.s01e03.r00" ... +* OPTIONS+-q --quiet suppress hunp's stdout+-Q --stfu suppress hunp'sstdout and stderr+-d --default prints the default configuration+-v --version prints the version++* CONFIGURATION+You can use your own configuration by creating the file ~/.hunp/hunp.conf. The+file format is pretty simple and you can get a good start by running "hunp -d >+~/.hunp/hunp.conf", which will put the default configuration in that file.++** Format+The configuration file needs to be on the following format:+reg1,reg2 command+reg3 command+reg4,reg5,reg6 command++"regX" means a regular expression (Perl-compatible) and command is any command+that you would normally execute in the shell to unpack a file matching one of+the corresponding regular expressions. The regular expressions are read+left-to-right, top-to-bottom and will stop on the first match. Each command+should contain %s as a whole word, i.e. no quotation marks or other stuff is+allowed around it. Note that the delimiter between regular expressions and+commands is a literal tab character. It is allowed however to use more than one+tab.++Examples:+unrar x '%s' <- invalid+unrar x %s <- valid+unrar x %s> hello <- invalid+ * KNOWN BUGS- - It seems that it is currently impossible to stop individual- unpacking processes by hitting C-c.+- None.++* DOWNLOADING+Get the latest git repository:+$ git clone git://github.com/skorpan/hunp.git++If you're not interested in the bleeding-edge version of hunp, just+use cabal-install:++$ cabal install hunp
+ VERSION view
@@ -0,0 +1,2 @@+hunpVersion :: String+hunpVersion = "0.1"
hunp.cabal view
@@ -1,20 +1,23 @@ name: hunp-version: 0.0+version: 0.1 synopsis: Unpacker tool with DWIM-description:- hünp is an unpacker tool which does what you mean. By matching on regular expressions, it automagically calls the right unpacking program for you, e.g. "unrar" for files ending in ".rar", etc.+description: hunp is an unpacker tool which does what you mean. By matching on regular expressions, it automagically calls the right unpacking program for you, e.g. "unrar" for files ending in ".rar", etc. license: GPL license-file: LICENSE author: Deniz Dogan-homepage: git://github.com/skorpan/hunp.git+homepage: http://github.com/skorpan/hunp/tree/master maintainer: deniz.a.m.dogan@gmail.com build-type: Simple-cabal-version: >=1.2+cabal-version: >=1.6 category: Console, Utils tested-with: GHC==6.10.3-extra-source-files: Hunp.hs README CHANGELOG+extra-source-files: Hunp.hs HunpParser.hs VERSION README CHANGELOG executable hunp main-is: Hunp.hs- build-depends: base>=3 && <5, filepath, process, directory, pcre-light, bytestring+ build-depends: base >= 3 && <5, filepath >= 1.1 && < 2, process >= 1 && < 2, directory > 1 && < 2, pcre-light > 0.3 && < 1, bytestring > 0.9 && < 2, monads-fd > 0 && < 1, split>=0.1 && < 2, parsec >= 3 ghc-options: -Wall -fno-warn-orphans -threaded++source-repository head+ type: git+ location: git://github.com/skorpan/hunp.git