Skip to content

Commit

Permalink
Merge pull request #6 from elzik/make-file-filter-configurable
Browse files Browse the repository at this point in the history
Make File Filter Configurable
  • Loading branch information
elzik authored Sep 15, 2023
2 parents 9fc1f14 + 840d6bf commit e20dbd8
Show file tree
Hide file tree
Showing 11 changed files with 445 additions and 420 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Ensure that a Markdown file's created date is synchronised with the created-at d

## Usage

Execute FmSync passing a path to a directory which contains files you wish to recursively scan. For any Markdown (*.md) files found, the file's created date will be updated to match that of the `created` date found in the file's Front Matter where one exists.
Execute FmSync passing a path to a directory which contains files you wish to recursively scan. For any Markdown files found, the file's created date will be updated to match that of the `created` date found in the file's Front Matter where one exists.

```powershell
fmsync c:\my-markdownfiles
Expand All @@ -32,3 +32,7 @@ By default, logging is implemented using a single-line simple console logger wit
- Alternatively, `TimeZoneId` can be set to any time zone as specified in the `Timezone` column of [this documentation](https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/default-time-zones). FmSync will then use this timezone when setting the created date on a file.
- If the date given in a file's Front Matter contains a time offset, the TimeZoneId given here will be ignored and the offset given will be taken into account when setting the created date on a file.

### FileSystemOptions

This contains a single setting, `FilenamePattern`, which by default is `*.md`. Only files matching this filter will be acted upon by FmSync.

2 changes: 2 additions & 0 deletions src/Elzik.FmSync.Application/Elzik.FmSync.Application.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="7.0.1" />
<PackageReference Include="SonarAnalyzer.CSharp" Version="8.52.0.60960">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand All @@ -22,6 +23,7 @@

<ItemGroup>
<ProjectReference Include="..\Elzik.FmSync.Domain\Elzik.FmSync.Domain.csproj" />
<ProjectReference Include="..\Elzik.FmSync.Infrastructure\Elzik.FmSync.Infrastructure.csproj" />
</ItemGroup>

</Project>
15 changes: 10 additions & 5 deletions src/Elzik.FmSync.Application/FrontMatterFolderSynchroniser.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.Diagnostics;
using Elzik.FmSync.Infrastructure;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Thinktecture.IO;

namespace Elzik.FmSync;
Expand All @@ -9,22 +11,25 @@ public class FrontMatterFolderSynchroniser : IFrontMatterFolderSynchroniser
private readonly ILogger<FrontMatterFolderSynchroniser> _logger;
private readonly IDirectory _directory;
private readonly IFrontMatterFileSynchroniser _frontMatterFileSynchroniser;
private readonly FileSystemOptions _options;

public FrontMatterFolderSynchroniser(ILogger<FrontMatterFolderSynchroniser> logger,
IDirectory directory, IFrontMatterFileSynchroniser frontMatterFileSynchroniser)
public FrontMatterFolderSynchroniser(ILogger<FrontMatterFolderSynchroniser> logger, IDirectory directory,
IFrontMatterFileSynchroniser frontMatterFileSynchroniser, IOptions<FileSystemOptions> options)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_directory = directory ?? throw new ArgumentNullException(nameof(directory));
_frontMatterFileSynchroniser = frontMatterFileSynchroniser ?? throw new ArgumentNullException(nameof(frontMatterFileSynchroniser));
_frontMatterFileSynchroniser = frontMatterFileSynchroniser
?? throw new ArgumentNullException(nameof(frontMatterFileSynchroniser));
_options = options.Value;
}

public void SyncCreationDates(string directoryPath)
{
var loggingInfo = (StartTime: Stopwatch.GetTimestamp(), EditedCount: 0, ErrorCount: 0,TotalCount: 0);

_logger.LogInformation("Synchronising files in {DirectoryPath}", directoryPath);
_logger.LogInformation("Synchronising {FilenamePattern} files in {DirectoryPath}", _options.FilenamePattern, directoryPath);

var markdownFiles = _directory.EnumerateFiles(directoryPath, "*.md", new EnumerationOptions
var markdownFiles = _directory.EnumerateFiles(directoryPath, _options.FilenamePattern, new EnumerationOptions
{
MatchCasing = MatchCasing.CaseInsensitive,
RecurseSubdirectories = true
Expand Down
1 change: 1 addition & 0 deletions src/Elzik.FmSync.Console/Presentation/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
services.AddTransient<IFrontMatterFileSynchroniser, FrontMatterFileSynchroniser>();
services.AddTransient<IFrontMatterFolderSynchroniser, FrontMatterFolderSynchroniser>();
services.Configure<FrontMatterOptions>(context.Configuration.GetSection("FrontMatterOptions"));
services.Configure<FileSystemOptions>(context.Configuration.GetSection("FileSystemOptions"));
services.AddLogging(loggingBuilder =>
{
loggingBuilder.AddConfiguration(context.Configuration.GetSection("Logging"));
Expand Down
5 changes: 4 additions & 1 deletion src/Elzik.FmSync.Console/appSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,8 @@
},
"FrontMatterOptions": {
"TimeZoneId": ""
}
},
"FileSystemOptions": {
"FilenamePattern": "*.md"
}
}
6 changes: 6 additions & 0 deletions src/Elzik.FmSync.Infrastructure/FileSystemOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Elzik.FmSync.Infrastructure;

public class FileSystemOptions
{
public string? FilenamePattern { get; set; }
}
11 changes: 5 additions & 6 deletions src/Elzik.FmSync.Infrastructure/FrontMatterOptions.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
namespace Elzik.FmSync.Infrastructure
namespace Elzik.FmSync.Infrastructure;

public class FrontMatterOptions
{
public class FrontMatterOptions
{
public string? TimeZoneId { get; set; }
}
}
public string? TimeZoneId { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,134 +6,133 @@
using Thinktecture.IO;
using Xunit;

namespace Elzik.FmSync.Application.Tests.Unit
namespace Elzik.FmSync.Application.Tests.Unit;

public class FrontMatterFileSynchroniserTests
{
public class FrontMatterFileSynchroniserTests
private readonly Fixture _fixture;
private readonly MockLogger<FrontMatterFileSynchroniser> _mockLogger;
private readonly IMarkdownFrontMatter _mockMarkDownFrontMatter;
private readonly IFile _mockFile;
private readonly FrontMatterFileSynchroniser _frontMatterFileSynchroniser;

public FrontMatterFileSynchroniserTests()
{
_mockMarkDownFrontMatter = Substitute.For<IMarkdownFrontMatter>();
_mockFile = Substitute.For<IFile>();
_mockLogger = Substitute.For<MockLogger<FrontMatterFileSynchroniser>>();

_fixture = new Fixture();
_fixture.Register<ILogger<FrontMatterFileSynchroniser>>(() => _mockLogger);
_fixture.Register(() => _mockMarkDownFrontMatter);
_fixture.Register(() => _mockFile);
_frontMatterFileSynchroniser = _fixture.Create<FrontMatterFileSynchroniser>();
}

[Fact]
public void SyncCreationDates_NoMarkDownCreationDate_OnlyLogs()
{
private readonly Fixture _fixture;
private readonly MockLogger<FrontMatterFileSynchroniser> _mockLogger;
private readonly IMarkdownFrontMatter _mockMarkDownFrontMatter;
private readonly IFile _mockFile;
private readonly FrontMatterFileSynchroniser _frontMatterFileSynchroniser;

public FrontMatterFileSynchroniserTests()
{
_mockMarkDownFrontMatter = Substitute.For<IMarkdownFrontMatter>();
_mockFile = Substitute.For<IFile>();
_mockLogger = Substitute.For<MockLogger<FrontMatterFileSynchroniser>>();

_fixture = new Fixture();
_fixture.Register<ILogger<FrontMatterFileSynchroniser>>(() => _mockLogger);
_fixture.Register(() => _mockMarkDownFrontMatter);
_fixture.Register(() => _mockFile);
_frontMatterFileSynchroniser = _fixture.Create<FrontMatterFileSynchroniser>();
}

[Fact]
public void SyncCreationDates_NoMarkDownCreationDate_OnlyLogs()
{
// Arrange
var testFilePath = _fixture.Create<string>();
_mockMarkDownFrontMatter.GetCreatedDateUtc(testFilePath).ReturnsNull();

// Act
_frontMatterFileSynchroniser.SyncCreationDate(testFilePath);

// Assert
_mockLogger.Received(1).Log(LogLevel.Information,
$"{testFilePath} has no Front Matter created date.");
_mockFile.DidNotReceiveWithAnyArgs().SetCreationTimeUtc(default!, default);
}

[Fact]
public void SyncCreationDates_MarkDownAndFileDateEqual_OnlyLogs()
{
// Arrange
var testFilePath = _fixture.Create<string>();
var testDate = _fixture.Create<DateTime>();
_mockFile.GetCreationTimeUtc(testFilePath).Returns(testDate);
_mockMarkDownFrontMatter.GetCreatedDateUtc(testFilePath).Returns(testDate);

// Act
_frontMatterFileSynchroniser.SyncCreationDate(testFilePath);

// Assert
_mockLogger.Received(1).Log(
LogLevel.Information,
Arg.Is<IDictionary<string, object>>(
dict =>
dict.Any(kv => kv.Key == "{OriginalFormat}"
&& (string)kv.Value == "{FilePath} has a file created date ({FileCreatedDate}) " +
"the same as the created date specified in its Front Matter.") &&
dict.Any(kv => kv.Key == "FilePath"
&& (string)kv.Value == testFilePath) &&
dict.Any(kv => kv.Key == "FileCreatedDate"
&& (DateTime)kv.Value == testDate)));
_mockFile.DidNotReceiveWithAnyArgs().SetCreationTimeUtc(default!, default);
}

[Fact]
public void SyncCreationDates_FileDateLaterThanMarkdownDate_LogsAndUpdates()
{
// Arrange
var testFilePath = _fixture.Create<string>();
var testMarkDownDate = _fixture.Create<DateTime>();
var testFileDate = testMarkDownDate.AddTicks(_fixture.Create<long>());
_mockFile.GetCreationTimeUtc(testFilePath).Returns(testFileDate);
_mockMarkDownFrontMatter.GetCreatedDateUtc(testFilePath).Returns(testMarkDownDate);

// Act
_frontMatterFileSynchroniser.SyncCreationDate(testFilePath);

// Assert
_mockLogger.Received(1).Log(
LogLevel.Information,
Arg.Is<IDictionary<string, object>>(
dict =>
dict.Any(kv => kv.Key == "{OriginalFormat}"
&& (string)kv.Value == "{FilePath} has a file created date ({FileCreatedDate}) {RelativeDescription} " +
"than the created date specified in its Front Matter ({FrontMatterCreatedDate})") &&
dict.Any(kv => kv.Key == "FilePath"
&& (string)kv.Value == testFilePath) &&
dict.Any(kv => kv.Key == "FileCreatedDate"
&& (DateTime)kv.Value == testFileDate) &&
dict.Any(kv => kv.Key == "RelativeDescription"
&& (string)kv.Value == "later") &&
dict.Any(kv => kv.Key == "FrontMatterCreatedDate"
&& (DateTime)kv.Value == testMarkDownDate)));
_mockFile.Received(1).SetCreationTimeUtc(testFilePath, testMarkDownDate);
}

[Fact]
public void SyncCreationDates_FileDateEarlierThanMarkdownDate_LogsAndUpdates()
{
// Arrange
var testFilePath = _fixture.Create<string>();
var testMarkDownDate = _fixture.Create<DateTime>();
var testFileDate = testMarkDownDate.Subtract(_fixture.Create<TimeSpan>());
_mockFile.GetCreationTimeUtc(testFilePath).Returns(testFileDate);
_mockMarkDownFrontMatter.GetCreatedDateUtc(testFilePath).Returns(testMarkDownDate);

// Act
_frontMatterFileSynchroniser.SyncCreationDate(testFilePath);

// Assert
_mockLogger.Received(1).Log(
LogLevel.Information,
Arg.Is<IDictionary<string, object>>(
dict =>
dict.Any(kv => kv.Key == "{OriginalFormat}"
&& (string)kv.Value == "{FilePath} has a file created date ({FileCreatedDate}) {RelativeDescription} " +
"than the created date specified in its Front Matter ({FrontMatterCreatedDate})") &&
dict.Any(kv => kv.Key == "FilePath"
&& (string)kv.Value == testFilePath) &&
dict.Any(kv => kv.Key == "FileCreatedDate"
&& (DateTime)kv.Value == testFileDate) &&
dict.Any(kv => kv.Key == "RelativeDescription"
&& (string)kv.Value == "earlier") &&
dict.Any(kv => kv.Key == "FrontMatterCreatedDate"
&& (DateTime)kv.Value == testMarkDownDate)));
_mockFile.Received(1).SetCreationTimeUtc(testFilePath, testMarkDownDate);
}
// Arrange
var testFilePath = _fixture.Create<string>();
_mockMarkDownFrontMatter.GetCreatedDateUtc(testFilePath).ReturnsNull();

// Act
_frontMatterFileSynchroniser.SyncCreationDate(testFilePath);

// Assert
_mockLogger.Received(1).Log(LogLevel.Information,
$"{testFilePath} has no Front Matter created date.");
_mockFile.DidNotReceiveWithAnyArgs().SetCreationTimeUtc(default!, default);
}

[Fact]
public void SyncCreationDates_MarkDownAndFileDateEqual_OnlyLogs()
{
// Arrange
var testFilePath = _fixture.Create<string>();
var testDate = _fixture.Create<DateTime>();
_mockFile.GetCreationTimeUtc(testFilePath).Returns(testDate);
_mockMarkDownFrontMatter.GetCreatedDateUtc(testFilePath).Returns(testDate);

// Act
_frontMatterFileSynchroniser.SyncCreationDate(testFilePath);

// Assert
_mockLogger.Received(1).Log(
LogLevel.Information,
Arg.Is<IDictionary<string, object>>(
dict =>
dict.Any(kv => kv.Key == "{OriginalFormat}"
&& (string)kv.Value == "{FilePath} has a file created date ({FileCreatedDate}) " +
"the same as the created date specified in its Front Matter.") &&
dict.Any(kv => kv.Key == "FilePath"
&& (string)kv.Value == testFilePath) &&
dict.Any(kv => kv.Key == "FileCreatedDate"
&& (DateTime)kv.Value == testDate)));
_mockFile.DidNotReceiveWithAnyArgs().SetCreationTimeUtc(default!, default);
}

[Fact]
public void SyncCreationDates_FileDateLaterThanMarkdownDate_LogsAndUpdates()
{
// Arrange
var testFilePath = _fixture.Create<string>();
var testMarkDownDate = _fixture.Create<DateTime>();
var testFileDate = testMarkDownDate.AddTicks(_fixture.Create<long>());
_mockFile.GetCreationTimeUtc(testFilePath).Returns(testFileDate);
_mockMarkDownFrontMatter.GetCreatedDateUtc(testFilePath).Returns(testMarkDownDate);

// Act
_frontMatterFileSynchroniser.SyncCreationDate(testFilePath);

// Assert
_mockLogger.Received(1).Log(
LogLevel.Information,
Arg.Is<IDictionary<string, object>>(
dict =>
dict.Any(kv => kv.Key == "{OriginalFormat}"
&& (string)kv.Value == "{FilePath} has a file created date ({FileCreatedDate}) {RelativeDescription} " +
"than the created date specified in its Front Matter ({FrontMatterCreatedDate})") &&
dict.Any(kv => kv.Key == "FilePath"
&& (string)kv.Value == testFilePath) &&
dict.Any(kv => kv.Key == "FileCreatedDate"
&& (DateTime)kv.Value == testFileDate) &&
dict.Any(kv => kv.Key == "RelativeDescription"
&& (string)kv.Value == "later") &&
dict.Any(kv => kv.Key == "FrontMatterCreatedDate"
&& (DateTime)kv.Value == testMarkDownDate)));
_mockFile.Received(1).SetCreationTimeUtc(testFilePath, testMarkDownDate);
}

[Fact]
public void SyncCreationDates_FileDateEarlierThanMarkdownDate_LogsAndUpdates()
{
// Arrange
var testFilePath = _fixture.Create<string>();
var testMarkDownDate = _fixture.Create<DateTime>();
var testFileDate = testMarkDownDate.Subtract(_fixture.Create<TimeSpan>());
_mockFile.GetCreationTimeUtc(testFilePath).Returns(testFileDate);
_mockMarkDownFrontMatter.GetCreatedDateUtc(testFilePath).Returns(testMarkDownDate);

// Act
_frontMatterFileSynchroniser.SyncCreationDate(testFilePath);

// Assert
_mockLogger.Received(1).Log(
LogLevel.Information,
Arg.Is<IDictionary<string, object>>(
dict =>
dict.Any(kv => kv.Key == "{OriginalFormat}"
&& (string)kv.Value == "{FilePath} has a file created date ({FileCreatedDate}) {RelativeDescription} " +
"than the created date specified in its Front Matter ({FrontMatterCreatedDate})") &&
dict.Any(kv => kv.Key == "FilePath"
&& (string)kv.Value == testFilePath) &&
dict.Any(kv => kv.Key == "FileCreatedDate"
&& (DateTime)kv.Value == testFileDate) &&
dict.Any(kv => kv.Key == "RelativeDescription"
&& (string)kv.Value == "earlier") &&
dict.Any(kv => kv.Key == "FrontMatterCreatedDate"
&& (DateTime)kv.Value == testMarkDownDate)));
_mockFile.Received(1).SetCreationTimeUtc(testFilePath, testMarkDownDate);
}
}
Loading

0 comments on commit e20dbd8

Please sign in to comment.