From 3e23e859e897a8e24388aa7173c88f7374305c67 Mon Sep 17 00:00:00 2001 From: CodeConscious <50596087+codeconscious@users.noreply.github.com> Date: Mon, 12 Jan 2026 20:53:01 +0900 Subject: [PATCH 01/12] Add NuGet package --- src/CCVTAC.Main/CCVTAC.Main.fsproj | 1 + src/CCVTAC.Tests/CCVTAC.Tests.fsproj | 1 + 2 files changed, 2 insertions(+) diff --git a/src/CCVTAC.Main/CCVTAC.Main.fsproj b/src/CCVTAC.Main/CCVTAC.Main.fsproj index 5f7431d..73632f6 100644 --- a/src/CCVTAC.Main/CCVTAC.Main.fsproj +++ b/src/CCVTAC.Main/CCVTAC.Main.fsproj @@ -41,6 +41,7 @@ + diff --git a/src/CCVTAC.Tests/CCVTAC.Tests.fsproj b/src/CCVTAC.Tests/CCVTAC.Tests.fsproj index bf184e0..778b9c2 100644 --- a/src/CCVTAC.Tests/CCVTAC.Tests.fsproj +++ b/src/CCVTAC.Tests/CCVTAC.Tests.fsproj @@ -15,6 +15,7 @@ + From bae4a258291b9402986dc6c8bb241066049e4b4d Mon Sep 17 00:00:00 2001 From: CodeConscious <50596087+codeconscious@users.noreply.github.com> Date: Mon, 12 Jan 2026 21:03:10 +0900 Subject: [PATCH 02/12] Use package funcs --- src/CCVTAC.Main/IoUtilities/Directories.fs | 1 + src/CCVTAC.Main/IoUtilities/Files.fs | 3 ++- src/CCVTAC.Main/PostProcessing/Mover.fs | 2 +- src/CCVTAC.Main/ResultTracker.fs | 2 +- src/CCVTAC.Main/Shared.fs | 6 ------ 5 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/CCVTAC.Main/IoUtilities/Directories.fs b/src/CCVTAC.Main/IoUtilities/Directories.fs index 4b34243..2b1b42a 100644 --- a/src/CCVTAC.Main/IoUtilities/Directories.fs +++ b/src/CCVTAC.Main/IoUtilities/Directories.fs @@ -1,6 +1,7 @@ namespace CCVTAC.Main.IoUtilities open CCVTAC.Main +open CCFSharpUtils.Library open System.IO module Directories = diff --git a/src/CCVTAC.Main/IoUtilities/Files.fs b/src/CCVTAC.Main/IoUtilities/Files.fs index c8d3510..7fe18ea 100644 --- a/src/CCVTAC.Main/IoUtilities/Files.fs +++ b/src/CCVTAC.Main/IoUtilities/Files.fs @@ -1,7 +1,8 @@ namespace CCVTAC.Main.IoUtilities -open System.IO open CCVTAC.Main +open CCFSharpUtils.Library +open System.IO module Files = diff --git a/src/CCVTAC.Main/PostProcessing/Mover.fs b/src/CCVTAC.Main/PostProcessing/Mover.fs index e8ab9f8..bdbc62f 100644 --- a/src/CCVTAC.Main/PostProcessing/Mover.fs +++ b/src/CCVTAC.Main/PostProcessing/Mover.fs @@ -29,7 +29,7 @@ module Mover = let playlistImages = images |> List.filter (fun i -> isPlaylistImage i.FullName) if not (List.isEmpty playlistImages) then Some playlistImages[0] - elif audioFileCount > 1 && images.Length = 1 + elif audioFileCount > 1 && List.hasOne images then Some images[0] else None diff --git a/src/CCVTAC.Main/ResultTracker.fs b/src/CCVTAC.Main/ResultTracker.fs index 1a60397..fdefdb2 100644 --- a/src/CCVTAC.Main/ResultTracker.fs +++ b/src/CCVTAC.Main/ResultTracker.fs @@ -33,7 +33,7 @@ type ResultTracker<'a>(printer: Printer) = | Ok _ -> successCount <- successCount + 1UL | Error e -> - let msg = if e.Length > 0 then List.head e else String.Empty + let msg = if e |> List.isNotEmpty then List.head e else String.Empty if not (failures.TryAdd(input, msg)) then failures[input] <- msg diff --git a/src/CCVTAC.Main/Shared.fs b/src/CCVTAC.Main/Shared.fs index d607d8a..7c6d7ff 100644 --- a/src/CCVTAC.Main/Shared.fs +++ b/src/CCVTAC.Main/Shared.fs @@ -8,12 +8,6 @@ module Shared = type ResultMessageCollection = { Successes: string list; Failures: string list } - /// Safely runs a function that might raise an exception. - /// If an exception is thrown, only returns its message. - let ofTry (f: unit -> 'a) : Result<'a, string> = - try Ok (f()) - with exn -> Error exn.Message - let sleep workingMsgFn doneMsgFn seconds : string = let rec loop remaining (ctx: StatusContext) = if remaining > 0us then From 4fa624f8adb288c52f0c40586775c50500f9ec26 Mon Sep 17 00:00:00 2001 From: CodeConscious <50596087+codeconscious@users.noreply.github.com> Date: Mon, 12 Jan 2026 21:21:59 +0900 Subject: [PATCH 03/12] Upgrade package --- src/CCVTAC.Main/CCVTAC.Main.fsproj | 2 +- src/CCVTAC.Tests/CCVTAC.Tests.fsproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CCVTAC.Main/CCVTAC.Main.fsproj b/src/CCVTAC.Main/CCVTAC.Main.fsproj index 73632f6..ab66807 100644 --- a/src/CCVTAC.Main/CCVTAC.Main.fsproj +++ b/src/CCVTAC.Main/CCVTAC.Main.fsproj @@ -41,7 +41,7 @@ - + diff --git a/src/CCVTAC.Tests/CCVTAC.Tests.fsproj b/src/CCVTAC.Tests/CCVTAC.Tests.fsproj index 778b9c2..3862aee 100644 --- a/src/CCVTAC.Tests/CCVTAC.Tests.fsproj +++ b/src/CCVTAC.Tests/CCVTAC.Tests.fsproj @@ -15,7 +15,7 @@ - + From e3aaaf21050058f21e99aea1585f52633dea03ff Mon Sep 17 00:00:00 2001 From: CodeConscious <50596087+codeconscious@users.noreply.github.com> Date: Mon, 12 Jan 2026 21:54:03 +0900 Subject: [PATCH 04/12] Update func to library ones, fix tests, etc. --- src/CCVTAC.Main/CCVTAC.Main.fsproj | 1 - src/CCVTAC.Main/Commands.fs | 1 + src/CCVTAC.Main/Downloading/Updater.fs | 3 +- src/CCVTAC.Main/Extensions.fs | 167 ---------- src/CCVTAC.Main/ExternalTools/Runner.fs | 1 + src/CCVTAC.Main/History.fs | 1 + src/CCVTAC.Main/IoUtilities/Directories.fs | 5 +- src/CCVTAC.Main/IoUtilities/Files.fs | 2 +- src/CCVTAC.Main/Orchestrator.fs | 9 +- src/CCVTAC.Main/PostProcessing/Deleter.fs | 5 +- .../PostProcessing/MetadataUtilities.fs | 5 +- src/CCVTAC.Main/PostProcessing/Mover.fs | 6 +- .../PostProcessing/PostProcessing.fs | 1 + src/CCVTAC.Main/PostProcessing/Renamer.fs | 13 +- .../PostProcessing/Tagging/Tagger.fs | 5 +- .../PostProcessing/Tagging/TaggingSet.fs | 3 +- src/CCVTAC.Main/Printer.fs | 3 +- src/CCVTAC.Main/Program.fs | 7 +- src/CCVTAC.Main/ResultTracker.fs | 3 +- src/CCVTAC.Main/Settings/Settings.fs | 5 +- src/CCVTAC.Tests/CCVTAC.Tests.fsproj | 1 - src/CCVTAC.Tests/ExtensionsTests.fs | 288 ------------------ src/CCVTAC.Tests/TagDetectionTests.fs | 2 +- 23 files changed, 49 insertions(+), 488 deletions(-) delete mode 100644 src/CCVTAC.Main/Extensions.fs delete mode 100644 src/CCVTAC.Tests/ExtensionsTests.fs diff --git a/src/CCVTAC.Main/CCVTAC.Main.fsproj b/src/CCVTAC.Main/CCVTAC.Main.fsproj index ab66807..2c32439 100644 --- a/src/CCVTAC.Main/CCVTAC.Main.fsproj +++ b/src/CCVTAC.Main/CCVTAC.Main.fsproj @@ -8,7 +8,6 @@ - diff --git a/src/CCVTAC.Main/Commands.fs b/src/CCVTAC.Main/Commands.fs index e55342a..8eb8013 100644 --- a/src/CCVTAC.Main/Commands.fs +++ b/src/CCVTAC.Main/Commands.fs @@ -1,5 +1,6 @@ namespace CCVTAC.Main +open CCFSharpUtils.Library open System module Commands = diff --git a/src/CCVTAC.Main/Downloading/Updater.fs b/src/CCVTAC.Main/Downloading/Updater.fs index 596b179..7d90131 100644 --- a/src/CCVTAC.Main/Downloading/Updater.fs +++ b/src/CCVTAC.Main/Downloading/Updater.fs @@ -1,8 +1,9 @@ namespace CCVTAC.Main.Downloading -open CCVTAC.Main.ExternalTools open CCVTAC.Main +open CCVTAC.Main.ExternalTools open CCVTAC.Main.Settings.Settings +open CCFSharpUtils.Library module Updater = diff --git a/src/CCVTAC.Main/Extensions.fs b/src/CCVTAC.Main/Extensions.fs deleted file mode 100644 index e4e42bc..0000000 --- a/src/CCVTAC.Main/Extensions.fs +++ /dev/null @@ -1,167 +0,0 @@ -namespace CCVTAC.Main - -open System -open System.Globalization -open System.IO -open System.Text - -type SB = StringBuilder - -module Numerics = - - let inline isZero (n: ^a) = - n = LanguagePrimitives.GenericZero<'a> - - let inline isOne (n: ^a) = - n = LanguagePrimitives.GenericOne<'a> - - /// Formats a number of any type to a comma-formatted string. - let inline formatNumber (i: ^T) : string - when ^T : (member ToString : string * IFormatProvider -> string) = - (^T : (member ToString : string * IFormatProvider -> string) (i, "#,##0", CultureInfo.InvariantCulture)) - -module String = - - let newLine = Environment.NewLine - - let hasNoText text = - String.IsNullOrWhiteSpace text - - let hasText text = - not (hasNoText text) - - let allHaveText xs = - xs |> List.forall hasText - - let textOrFallback fallback text = - if hasText text then text else fallback - - let textOrEmpty text = - textOrFallback text String.Empty - - let equalIgnoringCase x y = - String.Equals(x, y, StringComparison.OrdinalIgnoreCase) - - /// Checks whether a string begins with a specified substring. Case is ignored. - let startsWith startText (text: string) = - text.StartsWith(startText, StringComparison.InvariantCultureIgnoreCase) - - /// Checks whether a string ends with a specified substring. Case is ignored. - let endsWith endText (text: string) = - text.EndsWith(endText, StringComparison.InvariantCultureIgnoreCase) - - /// Pluralize text using a specified count. - let inline pluralize ifOne ifNotOne count = - if Numerics.isOne count then ifOne else ifNotOne - - /// Pluralize text including its count, such as "1 file", "30 URLs". - let inline pluralizeWithCount ifOne ifNotOne count = - sprintf "%d %s" count (pluralize ifOne ifNotOne count) - - let inline private fileLabeller descriptor (count: int) = - match descriptor with - | None -> $"""%s{Numerics.formatNumber count} %s{pluralize "file" "files" count}""" - | Some d -> $"""%s{Numerics.formatNumber count} %s{d} {pluralize "file" "files" count}""" - - /// Returns a file-count string, such as "0 files" or 1 file" or "140 files". - let fileLabel count = - fileLabeller None count - - /// Returns a file-count string with a descriptor, such as "0 audio files" or "140 deleted files". - let fileLabelWithDescriptor (descriptor: string) count = - fileLabeller (Some (descriptor.Trim())) count - - /// Returns a new string in which all invalid path characters for the current OS - /// have been replaced by the specified replacement character. - /// Throws if the replacement character is an invalid path character. - let replaceInvalidPathChars - (replaceWith: char option) - (customInvalidChars: char list option) - (text: string) - : string = - - let replaceWith = defaultArg replaceWith '_' - let custom = defaultArg customInvalidChars [] - - let invalidChars = - seq { - yield! Path.GetInvalidFileNameChars() - yield! Path.GetInvalidPathChars() - yield Path.PathSeparator - yield Path.DirectorySeparatorChar - yield Path.AltDirectorySeparatorChar - yield Path.VolumeSeparatorChar - yield! custom - } - |> Set.ofSeq - - if invalidChars |> Set.contains replaceWith then - invalidArg "replaceWith" $"The replacement char ('%c{replaceWith}') must be a valid path character." - - Set.fold - (fun (sb: SB) ch -> sb.Replace(ch, replaceWith)) - (SB text) - invalidChars - |> _.ToString() - - let trimTerminalLineBreak (text: string) = - text.TrimEnd(newLine.ToCharArray()) - -[] -module Seq = - - let isNotEmpty seq = not (Seq.isEmpty seq) - - let doesNotContain x seq = not <| Seq.contains x seq - - let hasOne seq = seq |> Seq.length |> Numerics.isOne - - let hasMultiple seq = seq |> Seq.length |> (<) 1 - - let caseInsensitiveContains text (xs: string seq) : bool = - xs |> Seq.exists (fun x -> String.Equals(x, text, StringComparison.OrdinalIgnoreCase)) - -[] -module List = - - let isNotEmpty lst = not (List.isEmpty lst) - - let doesNotContain x lst = not <| List.contains x lst - - let hasOne lst = lst |> List.length |> Numerics.isOne - - let hasMultiple lst = lst |> List.length |> (<) 1 - - let caseInsensitiveContains text (lst: string list) : bool = - lst |> List.exists (fun x -> String.Equals(x, text, StringComparison.OrdinalIgnoreCase)) - -[] -module Array = - - let isNotEmpty arr = not <| Array.isEmpty arr - - let doesNotContain x arr = not <| Array.contains x arr - - let hasOne arr = arr |> Array.length |> Numerics.isOne - - let hasMultiple arr = arr |> Array.length |> (<) 1 - - let caseInsensitiveContains text (arr: string array) : bool = - arr |> Array.exists (fun x -> String.Equals(x, text, StringComparison.OrdinalIgnoreCase)) - -/// Operations for regular expressions. -[] -module Rgx = - open System.Text.RegularExpressions - - let trySuccessMatch (rgx: Regex) txt = - let result = rgx.Match txt - match result.Success with - | false -> None - | true -> Some result - - let capturesToSeq (m: Match) : Match seq = - m.Captures |> Seq.cast - - let fstCapture (m: Match) = - m |> capturesToSeq |> Seq.head diff --git a/src/CCVTAC.Main/ExternalTools/Runner.fs b/src/CCVTAC.Main/ExternalTools/Runner.fs index 5fd2a1a..d49b61d 100644 --- a/src/CCVTAC.Main/ExternalTools/Runner.fs +++ b/src/CCVTAC.Main/ExternalTools/Runner.fs @@ -1,6 +1,7 @@ namespace CCVTAC.Main.ExternalTools open CCVTAC.Main +open CCFSharpUtils.Library open Startwatch.Library open System open System.Diagnostics diff --git a/src/CCVTAC.Main/History.fs b/src/CCVTAC.Main/History.fs index 80b084d..9af3e5a 100644 --- a/src/CCVTAC.Main/History.fs +++ b/src/CCVTAC.Main/History.fs @@ -1,6 +1,7 @@ namespace CCVTAC.Main open CCVTAC.Main.IoUtilities.Files +open CCFSharpUtils.Library open System open System.IO open System.Text.Json diff --git a/src/CCVTAC.Main/IoUtilities/Directories.fs b/src/CCVTAC.Main/IoUtilities/Directories.fs index 2b1b42a..007a05d 100644 --- a/src/CCVTAC.Main/IoUtilities/Directories.fs +++ b/src/CCVTAC.Main/IoUtilities/Directories.fs @@ -3,6 +3,7 @@ namespace CCVTAC.Main.IoUtilities open CCVTAC.Main open CCFSharpUtils.Library open System.IO +open System.Text module Directories = @@ -12,7 +13,7 @@ module Directories = /// Counts the number of audio files in a directory. let audioFileCount (directory: string) (includedExtensions: string list) = DirectoryInfo(directory).EnumerateFiles() - |> Seq.filter (fun f -> List.caseInsensitiveContains f.Extension includedExtensions) + |> Seq.filter (fun f -> List.containsIgnoreCase f.Extension includedExtensions) |> Seq.length /// Returns the filenames in a given directory, optionally ignoring specific filenames. @@ -72,7 +73,7 @@ module Directories = if Array.isEmpty fileNames then Ok () else - SB($"Unexpectedly found {String.fileLabel fileNames.Length} in working directory \"{dirName}\":{String.newLine}") + StringBuilder($"Unexpectedly found {String.fileLabel fileNames.Length} in working directory \"{dirName}\":{String.newLine}") .AppendLine (fileNames |> Array.truncate showMax diff --git a/src/CCVTAC.Main/IoUtilities/Files.fs b/src/CCVTAC.Main/IoUtilities/Files.fs index 7fe18ea..00d2e98 100644 --- a/src/CCVTAC.Main/IoUtilities/Files.fs +++ b/src/CCVTAC.Main/IoUtilities/Files.fs @@ -14,7 +14,7 @@ module Files = let imageFileExts = [".jpg"; ".jpeg"] - let filterByExt ext fs = fs |> List.filter (String.endsWith ext) + let filterByExt ext fs = fs |> List.filter (String.endsWithIgnoreCase ext) let readAllText (filePath: string) : Result = ofTry (fun _ -> File.ReadAllText filePath) diff --git a/src/CCVTAC.Main/Orchestrator.fs b/src/CCVTAC.Main/Orchestrator.fs index 833fd86..788cba1 100644 --- a/src/CCVTAC.Main/Orchestrator.fs +++ b/src/CCVTAC.Main/Orchestrator.fs @@ -7,6 +7,7 @@ open CCVTAC.Main.PostProcessing open CCVTAC.Main.Settings open CCVTAC.Main.Settings.Settings open CCVTAC.Main.Settings.Settings.LiveUpdating +open CCFSharpUtils.Library open Startwatch.Library open System @@ -120,10 +121,10 @@ module Orchestrator = (printer: Printer) : Result = - let checkCommand = List.caseInsensitiveContains command + let checkCommand = List.containsIgnoreCase command // Help - if String.equalIgnoringCase Commands.helpCommand command then + if String.equalIgnoreCase Commands.helpCommand command then for kvp in Commands.summary do printer.Info(kvp.Key) printer.Info $" %s{kvp.Value}" @@ -168,7 +169,7 @@ module Orchestrator = Ok { NextAction = NextAction.Continue; UpdatedSettings = Some newSettings } // Update audio formats - elif command |> String.startsWith Commands.updateAudioFormatPrefix then + elif command |> String.startsWithIgnoreCase Commands.updateAudioFormatPrefix then let format = command.Replace(Commands.updateAudioFormatPrefix, String.Empty).ToLowerInvariant() if String.hasNoText format then Error "You must append one or more supported audio formats separated by commas (e.g., \"m4a,opus,best\")." @@ -181,7 +182,7 @@ module Orchestrator = Ok { NextAction = NextAction.Continue; UpdatedSettings = Some newSettings } // Update audio quality - elif command |> String.startsWith Commands.updateAudioQualityPrefix then + elif command |> String.startsWithIgnoreCase Commands.updateAudioQualityPrefix then let inputQuality = command.Replace(Commands.updateAudioQualityPrefix, String.Empty) if String.hasNoText inputQuality then Error "You must enter a number representing an audio quality between 10 (lowest) and 0 (highest)." diff --git a/src/CCVTAC.Main/PostProcessing/Deleter.fs b/src/CCVTAC.Main/PostProcessing/Deleter.fs index b850d6c..e6485c5 100644 --- a/src/CCVTAC.Main/PostProcessing/Deleter.fs +++ b/src/CCVTAC.Main/PostProcessing/Deleter.fs @@ -1,6 +1,7 @@ namespace CCVTAC.Main.PostProcessing open CCVTAC.Main +open CCFSharpUtils.Library open System.IO module Deleter = @@ -35,7 +36,7 @@ module Deleter = let collectionFileNames = match getCollectionFiles collectionMetadata workingDirectory with | Ok files -> - printer.Debug $"""Found {String.fileLabelWithDescriptor "collection" files.Length}.""" + printer.Debug $"""Found {String.fileLabelWithDesc "collection" files.Length}.""" files | Error err -> printer.Warning err @@ -46,6 +47,6 @@ module Deleter = if Array.isEmpty allFileNames then printer.Warning "No files to delete were found." else - printer.Debug $"""Deleting {String.fileLabelWithDescriptor "temporary" allFileNames.Length}...""" + printer.Debug $"""Deleting {String.fileLabelWithDesc "temporary" allFileNames.Length}...""" deleteAll allFileNames printer printer.Info "Deleted temporary files." diff --git a/src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs b/src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs index e107389..087eb8a 100644 --- a/src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs +++ b/src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs @@ -1,8 +1,9 @@ namespace CCVTAC.Main.PostProcessing +open CCVTAC.Main +open CCFSharpUtils.Library open System open System.Text -open CCVTAC.Main module MetadataUtilities = @@ -20,7 +21,7 @@ module MetadataUtilities = sprintf "%s/%s/%s" m d y let generateComment (v: VideoMetadata) (c: CollectionMetadata option) : string = - let sb = SB() + let sb = StringBuilder() sb.AppendLine("CCVTAC SOURCE DATA:") |> ignore sb.AppendLine $"■ Downloaded: {DateTime.Now}" |> ignore sb.AppendLine $"■ URL: %s{v.WebpageUrl}" |> ignore diff --git a/src/CCVTAC.Main/PostProcessing/Mover.fs b/src/CCVTAC.Main/PostProcessing/Mover.fs index bdbc62f..26a5187 100644 --- a/src/CCVTAC.Main/PostProcessing/Mover.fs +++ b/src/CCVTAC.Main/PostProcessing/Mover.fs @@ -1,3 +1,4 @@ + namespace CCVTAC.Main.PostProcessing open CCVTAC.Main @@ -5,6 +6,7 @@ open CCVTAC.Main.IoUtilities open CCVTAC.Main.PostProcessing open CCVTAC.Main.PostProcessing.Tagging open CCVTAC.Main.Settings.Settings +open CCFSharpUtils.Library open TaggingSet open System open System.IO @@ -130,13 +132,13 @@ module Mover = let audioFileNames = workingDirInfo.EnumerateFiles() - |> Seq.filter (fun f -> List.caseInsensitiveContains f.Extension Files.audioFileExts) + |> Seq.filter (fun f -> List.containsIgnoreCase f.Extension Files.audioFileExts) |> List.ofSeq if audioFileNames.IsEmpty then printer.Error "No audio filenames to move were found." else - let fileCountMsg = String.fileLabelWithDescriptor "audio" + let fileCountMsg = String.fileLabelWithDesc "audio" printer.Debug $"Moving %s{fileCountMsg audioFileNames.Length} to \"%s{fullMoveToDir}\"..." diff --git a/src/CCVTAC.Main/PostProcessing/PostProcessing.fs b/src/CCVTAC.Main/PostProcessing/PostProcessing.fs index 55788ce..3c938e7 100644 --- a/src/CCVTAC.Main/PostProcessing/PostProcessing.fs +++ b/src/CCVTAC.Main/PostProcessing/PostProcessing.fs @@ -4,6 +4,7 @@ open CCVTAC.Main open CCVTAC.Main.IoUtilities open CCVTAC.Main.PostProcessing.Tagging open CCVTAC.Main.Settings.Settings +open CCFSharpUtils.Library open System.IO open System.Linq open System.Text.Json diff --git a/src/CCVTAC.Main/PostProcessing/Renamer.fs b/src/CCVTAC.Main/PostProcessing/Renamer.fs index 6d1d887..0c06de5 100644 --- a/src/CCVTAC.Main/PostProcessing/Renamer.fs +++ b/src/CCVTAC.Main/PostProcessing/Renamer.fs @@ -3,6 +3,7 @@ namespace CCVTAC.Main.PostProcessing open CCVTAC.Main open CCVTAC.Main.IoUtilities open CCVTAC.Main.Settings.Settings +open CCFSharpUtils.Library open System open System.IO open System.Text @@ -18,7 +19,7 @@ module Renamer = | "KC" -> NormalizationForm.FormKC | _ -> NormalizationForm.FormC - let updateTextViaPatterns isQuietMode (printer: Printer) (sb: SB) (renamePattern: RenamePattern) = + let updateTextViaPatterns isQuietMode (printer: Printer) (sb: StringBuilder) (renamePattern: RenamePattern) = let regex = Regex renamePattern.RegexPattern let matches = @@ -55,7 +56,7 @@ module Renamer = then m.Groups[i + 1].Value.Trim() else String.Empty (searchFor, replaceWith)) - |> Seq.fold (fun (sb': SB) -> sb'.Replace) (SB renamePattern.ReplaceWithPattern) + |> Seq.fold (fun (sb': StringBuilder) -> sb'.Replace) (StringBuilder renamePattern.ReplaceWithPattern) |> _.ToString() sb.Insert(m.Index, replacementText) |> ignore @@ -68,20 +69,20 @@ module Renamer = let audioFiles = workingDirInfo.EnumerateFiles() - |> Seq.filter (fun f -> List.caseInsensitiveContains f.Extension Files.audioFileExts) + |> Seq.filter (fun f -> List.containsIgnoreCase f.Extension Files.audioFileExts) |> List.ofSeq if List.isEmpty audioFiles then printer.Warning "No audio files to rename were found." else - printer.Debug $"""Renaming %s{String.fileLabelWithDescriptor "audio" audioFiles.Length}...""" + printer.Debug $"""Renaming %s{String.fileLabelWithDesc "audio" audioFiles.Length}...""" for audioFile in audioFiles do let newFileName = userSettings.RenamePatterns |> List.fold - (fun (sb: SB) -> updateTextViaPatterns userSettings.QuietMode printer sb) - (SB audioFile.Name) + (fun (sb: StringBuilder) -> updateTextViaPatterns userSettings.QuietMode printer sb) + (StringBuilder audioFile.Name) |> _.ToString() try diff --git a/src/CCVTAC.Main/PostProcessing/Tagging/Tagger.fs b/src/CCVTAC.Main/PostProcessing/Tagging/Tagger.fs index 9d4d2a6..8a905fc 100644 --- a/src/CCVTAC.Main/PostProcessing/Tagging/Tagger.fs +++ b/src/CCVTAC.Main/PostProcessing/Tagging/Tagger.fs @@ -5,6 +5,7 @@ open CCVTAC.Main.Settings.Settings open CCVTAC.Main.PostProcessing open CCVTAC.Main.PostProcessing.Tagging open CCVTAC.Main.Downloading.Downloading +open CCFSharpUtils.Library open Startwatch.Library open TaggingSet open MetadataUtilities @@ -60,7 +61,7 @@ module Tagger = printer.Error $"Error writing image to the audio file: %s{exn.Message}" let private releaseYear userSettings videoMetadata : uint32 option = - if userSettings.IgnoreUploadYearUploaders |> List.caseInsensitiveContains videoMetadata.Uploader then + if userSettings.IgnoreUploadYearUploaders |> List.containsIgnoreCase videoMetadata.Uploader then None elif videoMetadata.UploadDate.Length <> 4 then None @@ -181,7 +182,7 @@ module Tagger = (printer: Printer) : unit = - printer.Debug $"""Found %s{String.fileLabelWithDescriptor "audio" taggingSet.AudioFiles.Length} with resource ID %s{taggingSet.VideoId}.""" + printer.Debug $"""Found %s{String.fileLabelWithDesc "audio" taggingSet.AudioFiles.Length} with resource ID %s{taggingSet.VideoId}.""" match parseVideoJson taggingSet with | Ok videoData -> diff --git a/src/CCVTAC.Main/PostProcessing/Tagging/TaggingSet.fs b/src/CCVTAC.Main/PostProcessing/Tagging/TaggingSet.fs index e1e4de5..d7c170f 100644 --- a/src/CCVTAC.Main/PostProcessing/Tagging/TaggingSet.fs +++ b/src/CCVTAC.Main/PostProcessing/Tagging/TaggingSet.fs @@ -2,6 +2,7 @@ namespace CCVTAC.Main.PostProcessing.Tagging open CCVTAC.Main open CCVTAC.Main.IoUtilities +open CCFSharpUtils.Library open FsToolkit.ErrorHandling open System.IO open System.Text.RegularExpressions @@ -46,7 +47,7 @@ module TaggingSet = match Path.GetExtension fileName with | Null -> false | NonNull empty when String.hasNoText empty -> false - | NonNull ext -> Files.audioFileExts |> List.caseInsensitiveContains ext + | NonNull ext -> Files.audioFileExts |> List.containsIgnoreCase ext let audioFiles = files |> List.filter hasSupportedAudioExt let jsonFiles = files |> Files.filterByExt Files.jsonFileExt diff --git a/src/CCVTAC.Main/Printer.fs b/src/CCVTAC.Main/Printer.fs index d73f12b..70b6395 100644 --- a/src/CCVTAC.Main/Printer.fs +++ b/src/CCVTAC.Main/Printer.fs @@ -1,5 +1,6 @@ namespace CCVTAC.Main +open CCFSharpUtils.Library open System open System.Collections.Generic open System.Linq @@ -136,7 +137,7 @@ type Printer(showDebug: bool) = /// Prints the requested number of blank lines. static member EmptyLines(count: byte) = - if Numerics.isZero count + if Num.isZero count then () else let repeats = int count - 1 diff --git a/src/CCVTAC.Main/Program.fs b/src/CCVTAC.Main/Program.fs index dd1634b..50a6bfb 100644 --- a/src/CCVTAC.Main/Program.fs +++ b/src/CCVTAC.Main/Program.fs @@ -5,9 +5,10 @@ open CCVTAC.Main.IoUtilities open CCVTAC.Main.Settings open CCVTAC.Main.Settings.Settings open Settings.IO +open CCFSharpUtils.Library +open Spectre.Console open System open System.IO -open Spectre.Console module Program = @@ -24,13 +25,13 @@ module Program = let main args : int = let printer = Printer(showDebug = true) - if Array.isNotEmpty args && Array.caseInsensitiveContains args[0] helpFlags then + if Array.isNotEmpty args && Array.containsIgnoreCase args[0] helpFlags then printer.Info Help.helpText int ExitCodes.Success else let settingsPath = FileInfo <| - if Array.hasMultiple args && Array.caseInsensitiveContains args[0] settingsFileFlags then + if Array.hasMultiple args && Array.containsIgnoreCase args[0] settingsFileFlags then args[1] // Expected to be a settings file path. else defaultSettingsFileName diff --git a/src/CCVTAC.Main/ResultTracker.fs b/src/CCVTAC.Main/ResultTracker.fs index fdefdb2..2a9794e 100644 --- a/src/CCVTAC.Main/ResultTracker.fs +++ b/src/CCVTAC.Main/ResultTracker.fs @@ -1,5 +1,6 @@ namespace CCVTAC.Main +open CCFSharpUtils.Library open System open System.Collections.Generic @@ -39,7 +40,7 @@ type ResultTracker<'a>(printer: Printer) = /// Prints any failures for the current batch. member _.PrintBatchFailures() : unit = - if Numerics.isZero failures.Count then + if Num.isZero failures.Count then printer.Debug "No failures in batch." else let failureLabel = String.pluralize "failure" "failures" failures.Count diff --git a/src/CCVTAC.Main/Settings/Settings.fs b/src/CCVTAC.Main/Settings/Settings.fs index 23c8f4c..e0fa905 100644 --- a/src/CCVTAC.Main/Settings/Settings.fs +++ b/src/CCVTAC.Main/Settings/Settings.fs @@ -1,9 +1,10 @@ namespace CCVTAC.Main.Settings -open System -open System.Text.Json.Serialization open CCVTAC.Main +open CCFSharpUtils.Library open Spectre.Console +open System +open System.Text.Json.Serialization module Settings = diff --git a/src/CCVTAC.Tests/CCVTAC.Tests.fsproj b/src/CCVTAC.Tests/CCVTAC.Tests.fsproj index 3862aee..bbabcaa 100644 --- a/src/CCVTAC.Tests/CCVTAC.Tests.fsproj +++ b/src/CCVTAC.Tests/CCVTAC.Tests.fsproj @@ -9,7 +9,6 @@ - diff --git a/src/CCVTAC.Tests/ExtensionsTests.fs b/src/CCVTAC.Tests/ExtensionsTests.fs deleted file mode 100644 index 506958c..0000000 --- a/src/CCVTAC.Tests/ExtensionsTests.fs +++ /dev/null @@ -1,288 +0,0 @@ -module ExtensionsTests - -open CCVTAC.Main -open Xunit -open System - -module NumericsTests = - open CCVTAC.Main.Numerics - - [] - let ``isZero returns true for any zero value`` () = - Assert.True <| isZero 0 - Assert.True <| isZero 0u - Assert.True <| isZero 0us - Assert.True <| isZero 0. - Assert.True <| isZero 0L - Assert.True <| isZero 0m - Assert.True <| isZero -0 - Assert.True <| isZero -0. - Assert.True <| isZero -0L - Assert.True <| isZero -0m - - [] - let ``isZero returns false for any non-zero value`` () = - Assert.False <| isZero 1 - Assert.False <| isOne -1 - Assert.False <| isOne Int64.MinValue - Assert.False <| isOne Int64.MaxValue - Assert.False <| isOne 2 - Assert.False <| isZero 1u - Assert.False <| isZero 1us - Assert.False <| isZero -0.0000000000001 - Assert.False <| isZero 0.0000000000001 - Assert.False <| isZero 1. - Assert.False <| isZero 1L - Assert.False <| isZero 1m - - [] - let ``isOne returns true for any one value`` () = - Assert.True <| isOne 1 - Assert.True <| isOne 1u - Assert.True <| isOne 1us - Assert.True <| isOne 1. - Assert.True <| isOne 1L - Assert.True <| isOne 1m - - [] - let ``isOne returns false for any non-one value`` () = - Assert.False <| isOne 0 - Assert.False <| isOne -1 - Assert.False <| isOne Int64.MinValue - Assert.False <| isOne Int64.MaxValue - Assert.False <| isOne 2 - Assert.False <| isOne 0u - Assert.False <| isOne 16u - Assert.False <| isOne 0us - Assert.False <| isOne -0. - Assert.False <| isOne 0.001 - Assert.False <| isOne 0L - Assert.False <| isOne 0m - - module FormatNumberTests = - - // A tiny custom type that implements the required ToString signature. - type MyCustomNum(i: int) = - member _.ToString(fmt: string, provider: IFormatProvider) = - i.ToString(fmt, provider) - - [] - let ``format int`` () = - let actual = formatNumber 123456 - Assert.Equal("123,456", actual) - - [] - let ``format negative int`` () = - let actual = formatNumber -1234 - Assert.Equal("-1,234", actual) - - [] - let ``format zero`` () = - let actual = formatNumber 0 - Assert.Equal("0", actual) - - [] - let ``format int64`` () = - let actual = formatNumber 1234567890L - Assert.Equal("1,234,567,890", actual) - - [] - let ``format decimal rounds to integer display`` () = - let actual = formatNumber 123456.78M - Assert.Equal("123,457", actual) - - [] - let ``format float rounds to integer display`` () = - let actual = formatNumber 123456.78 - Assert.Equal("123,457", actual) - - [] - let ``format negative float rounds to integer display`` () = - let actual = formatNumber -1234.56 - Assert.Equal("-1,235", actual) - - [] - let ``format custom numeric type`` () = - let myNum = MyCustomNum 1234 - let actual = formatNumber myNum - Assert.Equal("1,234", actual) - -module StringTests = - open CCVTAC.Main.String - - [] - let ``fileLabel formats correctly`` () = - Assert.True <| (fileLabel 0 = "0 files") - Assert.True <| (fileLabel 1 = "1 file") - Assert.True <| (fileLabel 2 = "2 files") - Assert.True <| (fileLabel 1_000_000 = "1,000,000 files") - - [] - let ``fileLabelWithDescriptor formats correctly`` () = - Assert.True <| (fileLabelWithDescriptor "audio" 0 = "0 audio files") - Assert.True <| (fileLabelWithDescriptor " temporary " 1 = "1 temporary file") - Assert.True <| (fileLabelWithDescriptor "deleted" 2 = "2 deleted files") - Assert.True <| (fileLabelWithDescriptor "image" 1_000_000 = "1,000,000 image files") - - module ReplaceInvalidPathCharsTests = - open System.IO - - [] - let ``Default replacement '_' replaces invalid path chars`` () = - let invalids = Path.GetInvalidFileNameChars() |> Array.except ['\000'] - if Array.isEmpty invalids then - Assert.True(false, "Unexpected environment: not enough invalid filename chars") - let invalid = invalids[0] - let input = $"start%c{invalid}end" - let result = replaceInvalidPathChars None None input - - Assert.DoesNotContain(invalid.ToString(), result) - Assert.Contains("_", result) - Assert.StartsWith("start", result) - Assert.EndsWith("end", result) - - [] - let ``Custom invalid chars are replaced with provided replacement`` () = - let custom = ['#'; '%'] - let input = "abc#def%ghi" - let result = replaceInvalidPathChars (Some '-') (Some custom) input - - Assert.DoesNotContain("#", result) - Assert.DoesNotContain("%", result) - Assert.Equal("abc-def-ghi", result) - - [] - let ``Throws when replacement char itself is invalid`` () = - let invalidReplacement = Array.head <| Path.GetInvalidFileNameChars() - let input = "has-no-invalid-chars" - Assert.Throws(fun () -> - replaceInvalidPathChars (Some invalidReplacement) None input |> ignore) - - [] - let ``No invalid chars returns identical string`` () = - let input = "HelloWorld123" - let result = replaceInvalidPathChars None None input - Assert.Equal(input, result) - - [] - let ``All invalid path and filename chars are replaced`` () = - let fileInvalid = Path.GetInvalidFileNameChars() |> Array.truncate 3 - let pathInvalid = Path.GetInvalidPathChars() |> Array.truncate 3 - let extras = [| Path.PathSeparator - Path.DirectorySeparatorChar - Path.AltDirectorySeparatorChar - Path.VolumeSeparatorChar |] - let charsToTest = Array.concat [ fileInvalid; pathInvalid; extras ] - let input = String charsToTest - - let result = replaceInvalidPathChars None None input - - result.ToCharArray() |> Array.iter (fun ch -> Assert.Equal(ch, '_')) - Assert.Equal(input.Length, result.Length) - -module SeqTests = - - [] - let ``caseInsensitiveContains returns true when exact match exists`` () = - let input = ["Hello"; "World"; "Test"] - Assert.True <| Seq.caseInsensitiveContains "Hello" input - Assert.True <| Seq.caseInsensitiveContains "World" input - Assert.True <| Seq.caseInsensitiveContains "Test" input - - [] - let ``caseInsensitiveContains returns true when exists but case differs`` () = - let input = ["hello"; "WORLD"; "test"] - Assert.True <| Seq.caseInsensitiveContains "Hello" input - Assert.True <| Seq.caseInsensitiveContains "hello" input - Assert.True <| Seq.caseInsensitiveContains "HELLO" input - Assert.True <| Seq.caseInsensitiveContains "wOrLd" input - Assert.True <| Seq.caseInsensitiveContains "tESt" input - Assert.True <| Seq.caseInsensitiveContains "TEST" input - - [] - let ``caseInsensitiveContains returns false when text not in sequence`` () = - let input = ["Hello"; "World"; "Test"] - Assert.False <| Seq.caseInsensitiveContains "Missing" input - - [] - let ``caseInsensitiveContains works with empty sequence`` () = - Assert.False <| Seq.caseInsensitiveContains "Any" [] - - [] - let ``caseInsensitiveContains handles null or empty strings`` () = - let input = [String.Empty; null; "Test"] - Assert.True <| Seq.caseInsensitiveContains String.Empty input - Assert.True <| Seq.caseInsensitiveContains null input - - [] - let ``caseInsensitiveContains handles Japanese strings`` () = - let input = ["関数型プログラミング"; "楽しいぞ"] - Assert.True <| Seq.caseInsensitiveContains "関数型プログラミング" input - Assert.False <| Seq.caseInsensitiveContains "いや、楽しくないや" input - -module ListTests = - - [] - let ``caseInsensitiveContains returns true when exact match exists`` () = - let input = ["Hello"; "World"; "Test"] - Assert.True <| List.caseInsensitiveContains "Hello" input - Assert.True <| List.caseInsensitiveContains "World" input - Assert.True <| List.caseInsensitiveContains "Test" input - - [] - let ``caseInsensitiveContains returns true when exists but case differs`` () = - let input = ["hello"; "WORLD"; "test"] - Assert.True <| List.caseInsensitiveContains "Hello" input - Assert.True <| List.caseInsensitiveContains "hello" input - Assert.True <| List.caseInsensitiveContains "HELLO" input - Assert.True <| List.caseInsensitiveContains "wOrLd" input - Assert.True <| List.caseInsensitiveContains "tESt" input - Assert.True <| List.caseInsensitiveContains "TEST" input - - [] - let ``caseInsensitiveContains returns false when text not in sequence`` () = - let input = ["Hello"; "World"; "Test"] - Assert.False <| List.caseInsensitiveContains "Missing" input - - [] - let ``caseInsensitiveContains works with empty sequence`` () = - Assert.False <| List.caseInsensitiveContains "Any" [] - - [] - let ``caseInsensitiveContains handles null or empty strings`` () = - let input = [String.Empty; null; "Test"] - Assert.True <| List.caseInsensitiveContains String.Empty input - Assert.True <| List.caseInsensitiveContains null input - - [] - let ``caseInsensitiveContains handles Japanese strings`` () = - let input = ["関数型プログラミング"; "楽しいぞ"] - Assert.True <| List.caseInsensitiveContains "関数型プログラミング" input - Assert.False <| List.caseInsensitiveContains "いや、楽しくないや" input - -module ArrayTests = - - module HasMultiple = - - [] - let ``hasMultiple returns true for array with more than one element`` () = - Assert.True <| Array.hasMultiple [| 1; 2; 3 |] - - [] - let ``hasMultiple returns false for empty array`` () = - Assert.False <| Array.hasMultiple [||] - - [] - let ``hasMultiple returns false for single-element array`` () = - Assert.False <| Array.hasMultiple [| 0 |] - - [] - let ``hasMultiple works with different types of arrays`` () = - Assert.True <| Array.hasMultiple [| "hello"; "world" |] - Assert.True <| Array.hasMultiple [| 1.0; 2.0; 3.0 |] - Assert.True <| Array.hasMultiple [| false; true; true |] - Assert.True <| Array.hasMultiple [| Array.sum; Array.length |] - - [] - let ``hasMultiple handles large arrays`` () = - Assert.True <| Array.hasMultiple (Array.init 100 id) diff --git a/src/CCVTAC.Tests/TagDetectionTests.fs b/src/CCVTAC.Tests/TagDetectionTests.fs index ed06004..ccf40e1 100644 --- a/src/CCVTAC.Tests/TagDetectionTests.fs +++ b/src/CCVTAC.Tests/TagDetectionTests.fs @@ -1,9 +1,9 @@ module TagDetectionTests -open CCVTAC.Main open CCVTAC.Main.PostProcessing.Tagging open CCVTAC.Main.PostProcessing open CCVTAC.Main.Settings.Settings +open CCFSharpUtils.Library open System open Xunit From 60863ee47dfdd58e43e842eba17b9e0162b5d65a Mon Sep 17 00:00:00 2001 From: CodeConscious <50596087+codeconscious@users.noreply.github.com> Date: Tue, 13 Jan 2026 11:43:36 +0900 Subject: [PATCH 05/12] Use pattern matching over func --- src/CCVTAC.Main/ResultTracker.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CCVTAC.Main/ResultTracker.fs b/src/CCVTAC.Main/ResultTracker.fs index 2a9794e..ff717b2 100644 --- a/src/CCVTAC.Main/ResultTracker.fs +++ b/src/CCVTAC.Main/ResultTracker.fs @@ -34,7 +34,7 @@ type ResultTracker<'a>(printer: Printer) = | Ok _ -> successCount <- successCount + 1UL | Error e -> - let msg = if e |> List.isNotEmpty then List.head e else String.Empty + let msg = match e with [] -> String.Empty | _ -> List.head e if not (failures.TryAdd(input, msg)) then failures[input] <- msg From a5fc5bd81c63750e2cd0027740ac3b5301969464 Mon Sep 17 00:00:00 2001 From: CodeConscious <50596087+codeconscious@users.noreply.github.com> Date: Wed, 14 Jan 2026 09:35:41 +0900 Subject: [PATCH 06/12] Use List.isNotEmpty --- src/CCVTAC.Main/PostProcessing/Mover.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CCVTAC.Main/PostProcessing/Mover.fs b/src/CCVTAC.Main/PostProcessing/Mover.fs index 26a5187..abb3c7c 100644 --- a/src/CCVTAC.Main/PostProcessing/Mover.fs +++ b/src/CCVTAC.Main/PostProcessing/Mover.fs @@ -29,7 +29,7 @@ module Mover = None else let playlistImages = images |> List.filter (fun i -> isPlaylistImage i.FullName) - if not (List.isEmpty playlistImages) + if List.isNotEmpty playlistImages then Some playlistImages[0] elif audioFileCount > 1 && List.hasOne images then Some images[0] From a77181b76d0d9bd9e44433407593721cfa0d6f1b Mon Sep 17 00:00:00 2001 From: CodeConscious <50596087+codeconscious@users.noreply.github.com> Date: Wed, 14 Jan 2026 10:53:21 +0900 Subject: [PATCH 07/12] Extra: Add playlist description conditionally --- src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs b/src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs index 087eb8a..1be0291 100644 --- a/src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs +++ b/src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs @@ -54,7 +54,8 @@ module MetadataUtilities = match v.PlaylistIndex with | Some index -> if index > 0u then sb.AppendLine $"■ Playlist index: %d{index}" |> ignore | None -> () - sb.AppendLine($"■ Playlist description: %s{String.textOrEmpty c'.Description}") |> ignore + if String.hasText c'.Description then + sb.AppendLine($"■ Playlist description: %s{c'.Description}") |> ignore | None -> () sb.ToString() From 2d8b899e28665d52a0b99e3f827e45a919005a98 Mon Sep 17 00:00:00 2001 From: CodeConscious <50596087+codeconscious@users.noreply.github.com> Date: Wed, 14 Jan 2026 16:16:06 +0900 Subject: [PATCH 08/12] Tweak opens --- src/CCVTAC.Main/PostProcessing/PostProcessing.fs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CCVTAC.Main/PostProcessing/PostProcessing.fs b/src/CCVTAC.Main/PostProcessing/PostProcessing.fs index 3c938e7..414b85d 100644 --- a/src/CCVTAC.Main/PostProcessing/PostProcessing.fs +++ b/src/CCVTAC.Main/PostProcessing/PostProcessing.fs @@ -3,14 +3,14 @@ namespace CCVTAC.Main.PostProcessing open CCVTAC.Main open CCVTAC.Main.IoUtilities open CCVTAC.Main.PostProcessing.Tagging +open CCVTAC.Main.PostProcessing.Tagging.TaggingSet open CCVTAC.Main.Settings.Settings open CCFSharpUtils.Library +open Startwatch.Library open System.IO open System.Linq open System.Text.Json open System.Text.RegularExpressions -open Startwatch.Library -open TaggingSet module PostProcessor = From dc00c58d5dd2a2150845f408ead6ac451744040e Mon Sep 17 00:00:00 2001 From: CodeConscious <50596087+codeconscious@users.noreply.github.com> Date: Wed, 14 Jan 2026 16:20:12 +0900 Subject: [PATCH 09/12] Update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7fda574..6ba778a 100644 --- a/README.md +++ b/README.md @@ -221,4 +221,4 @@ The first incarnation of this application was written in C#. However, after pick As an experiment, I rewrote this application in OOP-style F#, using LLMs solely for the rough initial conversion (which greatly reduced the overall time and labor necessary at the cost of requiring a *lot* of manual cleanup). Ultimately, I was surprised how much I preferred the F# code over the C#, so I decided to keep this tool in F#. -Due to this background, the code is not particularly idiomatic F#, but it is perfectly viable in its current blended-style form. That said, I'll probably tweak it over time to gradually to introduce more FP, mainly for practice. +Due to this background, the code is not particularly idiomatic F#, but it is certainly perfectly viable in its current blended-style form. That said, I'll probably tweak it over time to gradually to introduce more idiomatic F# code. From 05e50bf7d6793d0d40e3d853f63409cecb6e3b0a Mon Sep 17 00:00:00 2001 From: CodeConscious <50596087+codeconscious@users.noreply.github.com> Date: Wed, 14 Jan 2026 17:42:47 +0900 Subject: [PATCH 10/12] Update CCFSharpUtils package --- src/CCVTAC.Main/CCVTAC.Main.fsproj | 2 +- src/CCVTAC.Tests/CCVTAC.Tests.fsproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CCVTAC.Main/CCVTAC.Main.fsproj b/src/CCVTAC.Main/CCVTAC.Main.fsproj index 2c32439..b9a83f0 100644 --- a/src/CCVTAC.Main/CCVTAC.Main.fsproj +++ b/src/CCVTAC.Main/CCVTAC.Main.fsproj @@ -40,7 +40,7 @@ - + diff --git a/src/CCVTAC.Tests/CCVTAC.Tests.fsproj b/src/CCVTAC.Tests/CCVTAC.Tests.fsproj index bbabcaa..c972faf 100644 --- a/src/CCVTAC.Tests/CCVTAC.Tests.fsproj +++ b/src/CCVTAC.Tests/CCVTAC.Tests.fsproj @@ -14,7 +14,7 @@ - + From ab1c3bfac6c4b6452caed72bd2473d8fc14e206f Mon Sep 17 00:00:00 2001 From: CodeConscious <50596087+codeconscious@users.noreply.github.com> Date: Wed, 14 Jan 2026 17:43:02 +0900 Subject: [PATCH 11/12] Change <=0 to <1 --- src/CCVTAC.Main/Printer.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CCVTAC.Main/Printer.fs b/src/CCVTAC.Main/Printer.fs index 70b6395..65f8a5c 100644 --- a/src/CCVTAC.Main/Printer.fs +++ b/src/CCVTAC.Main/Printer.fs @@ -141,7 +141,7 @@ type Printer(showDebug: bool) = then () else let repeats = int count - 1 - if repeats <= 0 + if repeats < 1 then AnsiConsole.WriteLine() else Enumerable.Repeat(String.newLine, repeats) |> String.Concat |> AnsiConsole.WriteLine From 841d81de4ee6b0292ad54cb3e0f1db067ff8b34c Mon Sep 17 00:00:00 2001 From: CodeConscious <50596087+codeconscious@users.noreply.github.com> Date: Thu, 15 Jan 2026 09:26:04 +0900 Subject: [PATCH 12/12] Rename func call --- src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs b/src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs index 1be0291..a75c543 100644 --- a/src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs +++ b/src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs @@ -43,7 +43,7 @@ module MetadataUtilities = if v.UploadDate.Length = 8 then sb.AppendLine $"■ Uploaded: %s{formattedUploadDate v.UploadDate}" |> ignore - let description = String.textOrFallback "None." v.Description + let description = String.textElse "None." v.Description sb.AppendLine $"■ Video description: %s{description}" |> ignore match c with