Skip to content

Commit

Permalink
Filter Reconciled Files with Regular Expression
Browse files Browse the repository at this point in the history
* Add regex filter options

* Add regex filter implmentation

* Run CI for all branches

* Document -l & -L options

* Refactor Entries

* Move regex filter to framework and add filesystem option

* Update readme for regex filter

* Update future plans

* Simplify reconciliation handler & entries and file system

* Refactor console & add tests

* Fix typo in readme

* Ensure command line options are validated before they're used

* Don't pass unrequired command line args to ReconciliationHandler

* Do not pass command line arguments to ReconciliationHandler

* Add XUnit analyser to all test projects

* Add Sonar analyser all projects

* Control structures should use curly braces (S121)

* Fix markdown tabs

* Fix tabs in Readme

* Add laguage to code blocks

* Fix line loosness

* Fix tabs in markdown

* Further markdown fixes

* Set code block language

* Code quality fixes

* Add code quality badge and links

* Update forthcoming features

* Add license badge

* Add release badge

* Fix example code blocks

* Fix publish paths and ignore output

* Readme improvements
  • Loading branch information
elzik authored May 25, 2022
1 parent aa28fcf commit 67686b4
Show file tree
Hide file tree
Showing 37 changed files with 626 additions and 331 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -335,3 +335,4 @@ ASALocalRun/
/src/Elzik.Mecon.Console/Properties/launchSettings.json
/src/Elzik.Mecon.Console/appsettings.development.json
/Local Build/output
/Build/output
4 changes: 2 additions & 2 deletions Build/publish.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ $runtimes = "linux-x64", "win-x64", "osx-x64"

foreach ($runtime in $runtimes)
{
dotnet publish ..\src\Elzik.Mecon.Console\Elzik.Mecon.Console.csproj `
dotnet publish $PSScriptRoot\..\src\Elzik.Mecon.Console\Elzik.Mecon.Console.csproj `
-p:PublishSingleFile=true `
-r $runtime `
-c Release `
--self-contained true `
-p:PublishTrimmed=true `
-o ".\output\$runtime"
-o $PSScriptRoot\output\$runtime
}
101 changes: 52 additions & 49 deletions README.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ public static IConfigurationBuilder AddCommandLineParser(
this IConfigurationBuilder configurationBuilder,
string[] args)
{
if (!args.Any()) return configurationBuilder;
if (!args.Any())
{
return configurationBuilder;
}

var commandParser = new Parser(setting =>
{
Expand Down
3 changes: 1 addition & 2 deletions src/Elzik.Mecon.Console/CommandLine/Config/ConfigHandler.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Elzik.Mecon.Console.Configuration;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration;

namespace Elzik.Mecon.Console.CommandLine.Config
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,17 @@ private static IEnumerable<string> GetSwitchNames(PropertyInfo propertyInfo)
// https://github.com/commandlineparser/commandline/blob/master/src/CommandLine/OptionAttribute.cs
var longName = optionAttribute.LongName;
if (string.IsNullOrEmpty(longName))
{
longName = propertyInfo.Name;
}

switchNames.Add($"--{longName}");

var shortName = optionAttribute.ShortName;
if (!string.IsNullOrEmpty(shortName))
{
switchNames.Add($"-{shortName}");
}

return switchNames;
}
Expand Down Expand Up @@ -205,7 +209,9 @@ private static bool TryGetMappedOption(
private static bool GetBoolOptionDefault(PropertyInfo propertyInfo)
{
if (propertyInfo.PropertyType != typeof(bool))
{
throw new ArgumentException("Property is not of type bool", nameof(propertyInfo));
}

var optionAttribute = GetOptionAttribute(propertyInfo);

Expand All @@ -217,8 +223,10 @@ private static OptionAttribute GetOptionAttribute(PropertyInfo propertyInfo)
{
var optionAttribute = propertyInfo.GetCustomAttribute<OptionAttribute>();
if (optionAttribute == null)
{
throw new ArgumentException(
$"Property does not have attribute {nameof(OptionAttribute)}", nameof(propertyInfo));
}

return optionAttribute;
}
Expand All @@ -227,8 +235,10 @@ private static OptionConfigurationAttribute GetOptionConfigurationAttribute(Prop
{
var optionConfigAttribute = propertyInfo.GetCustomAttribute<OptionConfigurationAttribute>();
if (optionConfigAttribute == null)
{
throw new ArgumentException(
$"Property does not have attribute {nameof(OptionConfigurationAttribute)}", nameof(propertyInfo));
}

return optionConfigAttribute;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Microsoft.Extensions.Configuration;

namespace Elzik.Mecon.Console.CommandLine.Reconciliation;

public interface IReconciliationHandler
{
void Handle(IConfigurationBuilder configurationBuilder, ReconciliationOptions reconciliationOptions);
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
using Elzik.Mecon.Console.CommandLine;
using Elzik.Mecon.Console.CommandLine.Reconciliation;
using Elzik.Mecon.Framework.Domain;
using Elzik.Mecon.Framework.Domain;

namespace Elzik.Mecon.Console
namespace Elzik.Mecon.Console.CommandLine.Reconciliation
{
internal static class Entries
internal static class MediaEntriesExtensions
{
internal static IEnumerable<MediaEntry> PerformOutputFilters(this IEnumerable<MediaEntry> entries, ReconciliationOptions options)
{
if (options.MissingFromLibrary)
if (options.MissingFromLibrary)
{
entries = entries.WhereNotInPlex();
}

if (options.PresentInLibrary)
if (options.PresentInLibrary)
{
entries = entries.WhereInPlex();
}

return entries;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,39 @@
using Elzik.Mecon.Console.Configuration;
using Elzik.Mecon.Framework.Application;
using Elzik.Mecon.Framework.Application;
using Elzik.Mecon.Framework.Infrastructure.FileSystem;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Nito.AsyncEx;

namespace Elzik.Mecon.Console.CommandLine.Reconciliation
{
public static class ReconciliationHandler

public class ReconciliationHandler : IReconciliationHandler
{
public static void Handle(ConfigurationManager configurationManager, ReconciliationOptions reconciliationOptions)
private readonly IReconciledMedia _reconciledMedia;
private readonly IFileSystem _fileSystem;

public ReconciliationHandler(IReconciledMedia reconciledMedia, IFileSystem fileSystem)
{
_reconciledMedia = reconciledMedia;
_fileSystem = fileSystem;
}

public void Handle(IConfigurationBuilder configurationBuilder, ReconciliationOptions reconciliationOptions)
{
try
{
configurationManager.AddCommandLineParser(Environment.GetCommandLineArgs());

var services = Services.Get(configurationManager);
var directoryDefinition = reconciliationOptions.DirectoryKey == null
? new DirectoryDefinition()
{
SupportedFileExtensions =
(reconciliationOptions.FileExtensions ?? Array.Empty<string>()).ToArray(),
MediaTypes = reconciliationOptions.MediaTypes,
Recurse = reconciliationOptions.Recurse ?? false,
DirectoryFilterRegexPattern = reconciliationOptions.MatchRegex,
DirectoryPath = reconciliationOptions.DirectoryPath
}
: _fileSystem.GetDirectoryDefinition(reconciliationOptions.DirectoryKey);

var reconciledMedia = services.GetRequiredService<IReconciledMedia>();
var entries = reconciliationOptions.DirectoryKey != null
? AsyncContext.Run(() => reconciledMedia.GetMediaEntries(reconciliationOptions.DirectoryKey))
: AsyncContext.Run(() => reconciledMedia.GetMediaEntries(
reconciliationOptions.DirectoryPath, reconciliationOptions.FileExtensions,
reconciliationOptions.Recurse!.Value, reconciliationOptions.MediaTypes));
var entries = AsyncContext.Run(() => _reconciledMedia.GetMediaEntries(directoryDefinition));

entries = entries.PerformOutputFilters(reconciliationOptions);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,20 @@ public class ReconciliationOptions
"If this is omitted, all libraries of all media types will be searched.")]
public IEnumerable<MediaType>? MediaTypes { get; set; }

[Option('L', "missing-from-library", Group = "output filter",
[Option('L', "missing-from-library", Group = "required output filter",
HelpText = "Filter output to only show files missing from media library.")]
public bool MissingFromLibrary { get; set; }

[Option('l', "present-in-library", Group = "output filter",
[Option('l', "present-in-library", Group = "required output filter",
HelpText = "Filter output to only show files present in media library.")]
public bool PresentInLibrary { get; set; }

[Option('f', "regex-match-filter",
HelpText = "Filter output to only show files where the filepath matches a regular expression.")]
public string? MatchRegex { get; set; }

[Option('F', "regex-no-match-filter",
HelpText = "Filter output to only show files where the filepath does not match a regular expression.")]
public string? NoMatchRegex { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
using System.Reflection;
using Elzik.Mecon.Console.CommandLine;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json.Linq;

namespace Elzik.Mecon.Console.Configuration
namespace Elzik.Mecon.Console
{
public static class Configuration
{
public static ConfigurationManager Get()
public static ConfigurationManager Get(string[] args)
{
var config = new ConfigurationManager();

config.AddJsonStream(Assembly.GetCallingAssembly()
config.AddJsonStream(Assembly.GetExecutingAssembly()
.GetManifestResourceStream("Elzik.Mecon.Console.appsettings.Default.json"));
config.AddJsonFile("appsettings.json", true);
#if DEBUG
config.AddJsonFile("appsettings.Development.json", true);
#endif
config.AddEnvironmentVariables("Mecon:");
config.AddCommandLineParser(args);

return config;
}
Expand Down
4 changes: 4 additions & 0 deletions src/Elzik.Mecon.Console/Elzik.Mecon.Console.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="6.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
<PackageReference Include="SonarAnalyzer.CSharp" Version="8.39.0.47922">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
Expand Down
17 changes: 10 additions & 7 deletions src/Elzik.Mecon.Console/Program.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
using CommandLine;
using Elzik.Mecon.Console.CommandLine;
using Elzik.Mecon.Console;
using Elzik.Mecon.Console.CommandLine.Config;
using Elzik.Mecon.Console.CommandLine.Error;
using Elzik.Mecon.Console.CommandLine.Reconciliation;
using Elzik.Mecon.Console.Configuration;

var config = Configuration.Get();
using Microsoft.Extensions.DependencyInjection;

var commandParser = new Parser(setting =>
{
setting.CaseInsensitiveEnumValues = true;
});

var parserResult = commandParser.ParseArguments<ReconciliationOptions, ConfigOptions>(args);

parserResult
.WithParsed<ReconciliationOptions>(options => ReconciliationHandler.Handle(config, options))
.WithParsed<ConfigOptions>(_ => ConfigHandler.Display(config))
.WithParsed<ReconciliationOptions>(options =>
{
var config = Configuration.Get(args);
var services = Services.Get(config);
var reconciliationHandler = services.GetRequiredService<IReconciliationHandler>();
reconciliationHandler.Handle(config, options);
})
.WithParsed<ConfigOptions>(_ => ConfigHandler.Display(Configuration.Get(args)))
.WithNotParsed(errors => ErrorHandler.Display(parserResult, errors));
2 changes: 1 addition & 1 deletion src/Elzik.Mecon.Console/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"profiles": {
"Elzik.Mecon.Console": {
"commandName": "Project",
"commandLineArgs": ""
"commandLineArgs": "-n Films -L -f (?i)^.*sample.*$"
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.IO.Abstractions;
using System.Reflection;
using Elzik.Mecon.Console.CommandLine.Reconciliation;
using Elzik.Mecon.Framework.Application;
using Elzik.Mecon.Framework.Infrastructure.FileSystem.Options;
using Elzik.Mecon.Framework.Infrastructure.Plex;
Expand All @@ -16,7 +17,7 @@
using FileSystem = Elzik.Mecon.Framework.Infrastructure.FileSystem.FileSystem;
using IFileSystem = Elzik.Mecon.Framework.Infrastructure.FileSystem.IFileSystem;

namespace Elzik.Mecon.Console.Configuration
namespace Elzik.Mecon.Console
{
internal static class Services
{
Expand All @@ -25,7 +26,10 @@ public static ServiceProvider Get(ConfigurationManager configurationManager)
var fullAssemblyName = Assembly.GetExecutingAssembly().GetName();

var version = fullAssemblyName.Version;
if (version == null) throw new InvalidOperationException("It was not possible to get the assembly version.");
if (version == null)
{
throw new InvalidOperationException("It was not possible to get the assembly version.");
}

var apiOptions = new ClientOptions
{
Expand All @@ -42,6 +46,7 @@ public static ServiceProvider Get(ConfigurationManager configurationManager)
loggingBuilder.AddConfiguration(configurationManager.GetSection("Logging"));
loggingBuilder.AddConsole();
})
.AddSingleton<IReconciliationHandler, ReconciliationHandler>()
.AddSingleton<IReconciledMedia, MediaReconciler>()
.AddTransient<IFileSystem, FileSystem>()
.AddTransient<IDirectory, DirectoryWrapper>()
Expand Down
10 changes: 6 additions & 4 deletions src/Elzik.Mecon.Console/appsettings.Development.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
"FileSystem": {
"DirectoryDefinitions": {
"Films": {
"DirectoryPath": "\\\\LOCALHOST\\Films",
"SupportedFileExtensions": ["mkv", "ts", "mp4"]
"DirectoryPath": "\\\\LOCALHOST\\Films",
"SupportedFileExtensions": [ "mkv", "ts", "mp4" ],
"DirectoryFilterRegexPattern": "(?i)^(?!.*sample).*$",
"MediaTypes": [ "Movie" ]
}
}
}
},
},
"Plex": {
"BaseUrl": "http://localhost:32400",
"AuthToken": "<plex-token>"
Expand Down
7 changes: 2 additions & 5 deletions src/Elzik.Mecon.Framework/Application/IReconciledMedia.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Elzik.Mecon.Framework.Domain;
using Elzik.Mecon.Framework.Infrastructure.FileSystem;

namespace Elzik.Mecon.Framework.Application
{
public interface IReconciledMedia
{
Task<IEnumerable<MediaEntry>> GetMediaEntries(string directoryDefinitionName);

Task<IEnumerable<MediaEntry>> GetMediaEntries(
string directoryPath,
IEnumerable<string> supportedFileExtensions,
bool recurse,
IEnumerable<MediaType> mediaTypes);
Task<IEnumerable<MediaEntry>> GetMediaEntries(DirectoryDefinition directoryDefinition);
}
}
Loading

0 comments on commit 67686b4

Please sign in to comment.