packages feed

fishfood 0.0.0.1 → 0.0.1.0

raw patch · 14 files changed

+711/−82 lines, 14 filesdep +QuickCheckdep +mtl

Dependencies added: QuickCheck, mtl

Files

changelog view
@@ -2,4 +2,9 @@  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'.
@@ -1,8 +1,8 @@ Author:-	Dr. Alistair Ward <squeeze at functionalley dot eu>.+	Dr. Alistair Ward <fishfood at functionalley dot eu>.  Copyright:-	Copyright (C) 2010-2011 Dr. Alistair Ward. All Rights Reserved.+	Copyright (C) 2013 Dr. Alistair Ward. All Rights Reserved.  Home-page: 	http://functionalley.eu
fishfood.cabal view
@@ -1,13 +1,13 @@ --Package-properties Name:			fishfood-Version:		0.0.0.1+Version:		0.0.1.0 Cabal-Version:		>= 1.6 Copyright:		(C) 2013 Dr. Alistair Ward License:		GPL License-file:		LICENSE Author:			Dr. Alistair Ward Stability:		Unstable interface, incomplete features.-Synopsis:		Finds the file-size frequency-distribution for the referenced files.+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@@ -16,7 +16,7 @@ Maintainer:		fishfood <at> functionalley <dot> eu Bug-reports:		fishfood <at> functionalley <dot> eu Data-files:-Extra-Source-Files:	changelog copyright makefile+Extra-Source-Files:	changelog copyright makefile man/man1/fishfood.1 fishfood.spec  flag llvm     Description:	Whether the 'llvm' compiler-backend has been installed and is required for code-generation.@@ -28,13 +28,21 @@     Main-Is:		Main.hs     Other-modules:         FishFood.Data.CommandOptions,-        FishFood.Data.File+        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,         containers,         factory >= 0.2.0.5,+        mtl,+        QuickCheck,         toolshed >= 0.13,         unix 
+ fishfood.spec view
@@ -0,0 +1,65 @@+# 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/>.++%define package		%name-%version+%define tarBall		%package.tar.gz+%define _bindir		%prefix/bin+%define _sharedir	%prefix/share+%define _docdir		%_sharedir/doc/%package+%define _mandir		%_sharedir/man++Summary:	Calculates file-size frequency-distribution.+Name:		fishfood+Version:	0.0.1.0+Release:	1+License:	GPLv3+#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++%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.++%build+cd '%package/' && runhaskell Setup 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/'+mkdir -p -- '%buildroot%_mandir' && mv 'man/man1' '%buildroot%_mandir/'++%clean+make --directory='%package' clobber+rm -rf -- '%_builddir/%package/' '%buildroot'++%files+%attr(0755, root, root)		%_bindir/%name+%attr(0644, root, root)	%doc	%_docdir/changelog+%attr(0644, root, root)	%doc	%_docdir/copyright+%attr(0644, root, root)	%doc	%_docdir/LICENSE+%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.
makefile view
@@ -13,7 +13,7 @@ # You should have received a copy of the GNU General Public License # along with this program.  If not, see <http://www.gnu.org/licenses/>. -.PHONY: all build check clean clobber configure copy haddock help hlint install prof sdist+.PHONY: all build check clean clobber configure copy haddock help hlint install prof rpm sdist  PACKAGE_NAME=fishfood @@ -49,6 +49,10 @@  check: sdist 	cabal upload --check --verbose=3 dist/*.tar.gz;++rpm: $(PACKAGE_NAME).spec+	rpmbuild -bb --nodeps -- $^+	mv -- `find ~/rpmbuild -type f -name '$(PACKAGE_NAME)*.rpm' -mtime 0 -print` ./  clean: 	runhaskell Setup $@
+ man/man1/fishfood.1 view
@@ -0,0 +1,166 @@+.TH fishfood 1+.SH NAME+\fBfishfood\fR - Calculates file-size frequency-distribution.+.SH SYNOPSIS+\fBfishfood\fR [\fIOPTIONS\fR] [\fIFile-path\fR ...]+.SH DESCRIPTION+.PP+Counts the number of the specified files which fall within each of a sequence of discrete size-intervals.+.SH OPTIONS+.TP+\fB-s\fR \fIInt\fR, \fB--binSizeIncrement=\fR\fIInt\fR+Defines the constant size-increase in the arithmetic sequence of bins into which the byte-sizes of files are categorised; defaulting to one standard-deviation.+.TP+\fB-r\fR \fIFloat\fR, \fB--binSizeRatio=\fR \fIFloat\fR+Defines the constant size-ratio in the geometric sequence of bins into which the byte-sizes of files are categorised; an alternative to "\fBbinSizeIncrement\fR".+.TP+\fB-p\fR[\fIBool\fR], \fB--deriveProbabilityMassFunction\fR[\fB=\fR\fIBool\fR]+Whether to derive the \fIProbability Mass Function\fR rather than the \fIFrequency-distribution\fR.+.br+The default value, in the absence of this option, is "\fBFalse\fR",+but in the absence of only the boolean argument, "\fBTrue\fR" will be inferred.+.TP+\fB-d\fR \fIInt\fR, \fB--nDecimalDigits=\fR\fIInt\fR+The precision to which fractional auxiliary data is displayed.+.TP+\fB--verbosity=\fR\fBSilent\fR|\fBNormal\fR|\fBVerbose\fR|\fBDeafening\fR+Produces additional output where appropriate; i.e. file-names, file-size statistics, & column-headers.+.SS "Generic Program-information"+.TP+\fB-v\fR, \fB--version\fR+Outputs version-information & then exits.+.TP+\fB-?\fR, \fB--help\fR+Displays help & 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, ...+.SH EXIT-STATUS+\fB0\fR on success, & >\fB0\fR if an error occurs.+.SH EXAMPLES+.SS Example 1+To find the frequency-distribution in the size of any Matroska video-files in a file-system:+.IP+\fBfishfood --verbosity=Verbose $(find / -name '*.mkv' 2>/dev/null)\fR	#CAVEAT: for efficiency, one may want to be more precise with the path supplied to "\fBfind\fR".+.nf+Files=86, mean=394459250.942, standard-deviation=304129537.729+  Bin-size Frequency+========== =========+         0        44+ 304129538        29+ 608259076         6+ 912388614         4+1216518152         2+1520647690         1+.fi+.PP +The left-hand column defines an arithmetic sequence of bins, whilst the right-hand column defines the number of files from those specified which fall into each.+The choice of the increment between each bin has defaulted to one standard-deviation.+.br+From this data one can conclude that there are 44 files whose size lies in the semi-closed interval [0, 1) standard-deviations,+decaying monotonically to only one file whose size lies in the semi-closed interval [5, 6) standard-deviations.+.SS Example 2+One can alternatively specify the arithmetic increment between bin-sizes,+& also derive the probability that a file-size lies in any specific bin.+.IP+.B fishfood --verbosity=Verbose --binSizeIncrement=100000000 --deriveProbabilityMassFunction $(find / -name '*.mkv' 2>/dev/null)+.nf+Files=86, mean=394459250.942, standard-deviation=304129537.729+  Bin-size Probability+========== ===========+ 100000000 0.209+ 200000000 0.302+ 300000000 0.233+ 400000000 0.035+ 500000000 0.070+ 600000000 0.023+ 800000000 0.035+ 900000000 0.035+1000000000 0.012+1100000000 0.012+1200000000 0.012+1400000000 0.012+1600000000 0.012+.fi+.PP+CAVEAT: the total probability may differ from "\fB1\fR", due to round-errors; see "\fBnDecimalDigits\fR".+.SS Example 3+One can alternatively define a geometric sequence of file-size bins,+& also read the file-names from standard-input, to bypass any limit applied by the shell to the length of the command-line.+.IP+.B find /etc -type f -readable 2>/dev/null | fishfood --verbosity=Verbose --binSizeRatio=10 -+.nf+Files=1735, mean=13846.622, standard-deviation=74846.621+  Bin-size Frequency+========== =========+         0         4	#Though "\fB0\fR" isn't a member of the requested geometric sequence, it's the integral value beneath all fractional values which are.+         1         2+        10       100+       100       563+      1000       794+     10000       188+    100000        83+   1000000         1+.fi+.PP+From this data one can conclude that there are 4 files whose size lies in the semi-closed interval [0, 1) bytes, 3 files in [1, 10), 100 files in [10, 100), ...+.IP+.B find $HOME -name '*.png' -o -name '*.gif' -o -name '*.jp*g' | fishfood --verbosity=Verbose -r 2 -p -+.nf+Files=878, mean=78365.943, standard-deviation=297831.014+  Bin-size Probability+========== ===========+        32 0.023+        64 0.017+       128 0.008+       256 0.015+       512 0.034+      1024 0.046+      2048 0.047+      4096 0.096+      8192 0.155+     16384 0.179+     32768 0.155+     65536 0.157+    131072 0.032+    262144 0.017+    524288 0.003+   1048576 0.010+   2097152 0.007+.fi+.PP+When specifying an arithmetic sequence of bin-sizes, the lack of resolution amongst smaller files makes the distribution appear like the decaying exponential of a \fIgeometric distribution\fR,+but by using a geometric sequence of bin-sizes, it can be seen more clearly to be a \fIlog-normal distribution\fR;+see "A Large-Scale Study of File-System Contents" by John R. Douceur and William J. Bolosky.+.SH AUTHOR+Written by Dr. Alistair Ward.+.SH BUGS+.SS "REPORTING BUGS"+Report bugs to \fBfishfood\fR \fIat\fR \fBfunctionalley\fR \fIdot\fR \fBeu\fR+.SH COPYRIGHT+Copyright \(co 2013 Dr. Alistair Ward+.PP+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.+.PP+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.+.PP+You should have received a copy of the GNU General Public License along with this program. If not, see \fB<http://www.gnu.org/licenses/>\fR.+.SH "SEE ALSO"+.IP \(bu+Home-page: \fBhttp://functionalley.eu\fR+.IP \(bu+.B http://en.wikipedia.org/wiki/Interval_(mathematics)+.IP \(bu+.B http://en.wikipedia.org/wiki/Standard_deviation+.IP \(bu+.B http://en.wikipedia.org/wiki/Frequency_distribution+.IP \(bu+.B http://en.wikipedia.org/wiki/Geometric_distribution+.IP \(bu+.B http://en.wikipedia.org/wiki/Log-normal_distribution+.IP \(bu+.B http://en.wikipedia.org/wiki/Probability_mass_function+.IP \(bu+Source-documentation is generated by \fBHaddock\fR, & is available in the distribution.+.IP \(bu+.B http://www.haskell.org/haddock/
src/FishFood/Data/CommandOptions.hs view
@@ -28,14 +28,22 @@  module FishFood.Data.CommandOptions( -- * Types+-- ** Type-synonyms+	BinSizeDelta, -- ** Data-types 	CommandOptions( --		MkCommandOptions,-		getBinSize,-		getIncludeEmpty,+		getBinSizeDelta,+		getDeriveProbabilityMassFunction, 		getNDecimalDigits, 		getVerbosity-	)+	),+-- * Functions+-- ** Constructors+	mkCommandOptions,+-- ** Mutators+	setBinSizeIncrement,+	setBinSizeRatio ) where  import qualified	Data.Maybe@@ -44,33 +52,37 @@ 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	= MkCommandOptions {-	getBinSize		:: Maybe Data.File.FileSize,		-- ^ The intervals into which file-sizes are categorised.-	getIncludeEmpty		:: Bool,				-- ^ Whether to display empty bins.-	getNDecimalDigits	:: Int,					-- ^ The precision to which fractional auxiliary data is displayed.-	getVerbosity		:: Distribution.Verbosity.Verbosity	-- ^ The threshold for ancillary information-output.+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	where+instance ToolShed.Defaultable.Defaultable (CommandOptions ratio)	where 	defaultValue	= MkCommandOptions {-		getBinSize		= Nothing,	--Interpreted as one standard-deviation.-		getIncludeEmpty		= False,-		getNDecimalDigits	= 3,-		getVerbosity		= Distribution.Verbosity.normal+		getBinSizeDelta				= Left Nothing,	--Interpreted as one standard-deviation.+		getDeriveProbabilityMassFunction	= False,+		getNDecimalDigits			= 3,+		getVerbosity				= Distribution.Verbosity.normal 	} -instance ToolShed.SelfValidate.SelfValidator CommandOptions	where+instance (Num ratio, Ord ratio, Show ratio) => ToolShed.SelfValidate.SelfValidator (CommandOptions ratio)	where 	getErrors commandOptions@MkCommandOptions {-		getBinSize		= binSize,-		getNDecimalDigits	= nDecimalDigits+		getBinSizeDelta				= binSizeDelta,+		getDeriveProbabilityMassFunction	= deriveProbabilityMassFunction,+		getNDecimalDigits			= nDecimalDigits 	} = map snd $ filter fst [ 		(-			Data.Maybe.maybe False (<= 0) binSize,-			"the bin-size must exceed zero; " ++ show commandOptions ++ "."+			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 ++ "." 		), (-			nDecimalDigits < 1,-			"the number of decimal digits must exceed zero; " ++ show commandOptions ++ "."+			deriveProbabilityMassFunction && nDecimalDigits < 1,+			"the number of decimal digits must exceed zero to adequately represent probabilities; " ++ show commandOptions ++ "." 		), 		let 			maxNDecimalDigits	= floor $ fromIntegral (@@ -84,3 +96,18 @@ 		) 	 ] +-- | 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/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/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/FishFood/Test/Data/CommandOptions.hs view
@@ -0,0 +1,61 @@+{-# 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 'Data.CommandOptions.CommandOptions'.+-}++module FishFood.Test.Data.CommandOptions(+-- * Types+-- ** Type-synonyms+	CommandOptions+) where++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 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/FishFood/Test/Profiler.hs view
@@ -0,0 +1,58 @@+{-+	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 view
@@ -0,0 +1,38 @@+{-+	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 view
@@ -22,23 +22,26 @@ 	* Contains the entry-point of the application.  	* Processes the command-line arguments.++	* Delegates the task to "Profiler". -}  module Main(main) where  import			Control.Applicative((<$>),(<*>))-import			Control.Arrow((&&&)) import qualified	Control.Monad+import qualified	Control.Monad.Writer import qualified	Data.List-import qualified	Data.Map import qualified	Data.Maybe import qualified	Data.Version import qualified	Distribution.Package import qualified	Distribution.Text-import qualified	Distribution.Verbosity 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@@ -49,8 +52,11 @@ 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	= Data.CommandOptions.CommandOptions -> IO Data.CommandOptions.CommandOptions	--Supplied as the type-argument to 'G.OptDescr'.+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@@ -83,31 +89,34 @@ 	progName	<- System.Environment.getProgName  	let-		defaultCommandOptions :: Data.CommandOptions.CommandOptions+		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` show [minBound :: Distribution.Verbosity.Verbosity .. maxBound]-			)										("Define the log-level; default '" ++ show (Data.CommandOptions.getVerbosity defaultCommandOptions) ++ "'."+--				 String	[String]				(G.ArgDescr CommandLineAction)				String+			G.Option "?"	["help"]				(G.NoArg $ const printUsage)				"Display this help, & then exit.",+			G.Option ""	["verbosity"]				(+				setVerbosity `G.ReqArg` show 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 "b"	["binSize"]		(setBinSize `G.ReqArg` "<Int>")		"The range in bytes, accepted by any one of the bins into which file-sizes are categorised; defaulting to one standard-deviation.",-			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 "z"	["includeEmpty"]	(setIncludeEmpty `G.OptArg` "<Bool>")	("Whether to display empty bins; default '" ++ show (Data.CommandOptions.getIncludeEmpty 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)				"Run Quick-checks using arbitrary data, & then exit." 		 ] where-			setBinSize, setNDecimalDigits, setVerbosity :: String -> CommandLineAction-			setBinSize arg commandOptions		= return {-to IO-monad-} commandOptions { Data.CommandOptions.getBinSize = Just $ readCommandArg arg }+			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 } -			setIncludeEmpty :: Maybe String -> CommandLineAction-			setIncludeEmpty arg commandOptions	= return {-to IO-monad-} commandOptions { Data.CommandOptions.getIncludeEmpty = Data.Maybe.maybe True 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 Data.CommandOptions.CommandOptions+			printVersion, printUsage :: IO CommandOptions 			printVersion	= Text.Printf.printf "%s\n\n%s %s.\n%s.\n%s.\n%s %s.\n" packageName "Copyright (C) 2013" author "This program comes with ABSOLUTELY NO WARRANTY" "This is free software, & you are welcome to redistribute it under certain conditions" "Written by" author >> System.Exit.exitWith System.Exit.ExitSuccess	where 				packageIdentifier :: Distribution.Package.PackageIdentifier 				packageIdentifier	= Distribution.Package.PackageIdentifier {@@ -121,6 +130,9 @@  			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@@ -151,43 +163,12 @@ 									else return filePaths 							else {-real fileNames-} return {-to IO-monad-} nonOptions -						let (nDecimalDigits, verbosity) = Data.CommandOptions.getNDecimalDigits &&& Data.CommandOptions.getVerbosity $ commandOptions	--Deconstruct.--						Control.Monad.when (verbosity == maxBound) $ System.IO.hPrint System.IO.stderr filePaths	--CAVEAT: potentially very long.--						fileSizes	<- mapM Data.File.findSize $ Data.List.nub filePaths	--De-duplicate the file-paths, & find the corresponding file-sizes.--						let-							mean, standardDeviation :: Double-							(nFiles, mean, standardDeviation)	= Data.File.getFileSizeStatistics fileSizes--							columnHeaders :: [String]-							columnHeaders	= ["Bin", "Frequency"]--							columnWidths :: [Int]-							columnWidths	= map length columnHeaders--							formatLine :: Data.File.FileSize -> Int -> IO ()-							formatLine bin	=  Text.Printf.printf "%*d\t%*d\n" (head columnWidths) bin (last columnWidths)--						Control.Monad.when (verbosity >= Distribution.Verbosity.verbose) $ do-							Control.Monad.void $ Text.Printf.hPrintf System.IO.stderr "Files=%d, mean=%.*f, standard-deviation=%.*f\n" nFiles nDecimalDigits mean nDecimalDigits standardDeviation	--Print statistics.--							let tabulate	= Data.List.intercalate "\t"--							Text.Printf.hPrintf System.IO.stderr "%s\n%s\n" (tabulate columnHeaders) (tabulate $ map (`replicate` '=') columnWidths)	--Print column-headers.+						Control.Monad.when (Data.CommandOptions.getVerbosity commandOptions == maxBound) $ System.IO.hPrint System.IO.stderr filePaths	--CAVEAT: potentially very long. -						if standardDeviation == 0-							then formatLine 0 nFiles-							else let-								bins	= map (`div` Data.Maybe.fromMaybe (round standardDeviation) (Data.CommandOptions.getBinSize commandOptions)) fileSizes+						(fileSizeDistribution, statistics)	<- Control.Monad.Writer.runWriter . Profiler.calculateFileSizeDistribution commandOptions <$> mapM Data.File.findSize (Data.List.nub filePaths) -								frequencyDistribution-									| Data.CommandOptions.getIncludeEmpty commandOptions	= Data.Map.fromList $ [0 .. maximum bins] `zip` repeat 0-									| otherwise						= Data.Map.empty-							in mapM_ (uncurry formatLine) . Data.Map.toList $ foldr (-								uncurry (Data.Map.insertWith (+))-							) frequencyDistribution $ bins `zip` repeat 1+						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