diff --git a/src/UniqueFileGenerator.Console/Arguments/ArgumentTypes.fs b/src/UniqueFileGenerator.Console/Arguments/ArgumentTypes.fs
index a71f7d9..28877e7 100644
--- a/src/UniqueFileGenerator.Console/Arguments/ArgumentTypes.fs
+++ b/src/UniqueFileGenerator.Console/Arguments/ArgumentTypes.fs
@@ -15,7 +15,7 @@ module ArgTypes =
let private tryParseIntInRange (floor, ceiling) text =
text
|> parseInRange (floor, ceiling)
- |> Result.mapError (fun _ -> InvalidNumber (text, floor, ceiling))
+ |> Result.mapError (fun _ -> ParseNumberFailure (text, floor, ceiling))
type FileCount = private FileCount of int with
static member val AllowedRange = 1, Int32.MaxValue
@@ -25,9 +25,7 @@ module ArgTypes =
|> stripSeparators
|> parseInRange FileCount.AllowedRange
|> Result.mapError (fun _ ->
- InvalidNumber (text,
- fst FileCount.AllowedRange,
- snd FileCount.AllowedRange))
+ ParseNumberFailure (text, fst FileCount.AllowedRange, snd FileCount.AllowedRange))
|> Result.map FileCount
member this.Value = let (FileCount count) = this in count
@@ -125,10 +123,11 @@ module ArgTypes =
private
{ fileCount: int
options: Options }
+
member x.FileCount = x.fileCount
member x.Options = x.options
- static member Create(count: FileCount, options: Options) =
+ static member Create (count: FileCount, options: Options) =
{ fileCount = count.Value
options =
{ Prefix = options.Prefix
diff --git a/src/UniqueFileGenerator.Console/Errors.fs b/src/UniqueFileGenerator.Console/Errors.fs
index bff2496..2e892ef 100644
--- a/src/UniqueFileGenerator.Console/Errors.fs
+++ b/src/UniqueFileGenerator.Console/Errors.fs
@@ -9,11 +9,12 @@ module Errors =
| MalformedFlags
| UnsupportedFlags
| DuplicateFlags
- | InvalidNumber of Arg: string * Floor: int * Ceiling: int
+ | ParseNumberFailure of Arg: string * Floor: int * Ceiling: int
| DirectoryMissing of string
| DriveSpaceConfirmationFailure
| DriveSpaceInsufficient of Needed: string * Actual: string
- | UnknownError of string
+ | IoError of string
+ | CancelledByUser
let getMessage error =
match error with
@@ -22,11 +23,12 @@ module Errors =
| MalformedFlags -> "Malformed flag(s) found."
| UnsupportedFlags -> "Unsupported flag(s) found."
| DuplicateFlags -> "Duplicate option flag(s) found. Each can only be used once."
- | InvalidNumber (x, f, c) ->
+ | ParseNumberFailure (x, f, c) ->
$"Could not parse \"%s{x}\" to an integer between %s{formatInt f} and %s{formatInt c}, inclusive."
| DirectoryMissing e -> $"Directory \"%s{e}\" was not found."
| DriveSpaceConfirmationFailure -> "Could not confirm available drive space."
| DriveSpaceInsufficient (needed, actual) ->
$"Insufficient drive space. Though %s{needed} is necessary, only %s{actual} is available."
- | UnknownError e -> e
+ | IoError e -> $"IO error: %s{e}"
+ | CancelledByUser -> "Cancelled."
diff --git a/src/UniqueFileGenerator.Console/Io.fs b/src/UniqueFileGenerator.Console/Io.fs
index d179534..9ad45f3 100644
--- a/src/UniqueFileGenerator.Console/Io.fs
+++ b/src/UniqueFileGenerator.Console/Io.fs
@@ -10,7 +10,12 @@ open System.IO
open System.Threading
module Io =
- let private formatBytes (bytes: int64) : string =
+ let verifyDirectory dir =
+ match Directory.Exists dir with
+ | true -> Ok ()
+ | false -> Error (DirectoryMissing dir)
+
+ let private formatBytes (bytes: int64) =
let kilobyte = 1024L
let megabyte = kilobyte * 1024L
let gigabyte = megabyte * 1024L
@@ -23,11 +28,11 @@ module Io =
| _ when bytes >= kilobyte -> sprintf "%s KB" ((float bytes / float kilobyte) |> formatFloat)
| _ -> sprintf "%s bytes" (bytes |> formatInt64)
-
let verifyDriveSpace (args: Args) =
- let bytesToKeepAvailable = 536_870_912L // 0.5 GB
+ let driveSpaceToKeepAvailable = 536_870_912L // 0.5 GB
+ let warningRatio = 0.75
- let necessaryDriveSpace =
+ let necessarySpace =
let singleFileSize =
args.Options.Size
|> Option.defaultValue
@@ -38,6 +43,25 @@ module Io =
singleFileSize * int64 args.FileCount // Rough estimation
+ let confirmContinueDespiteLargeSize usableFreeSpace : bool =
+ let ratio = float necessarySpace / float usableFreeSpace
+ let isLargeRatio = ratio > warningRatio
+
+ let confirm () =
+ Console.Write(
+ sprintf "This operation requires %s, which is %.2f%% of remaining drive space. Continue? (Y/n) "
+ (formatBytes necessarySpace)
+ (ratio * 100.0))
+
+ let reply = Console.ReadLine().Trim()
+
+ [| "y"; "yes" |]
+ |> Array.exists (fun yesAnswer -> reply.Equals(yesAnswer, StringComparison.InvariantCultureIgnoreCase))
+
+ if isLargeRatio
+ then confirm ()
+ else true
+
try
let appDir = AppContext.BaseDirectory
let root = Path.GetPathRoot appDir
@@ -46,19 +70,15 @@ module Io =
| null -> Error DriveSpaceConfirmationFailure
| path ->
let driveInfo = DriveInfo path
- let usableFreeSpace = driveInfo.AvailableFreeSpace - bytesToKeepAvailable
+ let usableFreeSpace = driveInfo.AvailableFreeSpace - driveSpaceToKeepAvailable
- if necessaryDriveSpace < usableFreeSpace
- then Ok <| formatBytes necessaryDriveSpace
- else Error <| DriveSpaceInsufficient (formatBytes necessaryDriveSpace,
- formatBytes usableFreeSpace)
+ if necessarySpace > usableFreeSpace
+ then Error (DriveSpaceInsufficient (formatBytes necessarySpace, formatBytes usableFreeSpace))
+ elif confirmContinueDespiteLargeSize usableFreeSpace
+ then Ok (formatBytes necessarySpace)
+ else Error CancelledByUser
with
- | e -> Error <| UnknownError $"%s{e.Message}"
-
- let verifyDirectory dir =
- match Directory.Exists dir with
- | true -> Ok ()
- | false -> Error (DirectoryMissing dir)
+ | e -> Error (IoError $"%s{e.Message}")
let private createFile directory fileName (contents: string) =
try
diff --git a/src/UniqueFileGenerator.Console/Program.fs b/src/UniqueFileGenerator.Console/Program.fs
index f0cdfe9..716f8a5 100644
--- a/src/UniqueFileGenerator.Console/Program.fs
+++ b/src/UniqueFileGenerator.Console/Program.fs
@@ -18,8 +18,8 @@ module Main =
do! verifyDirectory args.Options.OutputDirectory
let! spaceNeeded = verifyDriveSpace args
- printLine $"This operation will use approximately %s{spaceNeeded} of drive space."
- return generateFiles args
+ generateFiles args
+ return spaceNeeded
}
if Help.wasRequested rawArgs then
@@ -27,8 +27,8 @@ module Main =
0
else
match run rawArgs with
- | Ok _ ->
- printLine $"Done after %s{watch.ElapsedFriendly}"
+ | Ok spaceUsed ->
+ printLine $"Done after %s{watch.ElapsedFriendly}. Used approximately %s{spaceUsed} of drive space."
0
| Error e ->
printError <| getMessage e
diff --git a/src/UniqueFileGenerator.Console/UniqueFileGenerator.Console.fsproj b/src/UniqueFileGenerator.Console/UniqueFileGenerator.Console.fsproj
index 65a3c2b..f1b5ea6 100644
--- a/src/UniqueFileGenerator.Console/UniqueFileGenerator.Console.fsproj
+++ b/src/UniqueFileGenerator.Console/UniqueFileGenerator.Console.fsproj
@@ -4,6 +4,7 @@
Exe
net9.0
3579
+ true
diff --git a/src/UniqueFileGenerator.Tests/ArgParserTests.fs b/src/UniqueFileGenerator.Tests/ArgParserTests.fs
index 24da33f..3f62a7a 100644
--- a/src/UniqueFileGenerator.Tests/ArgParserTests.fs
+++ b/src/UniqueFileGenerator.Tests/ArgParserTests.fs
@@ -64,21 +64,21 @@ let ``Appropriate error when invalid arg count (second pair incomplete)`` () =
[]
let ``Appropriate error when invalid file count`` () =
let args = [| "notNumeric" |]
- let expected = Error <| InvalidNumber(args[0], 1, Int32.MaxValue)
+ let expected = Error <| ParseNumberFailure(args[0], 1, Int32.MaxValue)
let actual = validate args
Assert.Equal(expected, actual)
[]
let ``Appropriate error when negative file count`` () =
let args = [| "-1" |]
- let expected = Error <| InvalidNumber(args[0], 1, Int32.MaxValue)
+ let expected = Error <| ParseNumberFailure(args[0], 1, Int32.MaxValue)
let actual = validate args
Assert.Equal(expected, actual)
[]
let ``Appropriate error when zero file count`` () =
let args = [| "0" |]
- let expected = Error <| InvalidNumber(args[0], 1, Int32.MaxValue)
+ let expected = Error <| ParseNumberFailure(args[0], 1, Int32.MaxValue)
let actual = validate args
Assert.Equal(expected, actual)