fishfood 0.0.1.4 → 0.0.1.5
raw patch · 25 files changed
+913/−811 lines, 25 filesdep +fishfooddep ~Cabaldep ~toolshed
Dependencies added: fishfood
Dependency ranges changed: Cabal, toolshed
Files
- README.markdown +30/−0
- changelog +0/−23
- changelog.markdown +31/−0
- debian/DEBIAN/control +2/−3
- fishfood.cabal +95/−35
- fishfood.spec +19/−17
- man/man1/fishfood.1 +1/−5
- src-exe/Main.hs +179/−0
- src-lib/FishFood/Data/CommandOptions.hs +113/−0
- src-lib/FishFood/Data/File.hs +60/−0
- src-lib/FishFood/Data/Verbosity.hs +37/−0
- src-lib/FishFood/Profiler.hs +148/−0
- src-test/FishFood/Test/Data/CommandOptions.hs +65/−0
- src-test/FishFood/Test/Data/Verbosity.hs +31/−0
- src-test/FishFood/Test/Profiler.hs +58/−0
- src-test/Main.hs +44/−0
- src/FishFood/Data/CommandOptions.hs +0/−113
- src/FishFood/Data/File.hs +0/−60
- src/FishFood/Data/Verbosity.hs +0/−37
- src/FishFood/Profiler.hs +0/−148
- src/FishFood/Test/Data/CommandOptions.hs +0/−62
- src/FishFood/Test/Data/Verbosity.hs +0/−31
- src/FishFood/Test/Profiler.hs +0/−58
- src/FishFood/Test/QuickChecks.hs +0/−38
- src/Main.hs +0/−181
+ README.markdown view
@@ -0,0 +1,30 @@+# **FishFood**.++This is **FishFood**, an application which determines the distribution in sizes of the specified files.++## Installation++It can be built and installed using [Cabal](https://www.haskell.org/cabal/users-guide/installing-packages.html).++## Documentation++The directory **man/** contains the documentation.++## License++For information on copying and distributing this package, see the file **LICENSE** in this directory.++## Bug-reporting++Bug-reports should be emailed to <fishfood *at* functionalley *dot* eu>.++## Testing++The test-suite can be run using:+ cabal configure --enable-tests+ cabal build+ cabal test --show-details=always++## Author++This application is written and maintained by Dr. Alistair Ward.
− changelog
@@ -1,23 +0,0 @@-2013-07-16 Dr. Alistair Ward <fishfood at functionalley dot eu>--0.0.0.1- * First version of the package.-0.0.0.2- * Added 'man/man1/fishfood.1' & 'fishfood.spec' to the distribution.-0.0.1.0- * Migrated all but the I/O, from "Main" to a new module "FishFood.Profiler".- * Added module "FishFood.Test", containing "Test.QuickCheck"-implementation.- * Added Command-line options; '--deriveProbabilityMassFunction', '--binSizeRatio', '--runQuickChecks'.-0.0.1.1- * Added files to build .deb to 'fishfood.cabal'.-0.0.1.2- * Added '--runQuickChecks' to 'man/man1/fishfood.1' & corrected typo.- * Improved the syntax of 'verbosity' in the usage-message.- * Tested with 'haskell-platform-2013.2.0.0'.-0.0.1.3- * In module "FishFood.Data.File", replaced 'Control.Exception.throw' with 'Control.Exception.throwIO'.- * Either replaced instances of '(<$>)' with 'fmap' to avoid ambiguity between "Control.Applicative" & "Prelude" which (from 'base-4.8') also exports this symbol, or hid the symbol when importing the "Prelude"..- * In module "Main", redirected version-message to stderr.-0.0.1.4- * Added the compiler to the output returned for the command-line option "version".- * In module "FishFood.Data.File.hs", replaced the use of the package "unix" with the package "directory", for compatibility with Windows.
+ changelog.markdown view
@@ -0,0 +1,31 @@+# 2013-07-16 Dr. Alistair Ward <fishfood at functionalley dot eu>++## 0.0.0.1+ * First version of the package.+## 0.0.0.2+ * Added **man/man1/fishfood.1** & *.spec*-file to the distribution.+## 0.0.1.0+ * Migrated all but the I/O, from **Main** to a new module **FishFood.Profiler**.+ * Added module **FishFood.Test**, containing **Test.QuickCheck**-implementation.+ * Added Command-line options; **--deriveProbabilityMassFunction**, **--binSizeRatio**, **--runQuickChecks**.+## 0.0.1.1+ * Added files to build *.deb* to the *.cabal*-file.+## 0.0.1.2+ * Added **--runQuickChecks** to **man/man1/fishfood.1** & corrected typo.+ * Improved the syntax of **verbosity** in the usage-message.+ * Tested with **haskell-platform-2013.2.0.0**.+## 0.0.1.3+ * In module **FishFood.Data.File**, replaced `Control.Exception.throw` with `Control.Exception.throwIO`.+ * Either replaced instances of `(<$>)` with `fmap` to avoid ambiguity between **Control.Applicative** & **Prelude** which (from **base-4.8**) also exports this symbol, or hid the symbol when importing the **Prelude**..+ * In module **Main**, redirected version-message to stderr.+## 0.0.1.4+ * Added the compiler to the output returned for the command-line option **version**.+ * In module **FishFood.Data.File.hs**, replaced the use of the package **unix** with the package **directory**, for compatibility with Windows.+## 0.0.1.5+ * Added **Default-language**-specification to the *.cabal*-file.+ * Added file **README.markdown**.+ * Converted this file to markdown-format.+ * Replaced `System.Exit.exitWith System.Exit.ExitSuccess` with `System.Exit.exitSuccess`.+ * Moved the entry-point to the test-suite from **Main.hs** to **Test.hs**, both to integrate with **cabal** & to minimise the dependencies of the executable.+ * Partitioned the source-files into **src-lib**, **src-exe**, & **src-test** directories, & referenced them individually from the *.cabal*-file to avoid repeated compilation.+ * Used **CPP** to control the import of symbols from **Control.Applicative**.
debian/DEBIAN/control view
@@ -1,11 +1,10 @@ Package: fishfood-Version: 0.0.1.4-1+Version: 0.0.1.5-1 Section: utils Priority: optional-#Architecture: i386 Architecture: amd64 Depends: libc6-Installed-size: 2252+Installed-size: 2082 Maintainer: Dr. Alistair Ward <fishfood at functionalley dot eu> Provides: Homepage: http://functionalley.eu
fishfood.cabal view
@@ -1,54 +1,92 @@--- Package-properties-Name: fishfood-Version: 0.0.1.4-Cabal-Version: >= 1.6-Copyright: (C) 2013-2015 Dr. Alistair Ward-License: GPL-License-file: LICENSE-Author: Dr. Alistair Ward-Stability: Unstable interface, incomplete features.-Synopsis: Calculates file-size frequency-distribution.-Build-Type: Simple-Description: Counts the number of files in a set of bins, each of which holds only those files which fall within a specific size-interval.-Category: Utils-Tested-With: GHC == 7.4, GHC == 7.6, GHC == 7.10-Homepage: http://functionalley.eu-Maintainer: fishfood <at> functionalley <dot> eu-Bug-reports: fishfood <at> functionalley <dot> eu-Data-files:-Extra-Source-Files: changelog changelog.Debian copyright debian/DEBIAN/control fishfood.spec man/man1/fishfood.1+-- This file is part of FishFood.+--+-- FishFood is free software: you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation, either version 3 of the License, or+-- (at your option) any later version.+--+-- FishFood is distributed in the hope that it will be useful,+-- but WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+-- GNU General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with FishFood. If not, see <http://www.gnu.org/licenses/>. --- Turn on using: 'runhaskell ./Setup.hs configure -f llvm'.+Name: fishfood+Version: 0.0.1.5+Cabal-version: >= 1.10+Copyright: (C) 2013-2015 Dr. Alistair Ward+License: GPL+License-file: LICENSE+Author: Dr. Alistair Ward+Stability: stable+Synopsis: Calculates file-size frequency-distribution.+Build-type: Simple+Description: Counts the number of files in a set of bins, each of which holds only those files which fall within a specific size-interval.+Category: Utils+Tested-with: GHC == 7.4, GHC == 7.6, GHC == 7.8, GHC == 7.10+Homepage: http://functionalley.eu+Maintainer: mailto <colon> fishfood <at> functionalley <dot> eu+Bug-reports: mailto <colon> fishfood <at> functionalley <dot> eu++-- None of these files are needed at run-time.+Extra-source-files:+ changelog.markdown+ changelog.Debian+ copyright+ debian/DEBIAN/control+ fishfood.spec+ man/man1/fishfood.1+ README.markdown++-- Enable using: 'cabal configure -f llvm'. flag llvm Description: Whether the 'llvm' compiler-backend has been installed and is required for code-generation.- manual: True- default: False+ Manual: True+ Default: False -Executable fishfood- hs-source-dirs: src- Main-Is: Main.hs- Other-modules:- FishFood.Data.CommandOptions,- FishFood.Data.File,+Library+ Default-language: Haskell2010+ GHC-options: -Wall -fno-warn-tabs+ Hs-source-dirs: src-lib++ Exposed-modules:+ FishFood.Data.CommandOptions+ FishFood.Data.File FishFood.Data.Verbosity FishFood.Profiler- FishFood.Test.Data.CommandOptions- FishFood.Test.Data.Verbosity- FishFood.Test.Profiler- FishFood.Test.QuickChecks Build-depends: base == 4.*,- Cabal >= 1.6 && < 2,+ Cabal >= 1.10, containers, directory, factory >= 0.2.0.5, mtl,- QuickCheck,- toolshed >= 0.13+ toolshed >= 0.16 + if impl(ghc >= 7.4.1)+ GHC-prof-options: -prof -fprof-auto -fprof-cafs+ else+ GHC-prof-options: -prof -auto-all -caf-all++ if impl(ghc >= 7.0) && flag(llvm)+ GHC-options: -fllvm++Executable fishfood+ Default-language: Haskell2010 GHC-options: -Wall -fno-warn-tabs+ Hs-source-dirs: src-exe+ Main-is: Main.hs + Build-depends:+ base == 4.*,+ Cabal >= 1.10,+ fishfood,+ mtl,+ toolshed >= 0.16+ if impl(ghc >= 7.4.1) GHC-prof-options: -prof -fprof-auto -fprof-cafs else@@ -59,4 +97,26 @@ if flag(llvm) GHC-options: -fllvm++Test-Suite quickCheck+ Default-language: Haskell2010+ GHC-options: -Wall -fno-warn-tabs+ Hs-source-dirs: src-test+ Main-is: Main.hs+ Type: exitcode-stdio-1.0++-- Required for 'cabal sdist'.+ Other-modules:+ FishFood.Test.Data.CommandOptions+ FishFood.Test.Data.Verbosity+ FishFood.Test.Profiler++ Build-depends:+ base == 4.*,+ Cabal >= 1.10,+ containers,+ fishfood,+ mtl,+ QuickCheck,+ toolshed >= 0.16
fishfood.spec view
@@ -1,4 +1,4 @@-# Copyright (C) 2013-2014 Dr. Alistair Ward+# Copyright (C) 2013-2015 Dr. Alistair Ward # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by@@ -15,51 +15,53 @@ %define package %name-%version %define tarBall %package.tar.gz-%define _bindir %prefix/bin %define _sharedir %prefix/share-%define _docdir %_sharedir/doc/%package+%define _docdir %_sharedir/doc/%name %define _mandir %_sharedir/man Summary: Calculates file-size frequency-distribution. Name: fishfood-Version: 0.0.1.4+Version: 0.0.1.5 Release: 1 License: GPLv3-#From '/usr/share/doc/packages/rpm/GROUPS'.+# From '/usr/share/doc/packages/rpm/GROUPS'. Group: Applications/File Source0: http://functionalley.eu/Downloads/sdist/%tarBall URL: http://functionalley.eu Prefix: /usr-BuildRequires: ghc+BuildRequires: haskell-platform %description Counts the number of files in a set of bins, each of which holds only those files which fall within a specific size-interval. %prep-#NB: we've changed directory to %_builddir-(cd $OLDPWD && runhaskell Setup sdist) && tar -zxf $OLDPWD/dist/%tarBall #Make a source-distribution & unpack it into the build-directory.-cd '%package/' && runhaskell Setup configure --user --prefix=%prefix #Configure to use the user's personal package-database & to generate an appropriate "Paths" module.+# NB: CWD has changed to %_builddir+echo 'package="%package", prefix="%prefix", _builddir="%_builddir", buildroot="%buildroot"'+(cd $OLDPWD && cabal sdist) && tar -zxf $OLDPWD/dist/%tarBall # Make a source-distribution & unpack it into the build-directory.+cd '%package/' && cabal configure --user --prefix='%prefix' --docdir='%_docdir' # Tell cabal to use the user's personal package-database, to generate an appropriate "Paths" module, & where to place the documentation. %build-cd '%package/' && runhaskell Setup build #Descend into the source-distribution and build according to the previously established configuration.+cd '%package/' && cabal build # Descend into the source-distribution and build according to the previously established configuration. %install-cd '%package/' #Descend into the build-directory.-runhaskell Setup copy --destdir=%buildroot #Install the package in the target-directory.-mkdir -p -- '%buildroot%_docdir' && mv changelog copyright LICENSE '%buildroot%_docdir/'+cd '%package/' # Descend into the build-directory.+cabal copy --destdir='%buildroot' # Install the built package in the target-directory.+mkdir -p -- '%buildroot%_docdir' && mv 'changelog.markdown' 'copyright' 'README.markdown' '%buildroot%_docdir/' # 'LICENSE' has already been copied by cabal. mkdir -p -- '%buildroot%_mandir' && mv 'man/man1' '%buildroot%_mandir/'+rm -rf -- '%buildroot%prefix/lib/' # The library isn't a deliverable. %clean-make --directory='%package' clobber-rm -rf -- '%_builddir/%package/' '%buildroot'+rm -rf -- '%_builddir/%package/' '%buildroot/' # Only the '.rpm' is required. %files-%attr(0755, root, root) %_bindir/%name-%attr(0644, root, root) %doc %_docdir/changelog+%attr(0755, root, root) %prefix/bin/%name+%attr(0644, root, root) %doc %_docdir/changelog.markdown %attr(0644, root, root) %doc %_docdir/copyright %attr(0644, root, root) %doc %_docdir/LICENSE+%attr(0644, root, root) %doc %_docdir/README.markdown %attr(0644, root, root) %doc %_mandir/man1/%name.1.gz %changelog * Tue Jul 16 2013 Alistair Ward <fishfood at functionalley dot eu> 0.0.0.2-1 First cut.+
man/man1/fishfood.1 view
@@ -32,10 +32,6 @@ .TP \fB-?\fR, \fB--help\fR Displays help & then exits.-.SS Test-.TP-\fB-q\fR, \fB--runQuickChecks\fR-Tests the implementation, by validating some invariant properties using arbitrary data; & then exits. .SS File-paths .TP If \fIFile-path\fR is a single hyphen-minus (\fB-\fR), then the list of file-paths will be read from standard-input. Only plain files are acceptable; no directories, symlinks, sockets, ...@@ -140,7 +136,7 @@ Written by Dr. Alistair Ward. .SH BUGS .SS "REPORTING BUGS"-Report bugs to \fBfishfood\fR \fIat\fR \fBfunctionalley\fR \fIdot\fR \fBeu\fR+Report bugs to <\fBfishfood\fR \fIat\fR \fBfunctionalley\fR \fIdot\fR \fBeu\fR>. .SH COPYRIGHT Copyright \(co 2013-2015 Dr. Alistair Ward .PP
+ src-exe/Main.hs view
@@ -0,0 +1,179 @@+{-# LANGUAGE CPP #-}+{-+ Copyright (C) 2013-2015 Dr. Alistair Ward++ This program is free software: you can redistribute it and/or modify+ it under the terms of the GNU General Public License as published by+ the Free Software Foundation, either version 3 of the License, or+ (at your option) any later version.++ This program is distributed in the hope that it will be useful,+ but WITHOUT ANY WARRANTY; without even the implied warranty of+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+ GNU General Public License for more details.++ You should have received a copy of the GNU General Public License+ along with this program. If not, see <http://www.gnu.org/licenses/>.+-}+{- |+ [@AUTHOR@] Dr. Alistair Ward++ [@DESCRIPTION@]++ * Contains the entry-point of the application.++ * Processes the command-line arguments.++ * Delegates the task to "Profiler".+-}++module Main(main) where++import qualified Control.Monad+import qualified Control.Monad.Writer+import qualified Data.List+import qualified Data.Maybe+import qualified Data.Version+import qualified Distribution.Package+import qualified Distribution.Text+import qualified Distribution.Version+import qualified FishFood.Data.CommandOptions as Data.CommandOptions+import qualified FishFood.Data.File as Data.File+import qualified FishFood.Data.Verbosity as Data.Verbosity+import qualified FishFood.Profiler as Profiler+import qualified Paths_fishfood as Paths -- Either local stub, or package-instance autogenerated by 'Setup.hs build'.+import qualified System.Console.GetOpt as G+import qualified System.Environment+import qualified System.Exit+import qualified System.Info+import qualified System.IO+import qualified System.IO.Error+import qualified ToolShed.Data.List+import qualified ToolShed.Defaultable+import qualified ToolShed.SelfValidate++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative((<$>), (<*>))+#endif++-- | Define the concrete type of command-options.+type CommandOptions = Data.CommandOptions.CommandOptions Double++-- | Used to thread user-defined command-line options, though the list of functions which implement them.+type CommandLineAction = CommandOptions -> IO CommandOptions -- Supplied as the type-argument to 'G.OptDescr'.++-- | On failure to parse the specified string, returns an explanatory error.+read' :: Read a => String -> String -> a+read' errorMessage s = case reads s of+ [(x, "")] -> x+ _ -> error $ errorMessage ++ show s++-- | On failure to parse a command-line argument, returns an explanatory error.+readCommandArg :: Read a => String -> a+readCommandArg = read' "failed to parse command-line argument "++-- | Reads a bounded integral from the command-line, guarding against overflow.+readBoundedIntegral :: Integral i => String -> i+readBoundedIntegral s+ | fromIntegral bounded /= unbounded = error $ "integral value exceeds permissible bounds; " ++ show unbounded ++ "."+ | otherwise = bounded+ where+ unbounded = readCommandArg s+ bounded = fromInteger unbounded++{- |+ * Parses the command-line options, which over-ride default values.++ * Any arguments which follow known options, are interpreted as file-names.++ * If the specified file-name is /-/, then the actual file-names are read from /standard-input/, to augment any other non-options specified.+-}+main :: IO ()+main = do+ progName <- System.Environment.getProgName++ let+ defaultCommandOptions :: CommandOptions+ defaultCommandOptions = ToolShed.Defaultable.defaultValue++ optDescrList :: [G.OptDescr CommandLineAction]+ optDescrList = [+-- String [String] (G.ArgDescr CommandLineAction) String+ G.Option "?" ["help"] (G.NoArg $ const printUsage) "Display this help, & then exit.",+ G.Option "" ["verbosity"] (+ setVerbosity `G.ReqArg` ToolShed.Data.List.showListWith listDelimiters Data.Verbosity.range ""++ ) ("Define the log-level; default '" ++ show (Data.CommandOptions.getVerbosity defaultCommandOptions) ++ "'."+ ),+ G.Option "v" ["version"] (G.NoArg $ const printVersion) "Print version-information, & then exit.",+ G.Option "s" ["binSizeIncrement"] (setBinSizeIncrement `G.ReqArg` "<Int>") "The constant size-increase in the arithmetic sequence of bins into which the byte-sizes of files are categorised; defaulting to one standard-deviation.",+ G.Option "r" ["binSizeRatio"] (setBinSizeRatio `G.ReqArg` "<Float>") "The constant size-ratio in the geometric sequence of bins into which the byte-sizes of files are categorised; an alternative to 'binSizeIncrement'.",+ G.Option "p" ["deriveProbabilityMassFunction"] (setDeriveProbabilityMassFunction `G.OptArg` "<Bool>") ("Whether to derive the \"Probability mass function\" rather than the \"Frequency-distribution\"; default '" ++ show (Data.CommandOptions.getDeriveProbabilityMassFunction defaultCommandOptions) ++ "'."),+ G.Option "d" ["nDecimalDigits"] (setNDecimalDigits `G.ReqArg` "<Int>") ("The precision to which fractional auxiliary data is displayed; default " ++ show (Data.CommandOptions.getNDecimalDigits defaultCommandOptions) ++ ".")+ ] where+ listDelimiters = ('(', '|', ')')++ setBinSizeIncrement, setBinSizeRatio, setNDecimalDigits, setVerbosity :: String -> CommandLineAction+ setBinSizeIncrement arg = return {-to IO-monad-} . Data.CommandOptions.setBinSizeIncrement (readCommandArg arg)+ setBinSizeRatio arg = return {-to IO-monad-} . Data.CommandOptions.setBinSizeRatio (readCommandArg arg)+ setNDecimalDigits arg commandOptions = return {-to IO-monad-} commandOptions { Data.CommandOptions.getNDecimalDigits = readBoundedIntegral arg }+ setVerbosity arg commandOptions = return {-to IO-monad-} commandOptions { Data.CommandOptions.getVerbosity = readCommandArg arg }++ setDeriveProbabilityMassFunction :: Maybe String -> CommandLineAction+ setDeriveProbabilityMassFunction arg commandOptions = return {-to IO-monad-} commandOptions { Data.CommandOptions.getDeriveProbabilityMassFunction = Data.Maybe.maybe True readCommandArg arg }++ printVersion, printUsage :: IO CommandOptions+ printVersion = System.IO.hPutStrLn System.IO.stderr (+ Distribution.Text.display packageIdentifier ++ "\n\nCompiled by " ++ show compiler ++ ".\n\nCopyright (C) 2013-2015 " ++ author ++ ".\nThis program comes with ABSOLUTELY NO WARRANTY.\nThis is free software, and you are welcome to redistribute it under certain conditions.\n\nWritten by " ++ author ++ "."+ ) >> System.Exit.exitSuccess where+ packageIdentifier :: Distribution.Package.PackageIdentifier+ packageIdentifier = Distribution.Package.PackageIdentifier {+ Distribution.Package.pkgName = Distribution.Package.PackageName progName, -- CAVEAT: coincidentally.+ Distribution.Package.pkgVersion = Distribution.Version.Version (Data.Version.versionBranch Paths.version) []+ }++ author, compiler :: String+ author = "Dr. Alistair Ward"+ compiler = System.Info.compilerName ++ "-" ++ Data.List.intercalate "." (map show $ Data.Version.versionBranch System.Info.compilerVersion)++ printUsage = System.IO.hPutStrLn System.IO.stderr ("Usage:\t" ++ G.usageInfo progName optDescrList ++ " [<File-path> ...]") >> System.Exit.exitSuccess++ args <- System.Environment.getArgs++ case G.getOpt G.RequireOrder optDescrList args of+ (commandLineActions, nonOptions, [{-errors-}]) -> do+ commandOptions <- Data.List.foldl' (>>=) (return {-to IO-monad-} ToolShed.Defaultable.defaultValue) commandLineActions++ if not $ ToolShed.SelfValidate.isValid commandOptions+ then fail $ ToolShed.SelfValidate.getFirstError commandOptions+ else if null nonOptions+ then fail "zero file-paths specified"+ else let+ standardInputProxy = "-"+ in do+ filePaths <- if standardInputProxy `elem` nonOptions+ then let+ getFilePaths :: IO [String]+ getFilePaths = do+ eof <- System.IO.isEOF++ if eof+ then return {-to IO-monad-} []+ else {-more to read-} (:) <$> getLine <*> getFilePaths {-recurse-}+ in do+ filePaths <- (filter (/= standardInputProxy) nonOptions ++) <$> getFilePaths++ if null filePaths+ then fail "zero file-paths"+ else return filePaths+ else {-real fileNames-} return {-to IO-monad-} nonOptions++ Control.Monad.when (Data.CommandOptions.getVerbosity commandOptions == maxBound) $ System.IO.hPrint System.IO.stderr filePaths -- CAVEAT: potentially very long.++ (fileSizeDistribution, statistics) <- Control.Monad.Writer.runWriter . Profiler.calculateFileSizeDistribution commandOptions <$> mapM Data.File.findSize (Data.List.nub filePaths)++ Control.Monad.when (Data.CommandOptions.getVerbosity commandOptions > ToolShed.Defaultable.defaultValue) $ mapM_ (System.IO.hPutStrLn System.IO.stderr) statistics++ putStrLn $ Profiler.formatFileSizeDistribution commandOptions fileSizeDistribution+ (_, _, errors) -> System.IO.Error.ioError . System.IO.Error.userError $ concatMap init {-chop-} errors+
+ src-lib/FishFood/Data/CommandOptions.hs view
@@ -0,0 +1,113 @@+{-+ Copyright (C) 2013 Dr. Alistair Ward++ This program is free software: you can redistribute it and/or modify+ it under the terms of the GNU General Public License as published by+ the Free Software Foundation, either version 3 of the License, or+ (at your option) any later version.++ This program is distributed in the hope that it will be useful,+ but WITHOUT ANY WARRANTY; without even the implied warranty of+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+ GNU General Public License for more details.++ You should have received a copy of the GNU General Public License+ along with this program. If not, see <http://www.gnu.org/licenses/>.+-}+{- |+ [@AUTHOR@] Dr. Alistair Ward++ [@DESCRIPTION@]++ * Defines options for program-operation.++ * Defines an appropriate default value, which is expected to be over-ridden on the command-line.++ * Self-validates.+-}++module FishFood.Data.CommandOptions(+-- * Types+-- ** Type-synonyms+ BinSizeDelta,+-- ** Data-types+ CommandOptions(+-- MkCommandOptions,+ getBinSizeDelta,+ getDeriveProbabilityMassFunction,+ getNDecimalDigits,+ getVerbosity+ ),+-- * Functions+-- ** Constructors+ mkCommandOptions,+-- ** Mutators+ setBinSizeIncrement,+ setBinSizeRatio+) where++import qualified Data.Maybe+import qualified Distribution.Verbosity+import qualified FishFood.Data.File as Data.File+import qualified ToolShed.Defaultable+import qualified ToolShed.SelfValidate++-- | Either an arithmetic size-increase for which there's a default, or a geometric size-ratio.+type BinSizeDelta ratio = Either (Maybe Data.File.FileSize) ratio++-- | Declares a record to contain command-line options.+data CommandOptions ratio = MkCommandOptions {+ getBinSizeDelta :: BinSizeDelta ratio, -- ^ Either the arithmetic size-increase (defaulting to one standard-deviation), or the geometric size-ratio, of the sequence of bins into which files are categorized.+ getDeriveProbabilityMassFunction :: Bool, -- ^ Whether to derive the "Probability mass function" rather than the "Frequency-distribution".+ getNDecimalDigits :: Int, -- ^ The precision to which fractional data is displayed.+ getVerbosity :: Distribution.Verbosity.Verbosity -- ^ The threshold for ancillary information-output.+} deriving Show++instance ToolShed.Defaultable.Defaultable (CommandOptions ratio) where+ defaultValue = MkCommandOptions {+ getBinSizeDelta = Left Nothing, -- Interpreted as one standard-deviation.+ getDeriveProbabilityMassFunction = False,+ getNDecimalDigits = 3,+ getVerbosity = Distribution.Verbosity.normal+ }++instance (Num ratio, Ord ratio, Show ratio) => ToolShed.SelfValidate.SelfValidator (CommandOptions ratio) where+ getErrors commandOptions@MkCommandOptions {+ getBinSizeDelta = binSizeDelta,+ getDeriveProbabilityMassFunction = deriveProbabilityMassFunction,+ getNDecimalDigits = nDecimalDigits+ } = map snd $ filter fst [+ (+ either (Data.Maybe.maybe False (<= 0)) (<= 1) binSizeDelta,+ "either the bin-size's arithmetic increase must exceed zero, or it's geometric ratio must exceed one; " ++ show commandOptions ++ "."+ ), (+ deriveProbabilityMassFunction && nDecimalDigits < 1,+ "the number of decimal digits must exceed zero to adequately represent probabilities; " ++ show commandOptions ++ "."+ ),+ let+ maxNDecimalDigits = floor $ fromIntegral (+ floatDigits (+ undefined :: Double -- CAVEAT: the actual type could be merely 'Float', but that's currently unknown.+ )+ ) * (logBase 10 2 :: Double)+ in (+ nDecimalDigits > maxNDecimalDigits,+ "the number of decimal digits shouldn't exceed " ++ show maxNDecimalDigits ++ "; " ++ show commandOptions ++ "."+ )+ ]++-- | Smart constructor.+mkCommandOptions :: (Num ratio, Ord ratio, Show ratio) => BinSizeDelta ratio -> Bool -> Int -> Distribution.Verbosity.Verbosity -> CommandOptions ratio+mkCommandOptions binSizeDelta deriveProbabilityMassFunction nDecimalDigits verbosity+ | ToolShed.SelfValidate.isValid commandOptions = commandOptions+ | otherwise = error $ "FishFood.Data.CommandOptions.mkCommandOptions:\t" ++ ToolShed.SelfValidate.getFirstError commandOptions+ where+ commandOptions = MkCommandOptions binSizeDelta deriveProbabilityMassFunction nDecimalDigits verbosity++-- | Mutator.+setBinSizeIncrement :: Data.File.FileSize -> CommandOptions ratio -> CommandOptions ratio+setBinSizeIncrement fileSize commandOptions = commandOptions { getBinSizeDelta = Left $ Just fileSize }++-- | Mutator.+setBinSizeRatio :: ratio -> CommandOptions ratio -> CommandOptions ratio+setBinSizeRatio ratio commandOptions = commandOptions { getBinSizeDelta = Right ratio }
+ src-lib/FishFood/Data/File.hs view
@@ -0,0 +1,60 @@+{-+ Copyright (C) 2013-2014 Dr. Alistair Ward++ This program is free software: you can redistribute it and/or modify+ it under the terms of the GNU General Public License as published by+ the Free Software Foundation, either version 3 of the License, or+ (at your option) any later version.++ This program is distributed in the hope that it will be useful,+ but WITHOUT ANY WARRANTY; without even the implied warranty of+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+ GNU General Public License for more details.++ You should have received a copy of the GNU General Public License+ along with this program. If not, see <http://www.gnu.org/licenses/>.+-}+{- |+ [@AUTHOR@] Dr. Alistair Ward++ [@DESCRIPTION@] Allows one to find the size of a file, & to gather statistics on a population.+-}++module FishFood.Data.File(+-- * Types+-- ** Type-synonyms+ FileSize,+-- * Functions+ findSize,+ getFileSizeStatistics+) where++import qualified Control.Exception+import qualified Factory.Math.Statistics+import qualified System.Directory+import qualified System.IO+import qualified System.IO.Error++-- | A type-synonym specifically to hold file-sizes (in bytes).+type FileSize = Integer -- Matches the return-type of 'IO.hFileSize'.++-- | Get the size of the specified file.+findSize :: System.IO.FilePath -> IO FileSize+findSize f = do+ fileExists <- System.Directory.doesFileExist f++ if fileExists+ then System.IO.withFile f System.IO.ReadMode System.IO.hFileSize+ else {-not a file-} Control.Exception.throwIO $ System.IO.Error.mkIOError System.IO.Error.illegalOperationErrorType ("file=" ++ show f ++ " either doesn't exist or has an unexpected type") Nothing (Just f)++-- | Acquire statistics related to a list of file-sizes.+getFileSizeStatistics+ :: (Fractional mean, Floating standardDeviation)+ => [FileSize]+ -> (Int, mean, standardDeviation) -- ^ (The population-size, Mean size, Standard-deviation).+getFileSizeStatistics fileSizes = (+ length fileSizes,+ Factory.Math.Statistics.getMean fileSizes,+ Factory.Math.Statistics.getStandardDeviation fileSizes+ )+
+ src-lib/FishFood/Data/Verbosity.hs view
@@ -0,0 +1,37 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+{-+ Copyright (C) 2013 Dr. Alistair Ward++ This program is free software: you can redistribute it and/or modify+ it under the terms of the GNU General Public License as published by+ the Free Software Foundation, either version 3 of the License, or+ (at your option) any later version.++ This program is distributed in the hope that it will be useful,+ but WITHOUT ANY WARRANTY; without even the implied warranty of+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+ GNU General Public License for more details.++ You should have received a copy of the GNU General Public License+ along with this program. If not, see <http://www.gnu.org/licenses/>.+-}+{- |+ [@AUTHOR@] Dr. Alistair Ward++ [@DESCRIPTION@] Instance-definitions for 'Distribution.Verbosity.Verbosity'.+-}++module FishFood.Data.Verbosity(+-- * Constants+ range+) where++import qualified Distribution.Verbosity+import qualified ToolShed.Defaultable++instance ToolShed.Defaultable.Defaultable Distribution.Verbosity.Verbosity where+ defaultValue = Distribution.Verbosity.normal++-- | The constant complete range of values.+range :: [Distribution.Verbosity.Verbosity]+range = [minBound .. maxBound]
+ src-lib/FishFood/Profiler.hs view
@@ -0,0 +1,148 @@+{-+ Copyright (C) 2013 Dr. Alistair Ward++ This program is free software: you can redistribute it and/or modify+ it under the terms of the GNU General Public License as published by+ the Free Software Foundation, either version 3 of the License, or+ (at your option) any later version.++ This program is distributed in the hope that it will be useful,+ but WITHOUT ANY WARRANTY; without even the implied warranty of+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+ GNU General Public License for more details.++ You should have received a copy of the GNU General Public License+ along with this program. If not, see <http://www.gnu.org/licenses/>.+-}+{- |+ [@AUTHOR@] Dr. Alistair Ward++ [@DESCRIPTION@] Profiles lists of file-sizes.+-}++module FishFood.Profiler(+-- * Types+-- ** Type-synonyms+-- Probability,+-- Result,+-- FileSizeDistribution,+-- * Functions+ calculateFileSizeDistribution,+ formatFileSizeDistribution,+-- ** Accessors+ getFileSize,+ getValue+) where++import Control.Arrow((&&&),(***))+import qualified Control.Monad.Writer+import qualified Data.List+import qualified Data.Map+import qualified Data.Maybe+import qualified FishFood.Data.CommandOptions as Data.CommandOptions+import qualified FishFood.Data.File as Data.File+import FishFood.Data.Verbosity()+import qualified Text.Printf+import qualified ToolShed.Defaultable++-- | Define a type to represent the fractional closed unit-interval.+type Probability = Double++-- | Defines either the number of files or the probability that a files has a specific size.+type Result = (Data.File.FileSize, Either Int {-file-count-} Probability)++-- | Accessor.+getFileSize :: Result -> Data.File.FileSize+getFileSize = fst++-- | Accessor.+getValue :: Result -> Either Int {-file-count-} Probability+getValue = snd++-- | Defines either a /Probability Mass Function/ or /Frequency-distribution/.+type FileSizeDistribution = [Result]++-- | Calculates either the /Probability Mass Function/ or /Frequency-distribution/ for the specified files.+calculateFileSizeDistribution :: (Floating ratio, RealFrac ratio) => Data.CommandOptions.CommandOptions ratio -> [Data.File.FileSize] -> Control.Monad.Writer.Writer [String] FileSizeDistribution+calculateFileSizeDistribution commandOptions fileSizes = let+ binSizeDelta = Data.CommandOptions.getBinSizeDelta commandOptions+ deriveProbabilityMassFunction = Data.CommandOptions.getDeriveProbabilityMassFunction commandOptions+ nDecimalDigits = Data.CommandOptions.getNDecimalDigits commandOptions++ mean, standardDeviation :: Double+ (nFiles, mean, standardDeviation) = Data.File.getFileSizeStatistics fileSizes+ in do+ Control.Monad.Writer.tell [Text.Printf.printf "Files=%d, mean=%.*f, standard-deviation=%.*f" nFiles nDecimalDigits mean nDecimalDigits standardDeviation]++ return {-to Writer-monad-} $ if standardDeviation == 0+ then return {-to List-monad-} . (,) (head fileSizes) $ if deriveProbabilityMassFunction+ then Right 1 -- i.e. certainty.+ else Left nFiles -- i.e. all.+ else let+ getDefaultedBinSizeIncrement :: Maybe Data.File.FileSize -> Data.File.FileSize+ getDefaultedBinSizeIncrement = Data.Maybe.fromMaybe $ round standardDeviation `max` 1 {-minimum increment-} -- CAVEAT: guard against subsequent division by zero or infinite iteration.++ calculatedBinSizes :: [Data.File.FileSize]+ calculatedBinSizes = map (+ \fileSize -> either (+ div {-round down-} fileSize . getDefaultedBinSizeIncrement {-non-zero-}+ ) (+ floor {-round down-} . (`logBase` fromIntegral fileSize) -- CAVEAT: converts file-size 0, to bin-size -infinity.+ ) binSizeDelta+ ) fileSizes -- Each bin spans the semi-closed integral interval [size, succ size), so round down fractional values to match the lower bin.++ initialFrequencyDistribution :: Data.Map.Map Data.File.FileSize Int+ initialFrequencyDistribution = Data.Map.fromAscList . (+ `zip` repeat 0 -- The initial file-count.+ ) . takeWhile (+ <= maximum calculatedBinSizes+ ) . dropWhile (+ < minimum calculatedBinSizes+ ) $ either (+ \maybeBinSizeIncrement -> iterate (+ getDefaultedBinSizeIncrement {-non-zero-} maybeBinSizeIncrement) 0+ ) (+ \binRatio -> map round {-file-sizes are integral-} $ iterate (* binRatio) 1 -- The sequence could be started at fractional values in the open unit-interval, but the only value less than 1 which may be required is 0 (which isn't a sequence-member), which will be created later on demand.+ ) binSizeDelta++ mapBinSizeToFileSize :: Data.Map.Map Data.File.FileSize value -> Data.Map.Map Data.File.FileSize value+ mapBinSizeToFileSize = Data.Map.mapKeys $ \binSize -> either (+ (* binSize) . getDefaultedBinSizeIncrement+ ) (+ ceiling {-round up-} . (^^ binSize) -- Converts binSize -infinity, back to file-size 0.+ ) binSizeDelta -- Represent each bin by the minimum file-size it can accept.+ in Data.Map.toList . (+ if deriveProbabilityMassFunction+ then Data.Map.map Right . mapBinSizeToFileSize . Data.Map.map ((/ fromIntegral nFiles {-non-zero-}) . fromIntegral)+ else Data.Map.map Left . mapBinSizeToFileSize+ ) $ foldr (+ Data.Map.insertWith (+) `flip` 1 -- Count the files allocated to each bin.+ ) initialFrequencyDistribution calculatedBinSizes++-- | Formats a file-size distribution.+formatFileSizeDistribution :: Data.CommandOptions.CommandOptions ratio -> FileSizeDistribution -> String+formatFileSizeDistribution commandOptions = Data.List.intercalate "\n" . map (+ \(fileSize, value) -> fileSize ++ " " ++ value+ ) . (+ if Data.CommandOptions.getVerbosity commandOptions > ToolShed.Defaultable.defaultValue+ then (+ [+ (+ ($ (fileSizeWidth, fileSizeHeader)) &&& ($ (valueWidth, valueHeader))+ ) . uncurry $ Text.Printf.printf "%*s", -- Column-headers.+ (`replicate` '=') *** (`replicate` '=') $ columnWidths -- Separator-bar.+ ] +++ ) -- Section.+ else id+ ) . map (+ Text.Printf.printf "%*d" fileSizeWidth *** either (+ Text.Printf.printf "%*d" valueWidth+ ) (+ Text.Printf.printf "%.*f" $ Data.CommandOptions.getNDecimalDigits commandOptions+ )+ ) where+ fileSizeHeader, valueHeader :: String+ headers@(fileSizeHeader, valueHeader) = (,) "Bin-size" $ if Data.CommandOptions.getDeriveProbabilityMassFunction commandOptions then "Probability" else "Frequency"++ fileSizeWidth, valueWidth :: Int+ columnWidths@(fileSizeWidth, valueWidth) = (`max` 10) . length *** length $ headers -- CAVEAT: the data-length may exceed the header-length, so define a minimum.+
+ src-test/FishFood/Test/Data/CommandOptions.hs view
@@ -0,0 +1,65 @@+{-# LANGUAGE CPP #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+{-+ Copyright (C) 2013-2015 Dr. Alistair Ward++ This program is free software: you can redistribute it and/or modify+ it under the terms of the GNU General Public License as published by+ the Free Software Foundation, either version 3 of the License, or+ (at your option) any later version.++ This program is distributed in the hope that it will be useful,+ but WITHOUT ANY WARRANTY; without even the implied warranty of+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+ GNU General Public License for more details.++ You should have received a copy of the GNU General Public License+ along with this program. If not, see <http://www.gnu.org/licenses/>.+-}+{- |+ [@AUTHOR@] Dr. Alistair Ward++ [@DESCRIPTION@] Implements 'Test.QuickCheck.Arbitrary' for 'Data.CommandOptions.CommandOptions'.+-}++module FishFood.Test.Data.CommandOptions(+-- * Types+-- ** Type-synonyms+ CommandOptions+) where++import qualified Test.QuickCheck+import qualified FishFood.Data.CommandOptions as Data.CommandOptions+import FishFood.Test.Data.Verbosity()++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative((<$>), (<*>))+#endif++instance (+ Enum ratio,+ Fractional ratio,+ Ord ratio,+ Show ratio,+ Test.QuickCheck.Arbitrary ratio+ ) => Test.QuickCheck.Arbitrary (Data.CommandOptions.CommandOptions ratio) where+ arbitrary = do+ deriveProbabilityMassFunction <- Test.QuickCheck.arbitrary++ Data.CommandOptions.mkCommandOptions <$> (+ either (+ Left . (succ . abs <$> {-into Maybe-monad-})+ ) (+ Right . (+ recip 1000000) . succ . abs+ ) <$> Test.QuickCheck.arbitrary {-binSizeDelta-}+ ) <*> return {-to Gen-monad-} deriveProbabilityMassFunction <*> (+ max (+ if deriveProbabilityMassFunction then 1 else 0+ ) . (+ `mod` 15 {-maximum permissible-}+ ) <$> Test.QuickCheck.arbitrary {-nDecimalDigits-}+ ) <*> Test.QuickCheck.arbitrary {-verbosity-}++-- | Defines a concrete type for testing.+type CommandOptions = Data.CommandOptions.CommandOptions Double+
+ src-test/FishFood/Test/Data/Verbosity.hs view
@@ -0,0 +1,31 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+{-+ Copyright (C) 2013 Dr. Alistair Ward++ This program is free software: you can redistribute it and/or modify+ it under the terms of the GNU General Public License as published by+ the Free Software Foundation, either version 3 of the License, or+ (at your option) any later version.++ This program is distributed in the hope that it will be useful,+ but WITHOUT ANY WARRANTY; without even the implied warranty of+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+ GNU General Public License for more details.++ You should have received a copy of the GNU General Public License+ along with this program. If not, see <http://www.gnu.org/licenses/>.+-}+{- |+ [@AUTHOR@] Dr. Alistair Ward++ [@DESCRIPTION@] Implements 'Test.QuickCheck.Arbitrary' for 'Distribution.Verbosity.Verbosity'.+-}++module FishFood.Test.Data.Verbosity() where++import qualified Distribution.Verbosity+import qualified FishFood.Data.Verbosity as Data.Verbosity+import qualified Test.QuickCheck++instance Test.QuickCheck.Arbitrary Distribution.Verbosity.Verbosity where+ arbitrary = Test.QuickCheck.elements Data.Verbosity.range
+ src-test/FishFood/Test/Profiler.hs view
@@ -0,0 +1,58 @@+{-+ Copyright (C) 2013-2015 Dr. Alistair Ward++ This program is free software: you can redistribute it and/or modify+ it under the terms of the GNU General Public License as published by+ the Free Software Foundation, either version 3 of the License, or+ (at your option) any later version.++ This program is distributed in the hope that it will be useful,+ but WITHOUT ANY WARRANTY; without even the implied warranty of+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+ GNU General Public License for more details.++ You should have received a copy of the GNU General Public License+ along with this program. If not, see <http://www.gnu.org/licenses/>.+-}+{- |+ [@AUTHOR@] Dr. Alistair Ward++ [@DESCRIPTION@] Defines /QuickCheck/-properties for 'Profiler'.+-}++module FishFood.Test.Profiler(+-- * Constants+ results+) where++import qualified Control.Monad.Writer+import qualified Data.Either+import qualified Data.Set+import qualified FishFood.Data.CommandOptions as Data.CommandOptions+import qualified FishFood.Data.File as Data.File+import qualified FishFood.Profiler as Profiler+import qualified FishFood.Test.Data.CommandOptions as Test.Data.CommandOptions+import qualified Test.QuickCheck+import Test.QuickCheck((==>))+import qualified ToolShed.SelfValidate++-- | The constant test-results for this data-type.+results :: IO [Test.QuickCheck.Result]+results = mapM Test.QuickCheck.quickCheckResult [prop_calculateProbabilityMassFunction, prop_calculateFileSizeFrequencyDistribution, prop_attendance] where+ prop_calculateProbabilityMassFunction, prop_calculateFileSizeFrequencyDistribution, prop_attendance :: Test.Data.CommandOptions.CommandOptions -> [Data.File.FileSize] -> Test.QuickCheck.Property+ prop_calculateProbabilityMassFunction commandOptions fileSizes = not (null fileSizes) && ToolShed.SelfValidate.isValid commandOptions' ==> Test.QuickCheck.label "prop_calculateProbabilityMassFunction" . (<= recip 1000000) . (+ negate 1) . sum . Data.Either.rights {-probabilities-} . map Profiler.getValue . fst {-distribution-} . Control.Monad.Writer.runWriter . Profiler.calculateFileSizeDistribution commandOptions' $ map abs fileSizes where+ commandOptions' = commandOptions { Data.CommandOptions.getDeriveProbabilityMassFunction = True }++ prop_calculateFileSizeFrequencyDistribution commandOptions fileSizes = not (null fileSizes) && ToolShed.SelfValidate.isValid commandOptions' ==> Test.QuickCheck.label "prop_calculateFileSizeFrequencyDistribution" . (== length fileSizes) . sum . Data.Either.lefts {-frequency-} . map Profiler.getValue . fst {-distribution-} . Control.Monad.Writer.runWriter . Profiler.calculateFileSizeDistribution commandOptions' $ map abs fileSizes where+ commandOptions' = commandOptions { Data.CommandOptions.getDeriveProbabilityMassFunction = False }++ prop_attendance commandOptions fileSizes = not (null fileSizes') && ToolShed.SelfValidate.isValid commandOptions' ==> Test.QuickCheck.label "prop_attendance" . (+ == Data.Set.fromList fileSizes'+ ) . Data.Set.fromList . map Profiler.getFileSize . filter (+ (/= 0) . either fromIntegral id . Profiler.getValue -- Remove file-sizes which match zero actual files.+ ) . fst {-distribution-} . Control.Monad.Writer.runWriter $ Profiler.calculateFileSizeDistribution commandOptions' fileSizes' where+ fileSizes' = map abs fileSizes++ commandOptions' :: Test.Data.CommandOptions.CommandOptions+ commandOptions' = Data.CommandOptions.setBinSizeIncrement 1 commandOptions+
+ src-test/Main.hs view
@@ -0,0 +1,44 @@+{-+ Copyright (C) 2015 Dr. Alistair Ward++ This program is free software: you can redistribute it and/or modify+ it under the terms of the GNU General Public License as published by+ the Free Software Foundation, either version 3 of the License, or+ (at your option) any later version.++ This program is distributed in the hope that it will be useful,+ but WITHOUT ANY WARRANTY; without even the implied warranty of+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+ GNU General Public License for more details.++ You should have received a copy of the GNU General Public License+ along with this program. If not, see <http://www.gnu.org/licenses/>.+-}+{- |+ [@AUTHOR@] Dr. Alistair Ward++ [@DESCRIPTION@]++ * The entry-point to the application's test-suite.+-}++module Main(main) where++import Control.Arrow((***))+import qualified Control.Monad+import qualified FishFood.Test.Profiler as Test.Profiler+import qualified System.Exit+import qualified ToolShed.Test.QuickCheck.Result++-- | Entry-point.+main :: IO ()+main = mapM_ (+ snd {-exit-status-} . (+ putStrLn . (++ ":") *** (+ >>= (`Control.Monad.unless` System.Exit.exitFailure) . all ToolShed.Test.QuickCheck.Result.isSuccessful+ )+ )+ ) [+ ("Profiler", Test.Profiler.results)+ ]+
− src/FishFood/Data/CommandOptions.hs
@@ -1,113 +0,0 @@-{-- Copyright (C) 2013 Dr. Alistair Ward-- This program is free software: you can redistribute it and/or modify- it under the terms of the GNU General Public License as published by- the Free Software Foundation, either version 3 of the License, or- (at your option) any later version.-- This program is distributed in the hope that it will be useful,- but WITHOUT ANY WARRANTY; without even the implied warranty of- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the- GNU General Public License for more details.-- You should have received a copy of the GNU General Public License- along with this program. If not, see <http://www.gnu.org/licenses/>.--}-{- |- [@AUTHOR@] Dr. Alistair Ward-- [@DESCRIPTION@]-- * Defines options for program-operation.-- * Defines an appropriate default value, which is expected to be over-ridden on the command-line.-- * Self-validates.--}--module FishFood.Data.CommandOptions(--- * Types--- ** Type-synonyms- BinSizeDelta,--- ** Data-types- CommandOptions(--- MkCommandOptions,- getBinSizeDelta,- getDeriveProbabilityMassFunction,- getNDecimalDigits,- getVerbosity- ),--- * Functions--- ** Constructors- mkCommandOptions,--- ** Mutators- setBinSizeIncrement,- setBinSizeRatio-) where--import qualified Data.Maybe-import qualified Distribution.Verbosity-import qualified FishFood.Data.File as Data.File-import qualified ToolShed.Defaultable-import qualified ToolShed.SelfValidate---- | Either an arithmetic size-increase for which there's a default, or a geometric size-ratio.-type BinSizeDelta ratio = Either (Maybe Data.File.FileSize) ratio---- | Declares a record to contain command-line options.-data CommandOptions ratio = MkCommandOptions {- getBinSizeDelta :: BinSizeDelta ratio, -- ^ Either the arithmetic size-increase (defaulting to one standard-deviation), or the geometric size-ratio, of the sequence of bins into which files are categorized.- getDeriveProbabilityMassFunction :: Bool, -- ^ Whether to derive the "Probability mass function" rather than the "Frequency-distribution".- getNDecimalDigits :: Int, -- ^ The precision to which fractional data is displayed.- getVerbosity :: Distribution.Verbosity.Verbosity -- ^ The threshold for ancillary information-output.-} deriving Show--instance ToolShed.Defaultable.Defaultable (CommandOptions ratio) where- defaultValue = MkCommandOptions {- getBinSizeDelta = Left Nothing, -- Interpreted as one standard-deviation.- getDeriveProbabilityMassFunction = False,- getNDecimalDigits = 3,- getVerbosity = Distribution.Verbosity.normal- }--instance (Num ratio, Ord ratio, Show ratio) => ToolShed.SelfValidate.SelfValidator (CommandOptions ratio) where- getErrors commandOptions@MkCommandOptions {- getBinSizeDelta = binSizeDelta,- getDeriveProbabilityMassFunction = deriveProbabilityMassFunction,- getNDecimalDigits = nDecimalDigits- } = map snd $ filter fst [- (- either (Data.Maybe.maybe False (<= 0)) (<= 1) binSizeDelta,- "either the bin-size's arithmetic increase must exceed zero, or it's geometric ratio must exceed one; " ++ show commandOptions ++ "."- ), (- deriveProbabilityMassFunction && nDecimalDigits < 1,- "the number of decimal digits must exceed zero to adequately represent probabilities; " ++ show commandOptions ++ "."- ),- let- maxNDecimalDigits = floor $ fromIntegral (- floatDigits (- undefined :: Double -- CAVEAT: the actual type could be merely 'Float', but that's currently unknown.- )- ) * (logBase 10 2 :: Double)- in (- nDecimalDigits > maxNDecimalDigits,- "the number of decimal digits shouldn't exceed " ++ show maxNDecimalDigits ++ "; " ++ show commandOptions ++ "."- )- ]---- | Smart constructor.-mkCommandOptions :: (Num ratio, Ord ratio, Show ratio) => BinSizeDelta ratio -> Bool -> Int -> Distribution.Verbosity.Verbosity -> CommandOptions ratio-mkCommandOptions binSizeDelta deriveProbabilityMassFunction nDecimalDigits verbosity- | ToolShed.SelfValidate.isValid commandOptions = commandOptions- | otherwise = error $ "FishFood.Data.CommandOptions.mkCommandOptions:\t" ++ ToolShed.SelfValidate.getFirstError commandOptions- where- commandOptions = MkCommandOptions binSizeDelta deriveProbabilityMassFunction nDecimalDigits verbosity---- | Mutator.-setBinSizeIncrement :: Data.File.FileSize -> CommandOptions ratio -> CommandOptions ratio-setBinSizeIncrement fileSize commandOptions = commandOptions { getBinSizeDelta = Left $ Just fileSize }---- | Mutator.-setBinSizeRatio :: ratio -> CommandOptions ratio -> CommandOptions ratio-setBinSizeRatio ratio commandOptions = commandOptions { getBinSizeDelta = Right ratio }
− src/FishFood/Data/File.hs
@@ -1,60 +0,0 @@-{-- Copyright (C) 2013-2014 Dr. Alistair Ward-- This program is free software: you can redistribute it and/or modify- it under the terms of the GNU General Public License as published by- the Free Software Foundation, either version 3 of the License, or- (at your option) any later version.-- This program is distributed in the hope that it will be useful,- but WITHOUT ANY WARRANTY; without even the implied warranty of- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the- GNU General Public License for more details.-- You should have received a copy of the GNU General Public License- along with this program. If not, see <http://www.gnu.org/licenses/>.--}-{- |- [@AUTHOR@] Dr. Alistair Ward-- [@DESCRIPTION@] Allows one to find the size of a file, & to gather statistics on a population.--}--module FishFood.Data.File(--- * Types--- ** Type-synonyms- FileSize,--- * Functions- findSize,- getFileSizeStatistics-) where--import qualified Control.Exception-import qualified Factory.Math.Statistics-import qualified System.Directory-import qualified System.IO-import qualified System.IO.Error---- | A type-synonym specifically to hold file-sizes (in bytes).-type FileSize = Integer -- Matches the return-type of 'IO.hFileSize'.---- | Get the size of the specified file.-findSize :: System.IO.FilePath -> IO FileSize-findSize f = do- fileExists <- System.Directory.doesFileExist f-- if fileExists- then System.IO.withFile f System.IO.ReadMode System.IO.hFileSize- else {-not a file-} Control.Exception.throwIO $ System.IO.Error.mkIOError System.IO.Error.illegalOperationErrorType ("file=" ++ show f ++ " either doesn't exist or has an unexpected type") Nothing (Just f)---- | Acquire statistics related to a list of file-sizes.-getFileSizeStatistics- :: (Fractional mean, Floating standardDeviation)- => [FileSize]- -> (Int, mean, standardDeviation) -- ^ (The population-size, Mean size, Standard-deviation).-getFileSizeStatistics fileSizes = (- length fileSizes,- Factory.Math.Statistics.getMean fileSizes,- Factory.Math.Statistics.getStandardDeviation fileSizes- )-
− src/FishFood/Data/Verbosity.hs
@@ -1,37 +0,0 @@-{-# OPTIONS_GHC -fno-warn-orphans #-}-{-- Copyright (C) 2013 Dr. Alistair Ward-- This program is free software: you can redistribute it and/or modify- it under the terms of the GNU General Public License as published by- the Free Software Foundation, either version 3 of the License, or- (at your option) any later version.-- This program is distributed in the hope that it will be useful,- but WITHOUT ANY WARRANTY; without even the implied warranty of- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the- GNU General Public License for more details.-- You should have received a copy of the GNU General Public License- along with this program. If not, see <http://www.gnu.org/licenses/>.--}-{- |- [@AUTHOR@] Dr. Alistair Ward-- [@DESCRIPTION@] Instance-definitions for 'Distribution.Verbosity.Verbosity'.--}--module FishFood.Data.Verbosity(--- * Constants- range-) where--import qualified Distribution.Verbosity-import qualified ToolShed.Defaultable--instance ToolShed.Defaultable.Defaultable Distribution.Verbosity.Verbosity where- defaultValue = Distribution.Verbosity.normal---- | The constant complete range of values.-range :: [Distribution.Verbosity.Verbosity]-range = [minBound .. maxBound]
− src/FishFood/Profiler.hs
@@ -1,148 +0,0 @@-{-- Copyright (C) 2013 Dr. Alistair Ward-- This program is free software: you can redistribute it and/or modify- it under the terms of the GNU General Public License as published by- the Free Software Foundation, either version 3 of the License, or- (at your option) any later version.-- This program is distributed in the hope that it will be useful,- but WITHOUT ANY WARRANTY; without even the implied warranty of- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the- GNU General Public License for more details.-- You should have received a copy of the GNU General Public License- along with this program. If not, see <http://www.gnu.org/licenses/>.--}-{- |- [@AUTHOR@] Dr. Alistair Ward-- [@DESCRIPTION@] Profiles lists of file-sizes.--}--module FishFood.Profiler(--- * Types--- ** Type-synonyms--- Probability,--- Result,--- FileSizeDistribution,--- * Functions- calculateFileSizeDistribution,- formatFileSizeDistribution,--- ** Accessors- getFileSize,- getValue-) where--import Control.Arrow((&&&),(***))-import qualified Control.Monad.Writer-import qualified Data.List-import qualified Data.Map-import qualified Data.Maybe-import qualified FishFood.Data.CommandOptions as Data.CommandOptions-import qualified FishFood.Data.File as Data.File-import FishFood.Data.Verbosity()-import qualified Text.Printf-import qualified ToolShed.Defaultable---- | Define a type to represent the fractional closed unit-interval.-type Probability = Double---- | Defines either the number of files or the probability that a files has a specific size.-type Result = (Data.File.FileSize, Either Int {-file-count-} Probability)---- | Accessor.-getFileSize :: Result -> Data.File.FileSize-getFileSize = fst---- | Accessor.-getValue :: Result -> Either Int {-file-count-} Probability-getValue = snd---- | Defines either a /Probability Mass Function/ or /Frequency-distribution/.-type FileSizeDistribution = [Result]---- | Calculates either the /Probability Mass Function/ or /Frequency-distribution/ for the specified files.-calculateFileSizeDistribution :: (Floating ratio, RealFrac ratio) => Data.CommandOptions.CommandOptions ratio -> [Data.File.FileSize] -> Control.Monad.Writer.Writer [String] FileSizeDistribution-calculateFileSizeDistribution commandOptions fileSizes = let- binSizeDelta = Data.CommandOptions.getBinSizeDelta commandOptions- deriveProbabilityMassFunction = Data.CommandOptions.getDeriveProbabilityMassFunction commandOptions- nDecimalDigits = Data.CommandOptions.getNDecimalDigits commandOptions-- mean, standardDeviation :: Double- (nFiles, mean, standardDeviation) = Data.File.getFileSizeStatistics fileSizes- in do- Control.Monad.Writer.tell [Text.Printf.printf "Files=%d, mean=%.*f, standard-deviation=%.*f" nFiles nDecimalDigits mean nDecimalDigits standardDeviation]-- return {-to Writer-monad-} $ if standardDeviation == 0- then return {-to List-monad-} . (,) (head fileSizes) $ if deriveProbabilityMassFunction- then Right 1 -- i.e. certainty.- else Left nFiles -- i.e. all.- else let- getDefaultedBinSizeIncrement :: Maybe Data.File.FileSize -> Data.File.FileSize- getDefaultedBinSizeIncrement = Data.Maybe.fromMaybe $ round standardDeviation `max` 1 {-minimum increment-} -- CAVEAT: guard against subsequent division by zero or infinite iteration.-- calculatedBinSizes :: [Data.File.FileSize]- calculatedBinSizes = map (- \fileSize -> either (- div {-round down-} fileSize . getDefaultedBinSizeIncrement {-non-zero-}- ) (- floor {-round down-} . (`logBase` fromIntegral fileSize) -- CAVEAT: converts file-size 0, to bin-size -infinity.- ) binSizeDelta- ) fileSizes -- Each bin spans the semi-closed integral interval [size, succ size), so round down fractional values to match the lower bin.-- initialFrequencyDistribution :: Data.Map.Map Data.File.FileSize Int- initialFrequencyDistribution = Data.Map.fromAscList . (- `zip` repeat 0 -- The initial file-count.- ) . takeWhile (- <= maximum calculatedBinSizes- ) . dropWhile (- < minimum calculatedBinSizes- ) $ either (- \maybeBinSizeIncrement -> iterate (+ getDefaultedBinSizeIncrement {-non-zero-} maybeBinSizeIncrement) 0- ) (- \binRatio -> map round {-file-sizes are integral-} $ iterate (* binRatio) 1 -- The sequence could be started at fractional values in the open unit-interval, but the only value less than 1 which may be required is 0 (which isn't a sequence-member), which will be created later on demand.- ) binSizeDelta-- mapBinSizeToFileSize :: Data.Map.Map Data.File.FileSize value -> Data.Map.Map Data.File.FileSize value- mapBinSizeToFileSize = Data.Map.mapKeys $ \binSize -> either (- (* binSize) . getDefaultedBinSizeIncrement- ) (- ceiling {-round up-} . (^^ binSize) -- Converts binSize -infinity, back to file-size 0.- ) binSizeDelta -- Represent each bin by the minimum file-size it can accept.- in Data.Map.toList . (- if deriveProbabilityMassFunction- then Data.Map.map Right . mapBinSizeToFileSize . Data.Map.map ((/ fromIntegral nFiles {-non-zero-}) . fromIntegral)- else Data.Map.map Left . mapBinSizeToFileSize- ) $ foldr (- Data.Map.insertWith (+) `flip` 1 -- Count the files allocated to each bin.- ) initialFrequencyDistribution calculatedBinSizes---- | Formats a file-size distribution.-formatFileSizeDistribution :: Data.CommandOptions.CommandOptions ratio -> FileSizeDistribution -> String-formatFileSizeDistribution commandOptions = Data.List.intercalate "\n" . map (- \(fileSize, value) -> fileSize ++ " " ++ value- ) . (- if Data.CommandOptions.getVerbosity commandOptions > ToolShed.Defaultable.defaultValue- then (- [- (- ($ (fileSizeWidth, fileSizeHeader)) &&& ($ (valueWidth, valueHeader))- ) . uncurry $ Text.Printf.printf "%*s", -- Column-headers.- (`replicate` '=') *** (`replicate` '=') $ columnWidths -- Separator-bar.- ] ++- ) -- Section.- else id- ) . map (- Text.Printf.printf "%*d" fileSizeWidth *** either (- Text.Printf.printf "%*d" valueWidth- ) (- Text.Printf.printf "%.*f" $ Data.CommandOptions.getNDecimalDigits commandOptions- )- ) where- fileSizeHeader, valueHeader :: String- headers@(fileSizeHeader, valueHeader) = (,) "Bin-size" $ if Data.CommandOptions.getDeriveProbabilityMassFunction commandOptions then "Probability" else "Frequency"-- fileSizeWidth, valueWidth :: Int- columnWidths@(fileSizeWidth, valueWidth) = (`max` 10) . length *** length $ headers -- CAVEAT: the data-length may exceed the header-length, so define a minimum.-
− src/FishFood/Test/Data/CommandOptions.hs
@@ -1,62 +0,0 @@-{-# OPTIONS_GHC -fno-warn-orphans #-}-{-- Copyright (C) 2013-2015 Dr. Alistair Ward-- This program is free software: you can redistribute it and/or modify- it under the terms of the GNU General Public License as published by- the Free Software Foundation, either version 3 of the License, or- (at your option) any later version.-- This program is distributed in the hope that it will be useful,- but WITHOUT ANY WARRANTY; without even the implied warranty of- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the- GNU General Public License for more details.-- You should have received a copy of the GNU General Public License- along with this program. If not, see <http://www.gnu.org/licenses/>.--}-{- |- [@AUTHOR@] Dr. Alistair Ward-- [@DESCRIPTION@] Implements 'Test.QuickCheck.Arbitrary' for 'Data.CommandOptions.CommandOptions'.--}--module FishFood.Test.Data.CommandOptions(--- * Types--- ** Type-synonyms- CommandOptions-) where--import Prelude hiding ((<$>), (<*>)) -- The "Prelude" from 'base-4.8' exports this symbol.-import Control.Applicative((<$>), (<*>))-import qualified Test.QuickCheck-import qualified FishFood.Data.CommandOptions as Data.CommandOptions-import FishFood.Test.Data.Verbosity()--instance (- Enum ratio,- Fractional ratio,- Ord ratio,- Show ratio,- Test.QuickCheck.Arbitrary ratio- ) => Test.QuickCheck.Arbitrary (Data.CommandOptions.CommandOptions ratio) where- arbitrary = do- deriveProbabilityMassFunction <- Test.QuickCheck.arbitrary-- Data.CommandOptions.mkCommandOptions <$> (- either (- Left . (succ . abs <$> {-into Maybe-monad-})- ) (- Right . (+ recip 1000000) . succ . abs- ) <$> Test.QuickCheck.arbitrary {-binSizeDelta-}- ) <*> return {-to Gen-monad-} deriveProbabilityMassFunction <*> (- max (- if deriveProbabilityMassFunction then 1 else 0- ) . (- `mod` 15 {-maximum permissible-}- ) <$> Test.QuickCheck.arbitrary {-nDecimalDigits-}- ) <*> Test.QuickCheck.arbitrary {-verbosity-}---- | Defines a concrete type for testing.-type CommandOptions = Data.CommandOptions.CommandOptions Double-
− src/FishFood/Test/Data/Verbosity.hs
@@ -1,31 +0,0 @@-{-# OPTIONS_GHC -fno-warn-orphans #-}-{-- Copyright (C) 2013 Dr. Alistair Ward-- This program is free software: you can redistribute it and/or modify- it under the terms of the GNU General Public License as published by- the Free Software Foundation, either version 3 of the License, or- (at your option) any later version.-- This program is distributed in the hope that it will be useful,- but WITHOUT ANY WARRANTY; without even the implied warranty of- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the- GNU General Public License for more details.-- You should have received a copy of the GNU General Public License- along with this program. If not, see <http://www.gnu.org/licenses/>.--}-{- |- [@AUTHOR@] Dr. Alistair Ward-- [@DESCRIPTION@] Implements 'Test.QuickCheck.Arbitrary' for 'Distribution.Verbosity.Verbosity'.--}--module FishFood.Test.Data.Verbosity() where--import qualified Distribution.Verbosity-import qualified FishFood.Data.Verbosity as Data.Verbosity-import qualified Test.QuickCheck--instance Test.QuickCheck.Arbitrary Distribution.Verbosity.Verbosity where- arbitrary = Test.QuickCheck.elements Data.Verbosity.range
− src/FishFood/Test/Profiler.hs
@@ -1,58 +0,0 @@-{-- Copyright (C) 2013 Dr. Alistair Ward-- This program is free software: you can redistribute it and/or modify- it under the terms of the GNU General Public License as published by- the Free Software Foundation, either version 3 of the License, or- (at your option) any later version.-- This program is distributed in the hope that it will be useful,- but WITHOUT ANY WARRANTY; without even the implied warranty of- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the- GNU General Public License for more details.-- You should have received a copy of the GNU General Public License- along with this program. If not, see <http://www.gnu.org/licenses/>.--}-{- |- [@AUTHOR@] Dr. Alistair Ward-- [@DESCRIPTION@] Implements 'Test.QuickCheck.Arbitrary' & defines /QuickCheck/-properties for 'Profiler'.--}--module FishFood.Test.Profiler(--- * Functions- quickChecks-) where--import qualified Control.Monad.Writer-import qualified Data.Either-import qualified Data.Set-import qualified FishFood.Data.CommandOptions as Data.CommandOptions-import qualified FishFood.Data.File as Data.File-import qualified FishFood.Profiler as Profiler-import qualified FishFood.Test.Data.CommandOptions as Test.Data.CommandOptions-import qualified Test.QuickCheck-import Test.QuickCheck((==>))-import qualified ToolShed.SelfValidate---- | Defines invariant properties.-quickChecks :: IO ()-quickChecks = Test.QuickCheck.quickCheck `mapM_` [prop_calculateProbabilityMassFunction, prop_calculateFileSizeFrequencyDistribution, prop_attendance] where- prop_calculateProbabilityMassFunction, prop_calculateFileSizeFrequencyDistribution, prop_attendance :: Test.Data.CommandOptions.CommandOptions -> [Data.File.FileSize] -> Test.QuickCheck.Property- prop_calculateProbabilityMassFunction commandOptions fileSizes = not (null fileSizes) && ToolShed.SelfValidate.isValid commandOptions' ==> Test.QuickCheck.label "prop_calculateProbabilityMassFunction" . (<= recip 1000000) . (+ negate 1) . sum . Data.Either.rights {-probabilities-} . map Profiler.getValue . fst {-distribution-} . Control.Monad.Writer.runWriter . Profiler.calculateFileSizeDistribution commandOptions' $ map abs fileSizes where- commandOptions' = commandOptions { Data.CommandOptions.getDeriveProbabilityMassFunction = True }-- prop_calculateFileSizeFrequencyDistribution commandOptions fileSizes = not (null fileSizes) && ToolShed.SelfValidate.isValid commandOptions' ==> Test.QuickCheck.label "prop_calculateFileSizeFrequencyDistribution" . (== length fileSizes) . sum . Data.Either.lefts {-frequency-} . map Profiler.getValue . fst {-distribution-} . Control.Monad.Writer.runWriter . Profiler.calculateFileSizeDistribution commandOptions' $ map abs fileSizes where- commandOptions' = commandOptions { Data.CommandOptions.getDeriveProbabilityMassFunction = False }-- prop_attendance commandOptions fileSizes = not (null fileSizes') && ToolShed.SelfValidate.isValid commandOptions' ==> Test.QuickCheck.label "prop_attendance" . (- == Data.Set.fromList fileSizes'- ) . Data.Set.fromList . map Profiler.getFileSize . filter (- (/= 0) . either fromIntegral id . Profiler.getValue -- Remove file-sizes which match zero actual files.- ) . fst {-distribution-} . Control.Monad.Writer.runWriter $ Profiler.calculateFileSizeDistribution commandOptions' fileSizes' where- fileSizes' = map abs fileSizes-- commandOptions' :: Test.Data.CommandOptions.CommandOptions- commandOptions' = Data.CommandOptions.setBinSizeIncrement 1 commandOptions-
− src/FishFood/Test/QuickChecks.hs
@@ -1,38 +0,0 @@-{-- Copyright (C) 2013 Dr. Alistair Ward-- This program is free software: you can redistribute it and/or modify- it under the terms of the GNU General Public License as published by- the Free Software Foundation, either version 3 of the License, or- (at your option) any later version.-- This program is distributed in the hope that it will be useful,- but WITHOUT ANY WARRANTY; without even the implied warranty of- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the- GNU General Public License for more details.-- You should have received a copy of the GNU General Public License- along with this program. If not, see <http://www.gnu.org/licenses/>.--}-{- |- [@AUTHOR@] Dr. Alistair Ward-- [@DESCRIPTION@] Calls the /quickChecks/-functions for modules supporting this feature.--}--module FishFood.Test.QuickChecks(--- * Functions- run-) where--import qualified Control.Arrow-import qualified FishFood.Test.Profiler as Test.Profiler---- | Runs checks on invariant properties.-run :: IO ()-run = mapM_ (- uncurry (>>) . Control.Arrow.first putStrLn- ) [- ("Profiler", Test.Profiler.quickChecks)- ]-
− src/Main.hs
@@ -1,181 +0,0 @@-{-- Copyright (C) 2013-2015 Dr. Alistair Ward-- This program is free software: you can redistribute it and/or modify- it under the terms of the GNU General Public License as published by- the Free Software Foundation, either version 3 of the License, or- (at your option) any later version.-- This program is distributed in the hope that it will be useful,- but WITHOUT ANY WARRANTY; without even the implied warranty of- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the- GNU General Public License for more details.-- You should have received a copy of the GNU General Public License- along with this program. If not, see <http://www.gnu.org/licenses/>.--}-{- |- [@AUTHOR@] Dr. Alistair Ward-- [@DESCRIPTION@]-- * Contains the entry-point of the application.-- * Processes the command-line arguments.-- * Delegates the task to "Profiler".--}--module Main(main) where--import Prelude hiding ((<$>), (<*>)) -- The "Prelude" from 'base-4.8' exports this symbol.-import Control.Applicative((<$>),(<*>))-import qualified Control.Monad-import qualified Control.Monad.Writer-import qualified Data.List-import qualified Data.Maybe-import qualified Data.Version-import qualified Distribution.Package-import qualified Distribution.Text-import qualified Distribution.Version-import qualified FishFood.Data.CommandOptions as Data.CommandOptions-import qualified FishFood.Data.File as Data.File-import qualified FishFood.Data.Verbosity as Data.Verbosity-import qualified FishFood.Profiler as Profiler-import qualified FishFood.Test.QuickChecks as Test.QuickChecks-import qualified Paths_fishfood as Paths -- Either local stub, or package-instance autogenerated by 'Setup.hs build'.-import qualified System.Console.GetOpt as G-import qualified System.Environment-import qualified System.Exit-import qualified System.Info-import qualified System.IO-import qualified System.IO.Error-import qualified ToolShed.Data.List-import qualified ToolShed.Defaultable-import qualified ToolShed.SelfValidate---- | Define the concrete type of command-options.-type CommandOptions = Data.CommandOptions.CommandOptions Double---- | Used to thread user-defined command-line options, though the list of functions which implement them.-type CommandLineAction = CommandOptions -> IO CommandOptions -- Supplied as the type-argument to 'G.OptDescr'.---- | On failure to parse the specified string, returns an explanatory error.-read' :: Read a => String -> String -> a-read' errorMessage s = case reads s of- [(x, "")] -> x- _ -> error $ errorMessage ++ show s---- | On failure to parse a command-line argument, returns an explanatory error.-readCommandArg :: Read a => String -> a-readCommandArg = read' "failed to parse command-line argument "---- | Reads a bounded integral from the command-line, guarding against overflow.-readBoundedIntegral :: Integral i => String -> i-readBoundedIntegral s- | fromIntegral bounded /= unbounded = error $ "integral value exceeds permissible bounds; " ++ show unbounded ++ "."- | otherwise = bounded- where- unbounded = readCommandArg s- bounded = fromInteger unbounded--{- |- * Parses the command-line options, which over-ride default values.-- * Any arguments which follow known options, are interpreted as file-names.-- * If the specified file-name is /-/, then the actual file-names are read from /standard-input/, to augment any other non-options specified.--}-main :: IO ()-main = do- progName <- System.Environment.getProgName-- let- defaultCommandOptions :: CommandOptions- defaultCommandOptions = ToolShed.Defaultable.defaultValue-- optDescrList :: [G.OptDescr CommandLineAction]- optDescrList = [--- String [String] (G.ArgDescr CommandLineAction) String- G.Option "?" ["help"] (G.NoArg $ const printUsage) "Display this help, & then exit.",- G.Option "" ["verbosity"] (- setVerbosity `G.ReqArg` ToolShed.Data.List.showListWith listDelimiters Data.Verbosity.range ""-- ) ("Define the log-level; default '" ++ show (Data.CommandOptions.getVerbosity defaultCommandOptions) ++ "'."- ),- G.Option "v" ["version"] (G.NoArg $ const printVersion) "Print version-information, & then exit.",- G.Option "s" ["binSizeIncrement"] (setBinSizeIncrement `G.ReqArg` "<Int>") "The constant size-increase in the arithmetic sequence of bins into which the byte-sizes of files are categorised; defaulting to one standard-deviation.",- G.Option "r" ["binSizeRatio"] (setBinSizeRatio `G.ReqArg` "<Float>") "The constant size-ratio in the geometric sequence of bins into which the byte-sizes of files are categorised; an alternative to 'binSizeIncrement'.",- G.Option "p" ["deriveProbabilityMassFunction"] (setDeriveProbabilityMassFunction `G.OptArg` "<Bool>") ("Whether to derive the \"Probability mass function\" rather than the \"Frequency-distribution\"; default '" ++ show (Data.CommandOptions.getDeriveProbabilityMassFunction defaultCommandOptions) ++ "'."),- G.Option "d" ["nDecimalDigits"] (setNDecimalDigits `G.ReqArg` "<Int>") ("The precision to which fractional auxiliary data is displayed; default " ++ show (Data.CommandOptions.getNDecimalDigits defaultCommandOptions) ++ "."),- G.Option "q" ["runQuickChecks"] (G.NoArg runQuickChecks) "Test the implementation, by validating some invariant properties using arbitrary data; & then exit."- ] where- listDelimiters = ('(', '|', ')')-- setBinSizeIncrement, setBinSizeRatio, setNDecimalDigits, setVerbosity :: String -> CommandLineAction- setBinSizeIncrement arg = return {-to IO-monad-} . Data.CommandOptions.setBinSizeIncrement (readCommandArg arg)- setBinSizeRatio arg = return {-to IO-monad-} . Data.CommandOptions.setBinSizeRatio (readCommandArg arg)- setNDecimalDigits arg commandOptions = return {-to IO-monad-} commandOptions { Data.CommandOptions.getNDecimalDigits = readBoundedIntegral arg }- setVerbosity arg commandOptions = return {-to IO-monad-} commandOptions { Data.CommandOptions.getVerbosity = readCommandArg arg }-- setDeriveProbabilityMassFunction :: Maybe String -> CommandLineAction- setDeriveProbabilityMassFunction arg commandOptions = return {-to IO-monad-} commandOptions { Data.CommandOptions.getDeriveProbabilityMassFunction = Data.Maybe.maybe True readCommandArg arg }-- printVersion, printUsage :: IO CommandOptions- printVersion = System.IO.hPutStrLn System.IO.stderr (- Distribution.Text.display packageIdentifier ++ "\n\nCompiled by " ++ show compiler ++ ".\n\nCopyright (C) 2013-2015 " ++ author ++ ".\nThis program comes with ABSOLUTELY NO WARRANTY.\nThis is free software, and you are welcome to redistribute it under certain conditions.\n\nWritten by " ++ author ++ "."- ) >> System.Exit.exitWith System.Exit.ExitSuccess where- packageIdentifier :: Distribution.Package.PackageIdentifier- packageIdentifier = Distribution.Package.PackageIdentifier {- Distribution.Package.pkgName = Distribution.Package.PackageName progName, -- CAVEAT: coincidentally.- Distribution.Package.pkgVersion = Distribution.Version.Version (Data.Version.versionBranch Paths.version) []- }-- author, compiler :: String- author = "Dr. Alistair Ward"- compiler = System.Info.compilerName ++ "-" ++ Data.List.intercalate "." (map show $ Data.Version.versionBranch System.Info.compilerVersion)-- printUsage = System.IO.hPutStrLn System.IO.stderr ("Usage:\t" ++ G.usageInfo progName optDescrList ++ " [<File-path> ...]") >> System.Exit.exitWith System.Exit.ExitSuccess-- runQuickChecks :: CommandOptions -> IO CommandOptions- runQuickChecks _ = Test.QuickChecks.run >> System.Exit.exitWith System.Exit.ExitSuccess-- args <- System.Environment.getArgs-- case G.getOpt G.RequireOrder optDescrList args of- (commandLineActions, nonOptions, [{-errors-}]) -> do- commandOptions <- Data.List.foldl' (>>=) (return {-to IO-monad-} ToolShed.Defaultable.defaultValue) commandLineActions-- if not $ ToolShed.SelfValidate.isValid commandOptions- then fail $ ToolShed.SelfValidate.getFirstError commandOptions- else if null nonOptions- then fail "zero file-paths specified"- else let- standardInputProxy = "-"- in do- filePaths <- if standardInputProxy `elem` nonOptions- then let- getFilePaths :: IO [String]- getFilePaths = do- eof <- System.IO.isEOF-- if eof- then return {-to IO-monad-} []- else {-more to read-} (:) <$> getLine <*> getFilePaths {-recurse-}- in do- filePaths <- (filter (/= standardInputProxy) nonOptions ++) <$> getFilePaths-- if null filePaths- then fail "zero file-paths"- else return filePaths- else {-real fileNames-} return {-to IO-monad-} nonOptions-- Control.Monad.when (Data.CommandOptions.getVerbosity commandOptions == maxBound) $ System.IO.hPrint System.IO.stderr filePaths -- CAVEAT: potentially very long.-- (fileSizeDistribution, statistics) <- Control.Monad.Writer.runWriter . Profiler.calculateFileSizeDistribution commandOptions <$> mapM Data.File.findSize (Data.List.nub filePaths)-- Control.Monad.when (Data.CommandOptions.getVerbosity commandOptions > ToolShed.Defaultable.defaultValue) $ mapM_ (System.IO.hPutStrLn System.IO.stderr) statistics-- System.IO.putStrLn $ Profiler.formatFileSizeDistribution commandOptions fileSizeDistribution- (_, _, errors) -> System.IO.Error.ioError . System.IO.Error.userError $ concatMap init {-chop-} errors-