sizes (empty) → 1.0.0
raw patch · 5 files changed
+226/−0 lines, 5 filesdep +basedep +cmdargsdep +deepseqsetup-changed
Dependencies added: base, cmdargs, deepseq, lens, parallel-io, system-fileio, system-filepath, text, unix
Files
- LICENSE +28/−0
- Main.hs +161/−0
- README.md +2/−0
- Setup.hs +0/−0
- sizes.cabal +35/−0
+ LICENSE view
@@ -0,0 +1,28 @@+Copyright (c) 2003-2009, John Wiegley. 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 New Artisans LLC nor the names of its+ 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.
+ Main.hs view
@@ -0,0 +1,161 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE TemplateHaskell #-}++module Main where++import Control.Concurrent.ParallelIO+import Control.DeepSeq+import Control.Exception+import Control.Lens+import Control.Monad hiding (sequence)+import Data.Function+import qualified Data.List as L+import Data.Monoid+import Data.Text as T hiding (filter, map, chunksOf)+import Filesystem (listDirectory)+import Filesystem.Path.CurrentOS+import GHC.Conc+import Prelude hiding (FilePath, sequence, catch)+import System.Console.CmdArgs+import System.Environment (getArgs, withArgs)+import System.Posix.Files+import Text.Printf++default (Int, Text)++version :: String+version = "1.0.0"++copyright :: String+copyright = "2012"++sizesSummary :: String+sizesSummary = "sizes v" ++ version ++ ", (C) John Wiegley " ++ copyright++data SizesOpts = SizesOpts { jobs :: Int+ , byCount :: Bool+ , smalls :: Bool+ -- , dirsOnly :: Bool+ , depth :: Int+ , dirs :: [String] }+ deriving (Data, Typeable, Show, Eq)++sizesOpts :: SizesOpts+sizesOpts = SizesOpts+ { jobs = def &= name "j" &= typ "INT"+ &= help "Run INT concurrent finds at once (default: 2)"+ , byCount = def &= typ "BOOL"+ &= help "Sort output by count (default: by size)"+ , smalls = def &= typ "BOOL"+ &= help "Also show small (<1M && <100 files) entries"+ -- , dirsOnly = def &= typ "BOOL"+ -- &= help "Show directories only"+ , depth = def &= typ "INT"+ &= help "Report all entries to a depth of INT (default: 0)"+ , dirs = def &= args &= typ "DIRS..." } &=+ summary sizesSummary &=+ program "sizes" &=+ help "Calculate amount of disk used by the given directories"++data EntryInfo = EntryInfo { _entryPath :: !FilePath+ , _entryCount :: !Int+ , _entrySize :: !Int+ , _entryIsDir :: !Bool }+ deriving Show++instance NFData EntryInfo where+ rnf a = a `seq` ()++newEntry :: FilePath -> Bool -> EntryInfo+newEntry p isDir = EntryInfo p 0 0 isDir++makeLenses ''EntryInfo++main :: IO ()+main = do+ mainArgs <- getArgs+ opts <- withArgs (if L.null mainArgs then ["."] else mainArgs)+ (cmdArgs sizesOpts)+ _ <- GHC.Conc.setNumCapabilities $ case jobs opts of 0 -> 2; x -> x+ runSizes opts++runSizes :: SizesOpts -> IO ()+runSizes opts = do+ reportSizes opts $ map (fromText . pack) (dirs opts)+ stopGlobalPool++parMap :: (a -> IO b) -> [a] -> IO ()+parMap f xs = parallel (map f xs) >> return ()++reportSizes :: SizesOpts -> [FilePath] -> IO ()+reportSizes opts xs = do+ entryInfos <- parallel $ map (gatherSizesW (depth opts) 0) xs+ let infos = map fst entryInfos ++ mconcat (map snd entryInfos)+ sorted = L.sortBy ((compare `on`) $ if byCount opts+ then (^. entryCount)+ else (^. entrySize)) infos+ forM_ sorted $ \entry ->+ when ( smalls opts+ || entry^.entrySize >= 10 * 1024^2+ || entry^.entryCount > 100)+ $ reportEntry entry++humanReadable :: Int -> String+humanReadable x+ | x < 1024 = printf "%db" x+ | x < 1024^2 = printf "%.0fK" (fromIntegral x / (1024 :: Double))+ | x < 1024^3 = printf "%.1fM" (fromIntegral x / (1024^2 :: Double))+ | x < 1024^4 = printf "%.2fG" (fromIntegral x / (1024^3 :: Double))+ | x < 1024^5 = printf "%.3fT" (fromIntegral x / (1024^4 :: Double))+ | x < 1024^6 = printf "%.3fP" (fromIntegral x / (1024^5 :: Double))+ | otherwise = error "Too large"++reportEntry :: EntryInfo -> IO ()+reportEntry entry =+ let path = unpack . toTextIgnore $ entry^.entryPath+ in printf (unpack "%10s %10d %s%s\n")+ (humanReadable (entry^.entrySize)) (entry^.entryCount) path+ (unpack $ if entry^.entryIsDir && L.last path /= '/'+ then "/" else "")++toTextIgnore :: FilePath -> Text+toTextIgnore p = case toText p of Left _ -> ""; Right x -> x++gatherSizesW :: Int -> Int -> FilePath -> IO (EntryInfo, [EntryInfo])+gatherSizesW m d p =+ catch (gatherSizes m d p)+ (\e -> do+ putStrLn $ show (e :: IOException)+ return (newEntry p False, []))++gatherSizes :: Int -> Int -> FilePath -> IO (EntryInfo, [EntryInfo])+gatherSizes maxDepth curDepth path = do+ let path' = unpack $ toTextIgnore path+ status <- if curDepth == 0+ then getFileStatus path'+ else getSymbolicLinkStatus path'+ if isDirectory status+ then do+ files <- listDirectory path+ entries <- let func = gatherSizesW maxDepth (curDepth + 1) . collapse+ in if curDepth == 0+ then parallel $ map func files+ else mapM func files+ let firsts = map fst entries+ return $!! ( L.foldl' (\current entry ->+ entryCount +~ entry^.entryCount $+ entrySize +~ entry^.entrySize $ current)+ (newEntry path True)+ firsts+ , if curDepth < maxDepth+ then firsts ++ L.concat (map snd entries)+ else [] )+ else+ if isRegularFile status+ then return ( entryCount .~ 1 $+ entrySize .~ fromIntegral (fileSize status) $+ newEntry path False, [] )+ else return (newEntry path False, [])++-- Main.hs (sizes) ends here
+ README.md view
@@ -0,0 +1,2 @@+sizes compute the space and i-node consumption of all files and directories+under the current directory. Think du, but with a bit more flexibility.
+ Setup.hs view
+ sizes.cabal view
@@ -0,0 +1,35 @@+Name: sizes++Version: 1.0.0+Synopsis: Recursively show space (size and i-nodes) used in subdirectories++Description: Recursively show space (size and i-nodes) used in subdirectories++Homepage: https://github.com/jwiegley/sizes+License: BSD3+License-file: LICENSE+Author: John Wiegley+Maintainer: John Wiegley <johnw@newartisans.com>+Category: Development+Build-type: Simple+Cabal-version: >= 1.8++Extra-Source-Files: README.md++Executable sizes+ Main-is: Main.hs+ Ghc-options: -threaded++ Build-depends: base >= 4 && < 5+ , cmdargs >= 0.10+ , deepseq >= 1.3+ , lens >= 2.8+ , parallel-io >= 0.3.2+ , system-fileio >= 0.3.9+ , system-filepath >= 0.4.7+ , text >= 0.11.2+ , unix >= 2.5.1++Source-repository head+ type: git+ location: https://github.com/jwiegley/sizes