root / datapacker.hs

1
{- 
2
Copyright (C) 2006-2008 John Goerzen <jgoerzen@complete.org>
3
4
This program is distributed in the hope that it will be useful, but
5
WITHOUT ANY WARRANTY; without even the implied warranty of
6
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
7
General Public License for more details.
8
-}
9
10
{- |
11
   Module     : Main
12
   Copyright  : Copyright (C) 2008 John Goerzen
13
   License    : GNU GPL, version 3 or above; see COPYRIGHT for details
14
15
   Maintainer : John Goerzen <jgoerzen@complete.org>
16
   Stability  : provisional
17
   Portability: portable
18
19
Written by John Goerzen, jgoerzen\@complete.org
20
21
-}
22
23
import System.Log.Logger
24
import System.Log.Handler.Simple
25
import System.IO(stderr)
26
import System.Console.GetOpt.Utils
27
import System.Console.GetOpt
28
import System.Environment
29
import Data.Quantity
30
import Data.List
31
import System.Exit
32
import Control.Monad
33
import Types
34
import Scan
35
import Data.List.Utils(split)
36
import Actions
37
38
main :: IO ()
39
main = 
40
    do updateGlobalLogger "" (setLevel INFO)
41
       argv <- getArgs
42
       case getOpt RequireOrder options argv of
43
         (o, n, []) -> worker o n
44
         (_, _, errors) -> usageerror (concat errors) -- ++ usageInfo header options)
45
46
options :: [OptDescr (String, String)]       
47
options = [
48
           Option "0" ["null"] (NoArg ("0", ""))
49
                  "Input items terminated by null character",
50
           Option "a" ["action"] (ReqArg (stdRequired "a") "ACTION")
51
                  "Give action for output.  Options are:\n\
52
                  \print     print each record with a newline\n\
53
                  \          after [default]\n\
54
                  \printfull print one line for each bin\n\
55
                  \print0    print each record with NULL after\n\
56
                  \exec:CMD  Execute CMD in the shell for each\n\
57
                  \record\n\
58
                  \hardlink  Hard link items into bins\n\
59
                  \symlink   Symlink items into bins",
60
           Option "b" ["binfmt"] (ReqArg (stdRequired "b") "FORMAT")
61
                  "Gives bin name format in printf style.\n\
62
                  \Tip: this can include a directory.\n\
63
                  \default: %03d",
64
           Option "d" ["debug"] (NoArg ("d", "")) "Enable debugging",
65
           Option "D" ["deep-links"] (NoArg ("D", "")) "Enable deep bin directories",
66
           Option "p" ["preserve-order"] (NoArg ("p", ""))
67
                  "Don't reorder files for maximum packing",
68
           Option "s" ["size"] (ReqArg (stdRequired "s") "SIZE")
69
                  "Size of each output bin",
70
           Option "S" ["size-first"] (ReqArg (stdRequired "S") "SIZE")
71
                  "Override size of first output bin",
72
           Option "" ["sort"] (NoArg ("sort", "")) "Sort input; useless without -p",
73
           Option "" ["help"] (NoArg ("help", "")) "Display this help"]
74
75
worker :: [(String, String)] -> [FilePath] -> IO ()
76
worker args files =
77
    do when (lookup "help" args == Just "") $ usageerror ""
78
       when (lookup "d" args == Just "") 
79
            (updateGlobalLogger "" (setLevel DEBUG))
80
       handler <- streamHandler stderr DEBUG
81
       updateGlobalLogger "" (setHandlers [handler])
82
       
83
       runinfo <- case parseArgs args of
84
                       Left x -> usageerror x
85
                       Right x -> return x
86
87
       when (files == [])
88
            (usageerror "One or more files, or \"-\", must be specified")
89
90
       files_scan <- if files == ["-"]
91
                        then readFileList (readNull runinfo)
92
                        else return files
93
94
       let listToProc = if sortFiles runinfo then sort files_scan else files_scan
95
96
       results <- scan runinfo listToProc
97
       let numberedResults = zip [1..] (map (map snd) results)
98
       runAction runinfo numberedResults
99
100
readFileList :: Bool -> IO [FilePath]
101
readFileList nullsep =
102
    do c <- getContents
103
       return (splitfunc c)
104
    where splitfunc 
105
              | nullsep = filter (/= "") . split "\0"
106
              | otherwise = lines
107
108
parseArgs :: [(String, String)] -> Either String RunInfo
109
parseArgs args =
110
    do size <- case lookup "s" args of
111
                 Nothing -> fail "Missing required argument --size"
112
                 Just x -> parseNumInt binaryOpts True x
113
       first <- case lookup "S" args of
114
                  Nothing -> return size
115
                  Just x -> parseNumInt binaryOpts True x
116
       let po = case lookup "p" args of
117
                  Nothing -> False
118
                  Just _ -> True
119
       let n = case lookup "0" args of
120
                 Nothing -> False
121
                 Just _ -> True
122
       let b = case lookup "b" args of
123
                 Nothing -> "%03d"
124
                 Just x -> x
125
       let deeplinks = case lookup "D" args of
126
                        Nothing -> False
127
                        Just _ -> True
128
       let dosort = case lookup "sort" args of
129
                      Nothing -> False
130
                      Just _ -> True
131
       a <- case lookup "a" args of
132
              Nothing -> return Print
133
              Just "print" -> return Print
134
              Just "printfull" -> return PrintFull
135
              Just "print0" -> return Print0
136
              Just "hardlink" -> return Hardlink
137
              Just "symlink" -> return Symlink
138
              Just x -> 
139
                  if "exec:" `isPrefixOf` x
140
                     then return (Exec (drop 5 x))
141
                     else fail $ "Unknown action: " ++ show x
142
       return $ RunInfo {binSize = size, firstBinSize = first,
143
                         preserveOrder = po, readNull = n, binFmt = b,
144
                         action = a, deepLinks = deeplinks, sortFiles = dosort}
145
146
usageerror :: String -> IO t
147
usageerror errormsg =
148
    do putStrLn errormsg
149
       putStrLn (usageInfo header options)
150
       putStrLn "If the single value \"-\" is given for inputfiles, the list of files"
151
       putStrLn "is read from stdin."
152
       exitFailure
153
154
header :: String
155
header = "\nUsage: datapacker [options] --size=n inputfiles\n\n\
156
         \Available options are:\n"