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..96749ba 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
@@ -292,9 +293,10 @@ module Orchestrator =
let input = printer.GetInput prompt
let splitInputs = splitInputText input
- if List.isEmpty splitInputs then
+ match splitInputs with
+ | [] ->
printer.Error $"Invalid input. Enter only URLs or commands beginning with \"%c{Commands.prefix}\"."
- else
+ | _ ->
let categorizedInputs = categorizeInputs splitInputs
let categoryCounts = countCategories categorizedInputs
summarizeInput categorizedInputs categoryCounts printer
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..2a7600e 100644
--- a/src/CCVTAC.Main/PostProcessing/Renamer.fs
+++ b/src/CCVTAC.Main/PostProcessing/Renamer.fs
@@ -4,6 +4,8 @@ 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
@@ -19,50 +21,39 @@ module Renamer =
| "KC" -> NormalizationForm.FormKC
| _ -> NormalizationForm.FormC
- let updateTextViaPatterns isQuietMode (printer: Printer) (sb: StringBuilder) (renamePattern: RenamePattern) =
+ let updateTextViaPattern isQuietMode (printer: Printer) (text: SB) (renamePattern: RenamePattern) : SB =
let regex = Regex renamePattern.RegexPattern
- let matches =
- regex.Matches(sb.ToString())
- |> Rgx.successMatches
- |> Seq.rev
- |> Seq.toList
+ let matches = text.ToString() |> regex.Matches |> Rgx.successMatches |> Seq.rev |> Seq.toList
- if List.isEmpty matches
- then sb
- else
- if not isQuietMode then
- let patternSummary =
- if String.hasText renamePattern.Summary then
- $"\"%s{renamePattern.Summary}\""
- else
- $"`%s{renamePattern.RegexPattern}` (no description)"
+ let printSummary () =
+ let patternDesc = if String.hasText renamePattern.Summary
+ then $"\"%s{renamePattern.Summary}\""
+ else $"`%s{renamePattern.RegexPattern}` (no description)"
+ printer.Debug $"> Rename pattern %s{patternDesc} matched (%d{matches.Length}×)."
- printer.Debug $"Rename pattern %s{patternSummary} matched × %d{matches.Length}."
+ /// Builds replacement text by substituting %s placeholders with captured regex group values.
+ /// Group values are trimmed, and indexing starts from 1 (because group 0 is the full match).
+ let buildReplacementText (renamePattern: RenamePattern) (m: Match) : string =
+ let sb = SB renamePattern.ReplaceWithPattern
- for m in matches do
- sb.Remove(m.Index, m.Length) |> ignore
-
- // Build replacement text by replacing % placeholders with group captures.
- let replacementText =
- m
- |> Rgx.groups
- |> Seq.mapi (fun i _ ->
- let searchFor = sprintf "%%<%d>s" (i + 1)
- let replaceWith =
- // Group 0 is the entire match, so we only want groups starting at 1.
- if i + 1 < m.Groups.Count
- then m.Groups[i + 1].Value.Trim()
- else String.Empty
- (searchFor, replaceWith))
- |> Seq.fold
- (fun (sb': StringBuilder) -> sb'.Replace)
- (StringBuilder renamePattern.ReplaceWithPattern)
- |> _.ToString()
+ for i = 1 to m.Groups.Count - 1 do
+ let placeholder = sprintf "%%<%d>s" i
+ let value = if i < m.Groups.Count then m.Groups[i].Value.Trim() else String.Empty
+ sb.Replace(placeholder, value) |> ignore
+
+ sb.ToString()
- sb.Insert(m.Index, replacementText) |> ignore
+ if List.isEmpty matches then
+ text
+ else
+ if not isQuietMode then printSummary ()
+
+ for m in matches do
+ let replacementText = buildReplacementText renamePattern m
+ text.Remove(m.Index, m.Length).Insert(m.Index, replacementText) |> ignore
- sb
+ text
let run userSettings workingDirectory (printer: Printer) : unit =
let watch = Watch()
@@ -82,8 +73,8 @@ module Renamer =
let newFileName =
userSettings.RenamePatterns
|> List.fold
- (fun (sb: StringBuilder) -> updateTextViaPatterns userSettings.QuietMode printer sb)
- (StringBuilder audioFile.Name)
+ (fun acc -> updateTextViaPattern userSettings.QuietMode printer acc)
+ (SB audioFile.Name)
|> _.ToString()
try
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..777d14f 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,40 @@ 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 {
+ 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 several video IDs.
- /// Any extra, unnecessary files will be ignored.
- /// Any validation errors will be accumulated and return in an Error.
+ /// 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/RenamerTests.fs b/src/CCVTAC.Tests/RenamerTests.fs
index 8453597..5598e05 100644
--- a/src/CCVTAC.Tests/RenamerTests.fs
+++ b/src/CCVTAC.Tests/RenamerTests.fs
@@ -36,11 +36,9 @@ module UpdateTextViaPatternsTests =
let actual =
List.fold
- (fun sb pattern -> Renamer.updateTextViaPatterns true (Printer false) sb pattern)
+ (fun sb pattern -> Renamer.updateTextViaPattern true (Printer false) sb pattern)
fileName
patterns
|> _.ToString()
Assert.Equal(expected, actual)
-
-