Skip to content

Commit

Permalink
Allow multiple operation flags (#74)
Browse files Browse the repository at this point in the history
* Initial working rough draft

* Code cleanup

* Code cleanup
  • Loading branch information
codeconscious authored May 28, 2024
1 parent 479a0a2 commit b20be96
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 93 deletions.
183 changes: 104 additions & 79 deletions AudioTagger.Console/OperationLibrary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,85 +5,85 @@ namespace AudioTagger.Console;

internal static class OperationLibrary
{
internal static readonly IReadOnlyList<Operation> Operations = new List<Operation>()
{
new(
["-v", "--view"],
"View tag data.",
new TagViewer()
),
new(
["-vs", "--view-summary"],
"View a tag data summary.",
new TagViewerSummary()
),
new(
["-u", "--update"],
"Update tag data using filename patterns.",
new TagUpdater()
),
new(
["-u1", "--update-single"],
"Update a single tag in multiple files to a single, manually-specified value.",
new TagUpdaterSingle()
),
new(
["-ug", "--update-genres"],
"Update the genres in all files automatically using the artist-genre data in the settings.",
new TagUpdaterGenreOnly()
),
new(
["-um", "--update-multiple"],
"Update a single tag with multiple values for multiple files.",
new TagUpdaterMultiple()),
new(
["-uy", "--update-year"],
"Update the year using media files' own dates of creation. (Must do before other updates, lest the creation date be modified.)",
new TagUpdaterYearOnly()
),
new(
["-urt", "--reverse-track-numbers"],
"Reverse the track numbers of the given files.",
new TagUpdaterReverseTrackNumbers()
),
new(
["-r", "--rename"],
"Rename and reorganize files into folders based on tag data.",
new MediaFileRenamer()
),
new(
["-d", "--duplicates"],
"List tracks with identical artists and titles. No files are modified or deleted.",
new TagDuplicateFinder()
),
new(
["-s", "--stats"],
"Display file statistics based on tag data.",
new TagStats()
),
// new(
// ["-n", "--normalize", "--replaygain"],
// "Apply track normalization.",
// new Normalization()),
new(
["-g", "--genres"],
"Save the primary genre for each artist to a genre file.",
new TagGenreExtractor()),
new(
["-p", "--parse"],
"Get a single tag value by parsing the data of another (generally Comments).",
new TagParser()),
new(
["--scan"],
"Ad-hoc maintenance scanning work. (Not intended for normal use.)",
new TagScanner(),
isHidden: true),
new(
["--cache-tags"],
"Cache files' tag data locally to a JSON file whose path is specified in the settings. (Eventually, this will be helpful in speeding up certain operations.)",
new TagCacher(),
isHidden: true),
};
internal static readonly IReadOnlyList<Operation> Operations =
[
new(
["-v", "--view"],
"View tag data.",
new TagViewer()
),
new(
["-vs", "--view-summary"],
"View a tag data summary.",
new TagViewerSummary()
),
new(
["-u", "--update"],
"Update tag data using filename patterns.",
new TagUpdater()
),
new(
["-u1", "--update-single"],
"Update a single tag in multiple files to a single, manually-specified value.",
new TagUpdaterSingle()
),
new(
["-ug", "--update-genres"],
"Update the genres in all files automatically using the artist-genre data in the settings.",
new TagUpdaterGenreOnly()
),
new(
["-um", "--update-multiple"],
"Update a single tag with multiple values for multiple files.",
new TagUpdaterMultiple()),
new(
["-uy", "--update-year"],
"Update the year using media files' own dates of creation. (Must do before other updates, lest the creation date be modified.)",
new TagUpdaterYearOnly()
),
new(
["-urt", "--reverse-track-numbers"],
"Reverse the track numbers of the given files.",
new TagUpdaterReverseTrackNumbers()
),
new(
["-r", "--rename"],
"Rename and reorganize files into folders based on tag data.",
new MediaFileRenamer()
),
new(
["-d", "--duplicates"],
"List tracks with identical artists and titles. No files are modified or deleted.",
new TagDuplicateFinder()
),
new(
["-s", "--stats"],
"Display file statistics based on tag data.",
new TagStats()
),
// new(
// ["-n", "--normalize", "--replaygain"],
// "Apply track normalization.",
// new Normalization()),
new(
["-g", "--genres"],
"Save the primary genre for each artist to a genre file.",
new TagGenreExtractor()),
new(
["-p", "--parse"],
"Get a single tag value by parsing the data of another (generally Comments).",
new TagParser()),
new(
["--scan"],
"Ad-hoc maintenance scanning work. (Not intended for normal use.)",
new TagScanner(),
isHidden: true),
new(
["--cache-tags"],
"Cache files' tag data locally to a JSON file whose path is specified in the settings. (Eventually, this will be helpful in speeding up certain operations.)",
new TagCacher(),
isHidden: true),
];

public static Dictionary<string, string> GenerateHelpTextPairs(bool includeHidden)
{
Expand All @@ -108,6 +108,31 @@ public static Result<IPathOperation> GetPathOperation(string requestedOperation)
: Result.Ok(maybeOperation);
}

public static Result<ImmutableList<IPathOperation>> GetPathOperations(
IEnumerable<string> requestedOperations)
{
var successes = new List<IPathOperation>();
var failures = new List<string>();

Result<IPathOperation> currentResult;
foreach (var operation in requestedOperations)
{
currentResult = GetPathOperation(operation);
if (currentResult.IsSuccess)
{
successes.Add(currentResult.Value);
}
else
{
failures.Add(currentResult.Errors.First().Message);
}
}

return failures.Count == 0
? Result.Ok(successes.ToImmutableList())
: Result.Fail(string.Join(Environment.NewLine, failures));
}

internal sealed class Operation
{
public required OperationFlags Commands { get; init; }
Expand Down
34 changes: 20 additions & 14 deletions AudioTagger.Console/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,17 @@ private static void Run(string[] args, IPrinter printer)
version: SettingsService.Id3v2Version.TwoPoint3,
forceAsDefault: true);

var (operationArg, pathArgs) = (args[0], args[1..].Distinct().ToImmutableList());
var operationArgs = args.TakeWhile(a => a.StartsWith('-')).ToImmutableArray();
var pathArgs = args[operationArgs.Length..];

var operationResult = OperationFactory(operationArg);
var operationResult = OperationFactory(operationArgs);
if (operationResult.IsFailed)
{
readSettingsResult.Errors.ForEach(x => printer.Error(x.Message));
PrintInstructions(printer);
return;
}
IPathOperation operation = operationResult.Value;
var operations = operationResult.Value;

var (validPaths, invalidPaths) = CheckPaths(pathArgs);

Expand All @@ -69,7 +70,7 @@ private static void Run(string[] args, IPrinter printer)
{
try
{
ProcessPath(path, operation, settings, printer);
ProcessPath(path, operations, settings, printer);
}
catch (Exception ex)
{
Expand All @@ -80,7 +81,7 @@ private static void Run(string[] args, IPrinter printer)

private static void ProcessPath(
string path,
IPathOperation operation,
ImmutableList<IPathOperation> operations,
Settings settings,
IPrinter printer)
{
Expand All @@ -99,7 +100,7 @@ private static void ProcessPath(
ImmutableArray<string> fileNames = fileNameResult.Value;
if (fileNames.IsEmpty)
{
printer.Warning("No files were found in \"{path}\".");
printer.Warning($"No files were found in \"{path}\".");
return;
}

Expand All @@ -122,7 +123,10 @@ private static void ProcessPath(

try
{
operation.Start(mediaFiles, new DirectoryInfo(path), settings, printer);
foreach (var operation in operations)
{
operation.Start(mediaFiles, new DirectoryInfo(path), settings, printer);
}
}
catch (Exception ex)
{
Expand All @@ -132,7 +136,8 @@ private static void ProcessPath(
}
}

private static (List<MediaFile>, List<string>) ReadTagsShowingProgress(ICollection<string> fileNames)
private static (List<MediaFile>, List<string>) ReadTagsShowingProgress(
ICollection<string> fileNames)
{
List<MediaFile> mediaFiles = new(fileNames.Count);
List<string> tagReadErrors = [];
Expand Down Expand Up @@ -173,24 +178,25 @@ private static (List<MediaFile>, List<string>) ReadTagsShowingProgress(ICollecti
/// <summary>
/// Get the correct operation from the argument passed in.
/// </summary>
/// <param name="modeArg">The argument passed from the console.</param>
/// <param name="modeArgs">The argument passed from the console.</param>
/// <returns>A class for performing operations on files.</returns>
private static Result<IPathOperation> OperationFactory(string modeArg)
private static Result<ImmutableList<IPathOperation>> OperationFactory(IEnumerable<string> modeArgs)
{
return OperationLibrary.GetPathOperation(modeArg);
return OperationLibrary.GetPathOperations(modeArgs);
}

private static void PrintInstructions(IPrinter printer)
{
printer.Print("ID3 audio tagger utilities.");
printer.Print("Usage: ccaudiotagger [COMMAND] [FILES/DIRECTORIES]...", 0, 1, string.Empty);
printer.Print("Supply one command, followed by one or more files or directories to process.", 0, 1, string.Empty);
printer.Print("Usage: dotnet run -- [COMMAND(s)] [FILES/DIRECTORIES]...", 0, 1, string.Empty);
printer.Print("Supply one or more commands, followed by one or more files or directories to process.", 0, 1, string.Empty);

Table table = new();
table.AddColumns("Commands", "Descriptions");
table.Border = TableBorder.Rounded;

foreach (KeyValuePair<string, string> pair in OperationLibrary.GenerateHelpTextPairs(includeHidden: false))
var helpPairs = OperationLibrary.GenerateHelpTextPairs(includeHidden: false);
foreach (KeyValuePair<string, string> pair in helpPairs)
{
table.AddRow(pair.Key, pair.Value);
}
Expand Down

0 comments on commit b20be96

Please sign in to comment.