diff --git a/src/CCVTAC.Main/CCVTAC.Main.fsproj b/src/CCVTAC.Main/CCVTAC.Main.fsproj index 96883f7..0ce451a 100644 --- a/src/CCVTAC.Main/CCVTAC.Main.fsproj +++ b/src/CCVTAC.Main/CCVTAC.Main.fsproj @@ -40,7 +40,7 @@ - + diff --git a/src/CCVTAC.Main/Commands.fs b/src/CCVTAC.Main/Commands.fs index a91d825..a96b485 100644 --- a/src/CCVTAC.Main/Commands.fs +++ b/src/CCVTAC.Main/Commands.fs @@ -1,6 +1,6 @@ namespace CCVTAC.Main -open CCFSharpUtils +open CCFSharpUtils.Text open System module Commands = diff --git a/src/CCVTAC.Main/Downloading/Downloading.fs b/src/CCVTAC.Main/Downloading/Downloading.fs index 5f8f89c..b7b0ad9 100644 --- a/src/CCVTAC.Main/Downloading/Downloading.fs +++ b/src/CCVTAC.Main/Downloading/Downloading.fs @@ -1,6 +1,6 @@ namespace CCVTAC.Main.Downloading -open CCFSharpUtils +open CCFSharpUtils.Text module Downloading = diff --git a/src/CCVTAC.Main/Downloading/Updater.fs b/src/CCVTAC.Main/Downloading/Updater.fs index 9aa8133..3d937e6 100644 --- a/src/CCVTAC.Main/Downloading/Updater.fs +++ b/src/CCVTAC.Main/Downloading/Updater.fs @@ -3,7 +3,7 @@ namespace CCVTAC.Main.Downloading open CCVTAC.Main open CCVTAC.Main.ExternalTools open CCVTAC.Main.Settings.Settings -open CCFSharpUtils +open CCFSharpUtils.Text module Updater = diff --git a/src/CCVTAC.Main/ExternalTools/Runner.fs b/src/CCVTAC.Main/ExternalTools/Runner.fs index 0659090..9ce28ec 100644 --- a/src/CCVTAC.Main/ExternalTools/Runner.fs +++ b/src/CCVTAC.Main/ExternalTools/Runner.fs @@ -1,7 +1,8 @@ namespace CCVTAC.Main.ExternalTools open CCVTAC.Main -open CCFSharpUtils +open CCFSharpUtils.Collections +open CCFSharpUtils.Text open Startwatch.Library open System open System.Diagnostics diff --git a/src/CCVTAC.Main/History.fs b/src/CCVTAC.Main/History.fs index 8f2b021..c803126 100644 --- a/src/CCVTAC.Main/History.fs +++ b/src/CCVTAC.Main/History.fs @@ -1,7 +1,8 @@ namespace CCVTAC.Main open CCVTAC.Main.IoUtilities.Files -open CCFSharpUtils +open CCFSharpUtils.Collections +open CCFSharpUtils.Text open System open System.IO open System.Text.Json @@ -11,7 +12,7 @@ type History(filePath: string, displayCount: int) = let separator = ';' - member private _.FilePath = filePath + member private _.FileInfo = FileInfo filePath member private _.DisplayCount = displayCount /// Write a URL and its related data to the history file. @@ -20,16 +21,16 @@ type History(filePath: string, displayCount: int) = let serializedTime = JsonSerializer.Serialize(entryTime).Replace("\"", "") let text = serializedTime + string separator + url + String.newLine - match appendToFile this.FilePath text with + match appendToFile this.FileInfo text with | Ok _ -> printer.Debug $"Added \"%s{url}\" to the history log." - | Error err -> printer.Error $"Failed to write \"%s{url}\" to the history log at \"{this.FilePath}\": {err}" + | Error err -> printer.Error $"Failed to write \"%s{url}\" to the history log at \"{this.FileInfo}\": {err}" with exn -> printer.Error $"Could not append URL(s) to history log: {exn.Message}" member this.ShowRecent(printer: Printer) : unit = try let lines = - File.ReadAllLines this.FilePath + File.ReadAllLines this.FileInfo.FullName |> Seq.takeLast this.DisplayCount |> Seq.toList diff --git a/src/CCVTAC.Main/InputHelper.fs b/src/CCVTAC.Main/InputHelper.fs index 84b7738..80c346b 100644 --- a/src/CCVTAC.Main/InputHelper.fs +++ b/src/CCVTAC.Main/InputHelper.fs @@ -52,13 +52,11 @@ module InputHelper = inputs |> List.map (fun input -> { Text = input - Category = if input[0] = Commands.prefix - then InputCategory.Command - else InputCategory.Url }) + Category = if input[0] = Commands.prefix then Command else Url }) let countCategories (inputs: CategorizedInput list) : CategoryCounts = inputs |> List.groupBy _.Category - |> List.map (fun (k, grp) -> k, grp |> Seq.length) + |> List.map (fun (category, items) -> category, Seq.length items) |> Map.ofSeq |> CategoryCounts diff --git a/src/CCVTAC.Main/IoUtilities/Directories.fs b/src/CCVTAC.Main/IoUtilities/Directories.fs index 1b75ccf..acd98fa 100644 --- a/src/CCVTAC.Main/IoUtilities/Directories.fs +++ b/src/CCVTAC.Main/IoUtilities/Directories.fs @@ -2,6 +2,8 @@ namespace CCVTAC.Main.IoUtilities open CCVTAC.Main open CCFSharpUtils +open CCFSharpUtils.Collections +open CCFSharpUtils.Text open System.IO open System.Text @@ -96,4 +98,3 @@ module Directories = |> Ok with exn -> Error $"Error accessing or creating directory \"%s{dirName}\": %s{exn.Message}" - diff --git a/src/CCVTAC.Main/IoUtilities/Files.fs b/src/CCVTAC.Main/IoUtilities/Files.fs index 9dbefdd..442dea5 100644 --- a/src/CCVTAC.Main/IoUtilities/Files.fs +++ b/src/CCVTAC.Main/IoUtilities/Files.fs @@ -1,6 +1,7 @@ namespace CCVTAC.Main.IoUtilities open CCFSharpUtils +open CCFSharpUtils.Text open System.IO module Files = @@ -18,5 +19,5 @@ module Files = let readAllText (filePath: string) : Result = ofTry (fun _ -> File.ReadAllText filePath) - let appendToFile (filePath: string) (text: string) : Result = - ofTry (fun _ -> File.AppendAllText(filePath, text)) + let appendToFile (file: FileInfo) (text: string) : Result = + ofTry (fun _ -> File.AppendAllText(file.FullName, text)) diff --git a/src/CCVTAC.Main/Orchestrator.fs b/src/CCVTAC.Main/Orchestrator.fs index 4fff70b..6192d11 100644 --- a/src/CCVTAC.Main/Orchestrator.fs +++ b/src/CCVTAC.Main/Orchestrator.fs @@ -7,7 +7,8 @@ open CCVTAC.Main.PostProcessing open CCVTAC.Main.Settings open CCVTAC.Main.Settings.Settings open CCVTAC.Main.Settings.Settings.LiveUpdating -open CCFSharpUtils +open CCFSharpUtils.Collections +open CCFSharpUtils.Text open Startwatch.Library open System diff --git a/src/CCVTAC.Main/PostProcessing/Deleter.fs b/src/CCVTAC.Main/PostProcessing/Deleter.fs index 36ca2a0..c0a64c8 100644 --- a/src/CCVTAC.Main/PostProcessing/Deleter.fs +++ b/src/CCVTAC.Main/PostProcessing/Deleter.fs @@ -1,7 +1,7 @@ namespace CCVTAC.Main.PostProcessing open CCVTAC.Main -open CCFSharpUtils +open CCFSharpUtils.Text open System.IO module Deleter = diff --git a/src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs b/src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs index 3916349..37174e8 100644 --- a/src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs +++ b/src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs @@ -1,7 +1,6 @@ namespace CCVTAC.Main.PostProcessing -open CCVTAC.Main -open CCFSharpUtils +open CCFSharpUtils.Text open System open System.Text diff --git a/src/CCVTAC.Main/PostProcessing/Mover.fs b/src/CCVTAC.Main/PostProcessing/Mover.fs index b783b7b..f3674c1 100644 --- a/src/CCVTAC.Main/PostProcessing/Mover.fs +++ b/src/CCVTAC.Main/PostProcessing/Mover.fs @@ -7,6 +7,8 @@ open CCVTAC.Main.PostProcessing open CCVTAC.Main.PostProcessing.Tagging open CCVTAC.Main.Settings.Settings open CCFSharpUtils +open CCFSharpUtils.Collections +open CCFSharpUtils.Text open TaggingSet open System open System.IO diff --git a/src/CCVTAC.Main/PostProcessing/PostProcessing.fs b/src/CCVTAC.Main/PostProcessing/PostProcessing.fs index 62550a7..103e0e9 100644 --- a/src/CCVTAC.Main/PostProcessing/PostProcessing.fs +++ b/src/CCVTAC.Main/PostProcessing/PostProcessing.fs @@ -5,7 +5,8 @@ open CCVTAC.Main.IoUtilities open CCVTAC.Main.PostProcessing.Tagging open CCVTAC.Main.PostProcessing.Tagging.TaggingSet open CCVTAC.Main.Settings.Settings -open CCFSharpUtils +open CCFSharpUtils.Collections +open CCFSharpUtils.Text open Startwatch.Library open System.IO open System.Linq diff --git a/src/CCVTAC.Main/PostProcessing/Renamer.fs b/src/CCVTAC.Main/PostProcessing/Renamer.fs index ecb1c4f..96c6eb7 100644 --- a/src/CCVTAC.Main/PostProcessing/Renamer.fs +++ b/src/CCVTAC.Main/PostProcessing/Renamer.fs @@ -1,9 +1,9 @@ namespace CCVTAC.Main.PostProcessing open CCVTAC.Main -open CCVTAC.Main.IoUtilities open CCVTAC.Main.Settings.Settings -open CCFSharpUtils +open CCFSharpUtils.Collections +open CCFSharpUtils.Text open System open System.IO open System.Text @@ -11,6 +11,7 @@ open System.Text.RegularExpressions open Startwatch.Library module Renamer = + open CCVTAC.Main.IoUtilities let private toNormalizationForm (form: string) = match form.Trim().ToUpperInvariant() with diff --git a/src/CCVTAC.Main/PostProcessing/Tagging/Tagger.fs b/src/CCVTAC.Main/PostProcessing/Tagging/Tagger.fs index 4f442ec..5a0bcfc 100644 --- a/src/CCVTAC.Main/PostProcessing/Tagging/Tagger.fs +++ b/src/CCVTAC.Main/PostProcessing/Tagging/Tagger.fs @@ -5,9 +5,9 @@ open CCVTAC.Main.Settings.Settings open CCVTAC.Main.PostProcessing open CCVTAC.Main.PostProcessing.Tagging open CCVTAC.Main.Downloading.Downloading -open CCFSharpUtils +open CCFSharpUtils.Collections +open CCFSharpUtils.Text open Startwatch.Library -open TaggingSet open MetadataUtilities open System open System.IO diff --git a/src/CCVTAC.Main/PostProcessing/Tagging/TaggingSet.fs b/src/CCVTAC.Main/PostProcessing/Tagging/TaggingSet.fs index 474e240..79ac234 100644 --- a/src/CCVTAC.Main/PostProcessing/Tagging/TaggingSet.fs +++ b/src/CCVTAC.Main/PostProcessing/Tagging/TaggingSet.fs @@ -1,8 +1,9 @@ namespace CCVTAC.Main.PostProcessing.Tagging open CCVTAC.Main.IoUtilities -open CCFSharpUtils +open CCFSharpUtils.Collections open CCFSharpUtils.Operators +open CCFSharpUtils.Text open FsToolkit.ErrorHandling open System.IO open System.Text.RegularExpressions @@ -32,17 +33,6 @@ module TaggingSet = ImageFile = i } let private createValidated (videoId, fileNames) : Result = - let ensureNotEmpty xs errorMsg : Validation<'a list, string> = - if List.isNotEmpty xs - then Ok xs - else Error [errorMsg] - - let ensureExactlyOne xs emptyErrorMsg multipleErrorMsg : Validation<'a, string> = - match xs with - | [] -> Error [emptyErrorMsg] - | [x] -> Ok x - | _ -> Error [multipleErrorMsg] - let hasSupportedAudioExt (fileName: string) = match Path.GetExtension fileName with | Null -> false @@ -55,39 +45,51 @@ module TaggingSet = Files.imageFileExts |> List.collect (fun ext -> fileNames |> Files.filterByExt ext) - Validation.map3 - (fun a j i -> create videoId a j i) - (ensureNotEmpty audioFiles - $"No supported audio files found for video ID {videoId}.") - (ensureExactlyOne jsonFiles - $"No JSON file found for video ID {videoId}." - $"Multiple JSON files found for video ID {videoId}.") - (ensureExactlyOne imageFiles - $"No image file found for video ID {videoId}." - $"Multiple image files found for video ID {videoId}.") + // Validation.map3 + // (fun a j i -> create videoId a j i) + // (List.ensureNotEmptyV audioFiles + // $"No supported audio files found for video ID {videoId}.") + // (List.ensureOneV jsonFiles + // $"No JSON file found for video ID {videoId}." + // $"Multiple JSON files found for video ID {videoId}.") + // (List.ensureOneV imageFiles + // $"No image file found for video ID {videoId}." + // $"Multiple image files found for video ID {videoId}.") - /// Creates a collection of TaggingSets from a collection of file paths related to several video IDs. - /// Any extra, unnecessary files will be ignored. - /// Any validation errors will be accumulated and return in an Error. + validation { + let! a = List.ensureNotEmptyV audioFiles + $"No supported audio files found for video ID {videoId}." + and! j = List.ensureOneV jsonFiles + $"No JSON file found for video ID {videoId}." + $"Multiple JSON files found for video ID {videoId}." + and! i = List.ensureOneV imageFiles + $"No image file found for video ID {videoId}." + $"Multiple image files found for video ID {videoId}." + return create videoId a j i + } + + /// Creates a collection of TaggingSets from a collection of file paths related to video IDs. + /// Irrelevant files are ignored. Validation errors are accumulated and returned in an Error. let createSets filePaths : Result = if Seq.isEmpty filePaths then Error ["No file paths to create a tagging set were provided."] else - let isRelevantFile fileName : Match option = + let relevantFileInfo fileName = // Regex group 0 is the full filename, and group 1 contains the video ID. let fileNamesHavingVideoIdsRgx = Regex(@".+\[([\w_\-]{11})\](?:.*)?\.(\w+)", RegexOptions.Compiled) - fileName |> Rgx.trySuccessMatch fileNamesHavingVideoIdsRgx - - let fileName (m: Match) = m.Groups[0].Value - let videoId (m: Match) = m.Groups[1].Value + fileName + |> Rgx.trySuccessMatch fileNamesHavingVideoIdsRgx + |> Option.map (fun m -> + {| FileName = m.Groups[0].Value + VideoId = m.Groups[1].Value |} ) filePaths |> List.ofSeq - |> List.choose isRelevantFile - |> List.groupBy videoId - |> List.mapSnd fileName + |> List.choose relevantFileInfo + |> List.groupBy _.VideoId + |> List.mapSnd _.FileName |> List.map createValidated |> List.sequenceResultA |!! List.collect id diff --git a/src/CCVTAC.Main/Printer.fs b/src/CCVTAC.Main/Printer.fs index e4a9e88..bbe820d 100644 --- a/src/CCVTAC.Main/Printer.fs +++ b/src/CCVTAC.Main/Printer.fs @@ -1,6 +1,7 @@ namespace CCVTAC.Main open CCFSharpUtils +open CCFSharpUtils.Text open System open System.Collections.Generic open System.Linq diff --git a/src/CCVTAC.Main/Program.fs b/src/CCVTAC.Main/Program.fs index 4a68add..6f78df7 100644 --- a/src/CCVTAC.Main/Program.fs +++ b/src/CCVTAC.Main/Program.fs @@ -5,7 +5,8 @@ open CCVTAC.Main.IoUtilities open CCVTAC.Main.Settings open CCVTAC.Main.Settings.Settings open Settings.IO -open CCFSharpUtils +open CCFSharpUtils.Collections +open CCFSharpUtils.Text open Spectre.Console open System open System.IO diff --git a/src/CCVTAC.Main/ResultTracker.fs b/src/CCVTAC.Main/ResultTracker.fs index 1aab17f..2787c2a 100644 --- a/src/CCVTAC.Main/ResultTracker.fs +++ b/src/CCVTAC.Main/ResultTracker.fs @@ -1,6 +1,7 @@ namespace CCVTAC.Main open CCFSharpUtils +open CCFSharpUtils.Text open System open System.Collections.Generic diff --git a/src/CCVTAC.Main/Settings/Settings.fs b/src/CCVTAC.Main/Settings/Settings.fs index 40ae149..1388721 100644 --- a/src/CCVTAC.Main/Settings/Settings.fs +++ b/src/CCVTAC.Main/Settings/Settings.fs @@ -1,8 +1,9 @@ namespace CCVTAC.Main.Settings open CCVTAC.Main -open CCFSharpUtils +open CCFSharpUtils.Text open Spectre.Console +open FSharpPlus open System open System.Text.Json.Serialization @@ -185,7 +186,7 @@ module Settings = fileInfo.FullName |> File.ReadAllText |> deserialize - |> Result.bind validate + >>= validate with | :? FileNotFoundException -> Error $"File \"{fileInfo.FullName}\" was not found." | :? JsonException as e -> Error $"Parse error in \"{fileInfo.FullName}\": {e.Message}" diff --git a/src/CCVTAC.Tests/CCVTAC.Tests.fsproj b/src/CCVTAC.Tests/CCVTAC.Tests.fsproj index 47529a3..7be8ea2 100644 --- a/src/CCVTAC.Tests/CCVTAC.Tests.fsproj +++ b/src/CCVTAC.Tests/CCVTAC.Tests.fsproj @@ -8,13 +8,13 @@ - + - + diff --git a/src/CCVTAC.Tests/TagDetectionTests.fs b/src/CCVTAC.Tests/PostProcessing/Tagging/TagDetectionTests.fs similarity index 99% rename from src/CCVTAC.Tests/TagDetectionTests.fs rename to src/CCVTAC.Tests/PostProcessing/Tagging/TagDetectionTests.fs index 10ece0b..6159704 100644 --- a/src/CCVTAC.Tests/TagDetectionTests.fs +++ b/src/CCVTAC.Tests/PostProcessing/Tagging/TagDetectionTests.fs @@ -3,7 +3,7 @@ module TagDetectionTests open CCVTAC.Main.PostProcessing.Tagging open CCVTAC.Main.PostProcessing open CCVTAC.Main.Settings.Settings -open CCFSharpUtils +open CCFSharpUtils.Text open System open Xunit diff --git a/src/CCVTAC.Tests/PostProcessing/Tagging/TaggingSetTests.fs b/src/CCVTAC.Tests/PostProcessing/Tagging/TaggingSetTests.fs index 679c0e1..bbefc5c 100644 --- a/src/CCVTAC.Tests/PostProcessing/Tagging/TaggingSetTests.fs +++ b/src/CCVTAC.Tests/PostProcessing/Tagging/TaggingSetTests.fs @@ -93,6 +93,7 @@ module TaggingSetInstantiationTests = Assert.Equal(expected, actual) + [] let ``parses multiple files from playlist`` () = let dir = Path.Combine("user", "Downloads", "Audio", "tmp")