File naming is Configuration driven: Configuration, AudioFileStorageExt, Templates, TemplateTags,

This commit is contained in:
Robert McRackan 2021-10-26 16:18:27 -04:00
parent a3d38e082d
commit 35f8c05106
12 changed files with 422 additions and 21 deletions

View File

@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net5.0</TargetFramework> <TargetFramework>net5.0</TargetFramework>
<Version>6.2.8.3</Version> <Version>6.2.9.1</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -59,6 +59,7 @@ namespace AppScaffolding
Migrations.migrate_to_v5_7_1(config); Migrations.migrate_to_v5_7_1(config);
Migrations.migrate_to_v6_1_2(config); Migrations.migrate_to_v6_1_2(config);
Migrations.migrate_to_v6_2_0(config); Migrations.migrate_to_v6_2_0(config);
Migrations.migrate_to_v6_2_9(config);
} }
/// <summary>Initialize logging. Run after migration</summary> /// <summary>Initialize logging. Run after migration</summary>
@ -345,5 +346,18 @@ namespace AppScaffolding
if (!config.Exists(nameof(config.SplitFilesByChapter))) if (!config.Exists(nameof(config.SplitFilesByChapter)))
config.SplitFilesByChapter = false; config.SplitFilesByChapter = false;
} }
// add file naming templates
public static void migrate_to_v6_2_9(Configuration config)
{
if (!config.Exists(nameof(config.FolderTemplate)))
config.FolderTemplate = Templates.Folder.DefaultTemplate;
if (!config.Exists(nameof(config.FileTemplate)))
config.FileTemplate = Templates.File.DefaultTemplate;
if (!config.Exists(nameof(config.ChapterFileTemplate)))
config.ChapterFileTemplate = Templates.ChapterFile.DefaultTemplate;
}
} }
} }

View File

@ -11,9 +11,8 @@ namespace FileLiberator
{ {
public static class AudioFileStorageExt public static class AudioFileStorageExt
{ {
private static string TEMP_SINGLE_TEMPLATE { get; } = "<title> [<id>]"; private static void AddParameterReplacement(this FileTemplate fileTemplate, TemplateTags templateTags, object value)
private static string TEMP_DIR_TEMPLATE { get; } = "<title short> [<id>]"; => fileTemplate.AddParameterReplacement(templateTags.TagName, value);
private static string TEMP_MULTI_TEMPLATE { get; } = "<title> [<id>] - <ch# 0> - <ch title>";
internal class MultipartRenamer internal class MultipartRenamer
{ {
@ -22,16 +21,16 @@ namespace FileLiberator
public MultipartRenamer(LibraryBook libraryBook) => this.libraryBook = libraryBook; public MultipartRenamer(LibraryBook libraryBook) => this.libraryBook = libraryBook;
internal string MultipartFilename(string outputFileName, int partsPosition, int partsTotal, AAXClean.NewSplitCallback newSplitCallback) internal string MultipartFilename(string outputFileName, int partsPosition, int partsTotal, AAXClean.NewSplitCallback newSplitCallback)
=> MultipartFilename(TEMP_MULTI_TEMPLATE, AudibleFileStorage.DecryptInProgressDirectory, Path.GetExtension(outputFileName), partsPosition, partsTotal, newSplitCallback?.Chapter?.Title ?? ""); => MultipartFilename(Configuration.Instance.ChapterFileTemplate, AudibleFileStorage.DecryptInProgressDirectory, Path.GetExtension(outputFileName), partsPosition, partsTotal, newSplitCallback?.Chapter?.Title ?? "");
internal string MultipartFilename(string template, string fullDirPath, string extension, int partsPosition, int partsTotal, string chapterTitle) internal string MultipartFilename(string template, string fullDirPath, string extension, int partsPosition, int partsTotal, string chapterTitle)
{ {
var fileTemplate = GetFileTemplateSingle(template, libraryBook, fullDirPath, extension); var fileTemplate = GetFileTemplateSingle(template, libraryBook, fullDirPath, extension);
fileTemplate.AddParameterReplacement("ch count", partsTotal.ToString()); fileTemplate.AddParameterReplacement(TemplateTags.ChCount, partsTotal);
fileTemplate.AddParameterReplacement("ch#", partsPosition.ToString()); fileTemplate.AddParameterReplacement(TemplateTags.ChNumber, partsPosition);
fileTemplate.AddParameterReplacement("ch# 0", FileUtility.GetSequenceFormatted(partsPosition, partsTotal)); fileTemplate.AddParameterReplacement(TemplateTags.ChNumber0, FileUtility.GetSequenceFormatted(partsPosition, partsTotal));
fileTemplate.AddParameterReplacement("ch title", chapterTitle); fileTemplate.AddParameterReplacement(TemplateTags.ChTitle, chapterTitle);
return fileTemplate.GetFilePath(); return fileTemplate.GetFilePath();
} }
@ -47,11 +46,11 @@ namespace FileLiberator
=> GetCustomDirFilename(_, libraryBook, AudibleFileStorage.BooksDirectory, extension); => GetCustomDirFilename(_, libraryBook, AudibleFileStorage.BooksDirectory, extension);
public static string GetDestinationDirectory(this AudioFileStorage _, LibraryBook libraryBook) public static string GetDestinationDirectory(this AudioFileStorage _, LibraryBook libraryBook)
=> GetFileTemplateSingle(TEMP_DIR_TEMPLATE, libraryBook, AudibleFileStorage.BooksDirectory, null) => GetFileTemplateSingle(Configuration.Instance.FolderTemplate, libraryBook, AudibleFileStorage.BooksDirectory, null)
.GetFilePath(); .GetFilePath();
public static string GetCustomDirFilename(this AudioFileStorage _, LibraryBook libraryBook, string dirFullPath, string extension) public static string GetCustomDirFilename(this AudioFileStorage _, LibraryBook libraryBook, string dirFullPath, string extension)
=> GetFileTemplateSingle(TEMP_SINGLE_TEMPLATE, libraryBook, dirFullPath, extension) => GetFileTemplateSingle(Configuration.Instance.FolderTemplate, libraryBook, dirFullPath, extension)
.GetFilePath(); .GetFilePath();
internal static FileTemplate GetFileTemplateSingle(string template, LibraryBook libraryBook, string dirFullPath, string extension) internal static FileTemplate GetFileTemplateSingle(string template, LibraryBook libraryBook, string dirFullPath, string extension)
@ -65,9 +64,17 @@ namespace FileLiberator
var title = libraryBook.Book.Title ?? ""; var title = libraryBook.Book.Title ?? "";
fileTemplate.AddParameterReplacement("title", title); fileTemplate.AddParameterReplacement(TemplateTags.Id, libraryBook.Book.AudibleProductId);
fileTemplate.AddParameterReplacement("title short", title.IndexOf(':') < 1 ? title : title.Substring(0, title.IndexOf(':'))); fileTemplate.AddParameterReplacement(TemplateTags.Title, title);
fileTemplate.AddParameterReplacement("id", libraryBook.Book.AudibleProductId); fileTemplate.AddParameterReplacement(TemplateTags.TitleShort, title.IndexOf(':') < 1 ? title : title.Substring(0, title.IndexOf(':')));
fileTemplate.AddParameterReplacement(TemplateTags.Author, libraryBook.Book.AuthorNames);
fileTemplate.AddParameterReplacement(TemplateTags.FirstAuthor, libraryBook.Book.Authors.FirstOrDefault()?.Name);
fileTemplate.AddParameterReplacement(TemplateTags.Narrator, libraryBook.Book.NarratorNames);
fileTemplate.AddParameterReplacement(TemplateTags.FirstNarrator, libraryBook.Book.Narrators.FirstOrDefault()?.Name);
var seriesLink = libraryBook.Book.SeriesLink.FirstOrDefault();
fileTemplate.AddParameterReplacement(TemplateTags.SeriesName, seriesLink?.Series.Name);
fileTemplate.AddParameterReplacement(TemplateTags.SeriesNumber, seriesLink?.Order);
return fileTemplate; return fileTemplate;
} }

View File

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using Dinah.Core; using Dinah.Core;
@ -16,10 +15,12 @@ namespace FileManager
public FileTemplate(string template) => Template = ArgumentValidator.EnsureNotNullOrWhiteSpace(template, nameof(template)); public FileTemplate(string template) => Template = ArgumentValidator.EnsureNotNullOrWhiteSpace(template, nameof(template));
/// <summary>Optional step 1: Replace html-styled template tags with parameters. Eg {"name", "Bill Gates"} => /&lt;name&gt;/ => /Bill Gates/</summary> /// <summary>Optional step 1: Replace html-styled template tags with parameters. Eg {"name", "Bill Gates"} => /&lt;name&gt;/ => /Bill Gates/</summary>
public Dictionary<string, string> ParameterReplacements { get; } = new Dictionary<string, string>(); public Dictionary<string, object> ParameterReplacements { get; } = new Dictionary<string, object>();
/// <summary>Convenience method</summary> /// <summary>Convenience method</summary>
public void AddParameterReplacement(string key ,string value) => ParameterReplacements.Add(key, value); public void AddParameterReplacement(string key, object value)
// using .Add() instead of "[key] = value" will make unintended overwriting throw exception
=> ParameterReplacements.Add(key, value);
/// <summary>If set, truncate each parameter replacement to this many characters. Default 50</summary> /// <summary>If set, truncate each parameter replacement to this many characters. Default 50</summary>
public int? ParameterMaxSize { get; set; } = 50; public int? ParameterMaxSize { get; set; } = 50;
@ -38,14 +39,14 @@ namespace FileManager
return FileUtility.GetValidFilename(filename, IllegalCharacterReplacements); return FileUtility.GetValidFilename(filename, IllegalCharacterReplacements);
} }
private string formatKey(string key) private static string formatKey(string key)
=> key => key
.Replace("<", "") .Replace("<", "")
.Replace(">", ""); .Replace(">", "");
private string formatValue(string value) private string formatValue(object value)
=> ParameterMaxSize.HasValue && ParameterMaxSize.Value > 0 => ParameterMaxSize.HasValue && ParameterMaxSize.Value > 0
? value?.Truncate(ParameterMaxSize.Value) ? value?.ToString().Truncate(ParameterMaxSize.Value)
: value; : value?.ToString();
} }
} }

View File

@ -62,6 +62,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileLiberator.Tests", "_Tes
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileManager.Tests", "_Tests\FileManager.Tests\FileManager.Tests.csproj", "{F2E04270-4551-41C4-99FF-E7125BED708C}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileManager.Tests", "_Tests\FileManager.Tests\FileManager.Tests.csproj", "{F2E04270-4551-41C4-99FF-E7125BED708C}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibationFileManager.Tests", "_Tests\LibationFileManager.Tests\LibationFileManager.Tests.csproj", "{EB781571-8548-477E-82AD-FB9FAB548D2F}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -136,6 +138,10 @@ Global
{F2E04270-4551-41C4-99FF-E7125BED708C}.Debug|Any CPU.Build.0 = Debug|Any CPU {F2E04270-4551-41C4-99FF-E7125BED708C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F2E04270-4551-41C4-99FF-E7125BED708C}.Release|Any CPU.ActiveCfg = Release|Any CPU {F2E04270-4551-41C4-99FF-E7125BED708C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F2E04270-4551-41C4-99FF-E7125BED708C}.Release|Any CPU.Build.0 = Release|Any CPU {F2E04270-4551-41C4-99FF-E7125BED708C}.Release|Any CPU.Build.0 = Release|Any CPU
{EB781571-8548-477E-82AD-FB9FAB548D2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EB781571-8548-477E-82AD-FB9FAB548D2F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EB781571-8548-477E-82AD-FB9FAB548D2F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EB781571-8548-477E-82AD-FB9FAB548D2F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -158,6 +164,7 @@ Global
{788294BE-0D8E-40D4-9CEE-67896FBB52CE} = {67E66E82-5532-4440-AFB3-9FB1DF9DEF53} {788294BE-0D8E-40D4-9CEE-67896FBB52CE} = {67E66E82-5532-4440-AFB3-9FB1DF9DEF53}
{5B8FC827-BF58-4CB1-A59E-BDEB9C62A05E} = {67E66E82-5532-4440-AFB3-9FB1DF9DEF53} {5B8FC827-BF58-4CB1-A59E-BDEB9C62A05E} = {67E66E82-5532-4440-AFB3-9FB1DF9DEF53}
{F2E04270-4551-41C4-99FF-E7125BED708C} = {67E66E82-5532-4440-AFB3-9FB1DF9DEF53} {F2E04270-4551-41C4-99FF-E7125BED708C} = {67E66E82-5532-4440-AFB3-9FB1DF9DEF53}
{EB781571-8548-477E-82AD-FB9FAB548D2F} = {67E66E82-5532-4440-AFB3-9FB1DF9DEF53}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {615E00ED-BAEF-4E8E-A92A-9B82D87942A9} SolutionGuid = {615E00ED-BAEF-4E8E-A92A-9B82D87942A9}

View File

@ -145,8 +145,40 @@ namespace LibationFileManager
{ {
get => persistentDictionary.GetNonString<bool>(nameof(DownloadEpisodes)); get => persistentDictionary.GetNonString<bool>(nameof(DownloadEpisodes));
set => persistentDictionary.SetNonString(nameof(DownloadEpisodes), value); set => persistentDictionary.SetNonString(nameof(DownloadEpisodes), value);
}
[Description("How to format the folders in which files will be saved")]
public string FolderTemplate
{
get => getTemplate(nameof(FolderTemplate), Templates.Folder);
set => setTemplate(nameof(FolderTemplate), Templates.Folder, value);
} }
[Description("How to format the saved pdf and audio files")]
public string FileTemplate
{
get => getTemplate(nameof(FileTemplate), Templates.File);
set => setTemplate(nameof(FileTemplate), Templates.File, value);
}
[Description("How to format the saved audio files which are split by chapters")]
public string ChapterFileTemplate
{
get => getTemplate(nameof(ChapterFileTemplate), Templates.ChapterFile);
set => setTemplate(nameof(ChapterFileTemplate), Templates.ChapterFile, value);
}
private string getTemplate(string settingName, Templates templ)
{
var value = persistentDictionary.GetString(settingName).Trim();
return templ.IsValid(value) ? value : templ.DefaultTemplate;
}
private void setTemplate(string settingName, Templates templ, string newValue)
{
var template = newValue.Trim();
if (templ.IsValid(template))
persistentDictionary.SetString(settingName, template);
}
#endregion #endregion
#region known directories #region known directories

View File

@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dinah.Core;
namespace LibationFileManager
{
public sealed class TemplateTags : Enumeration<TemplateTags>
{
public string TagName => DisplayName;
public string Description { get; }
public bool IsChapterOnly { get; }
private static int value = 0;
private TemplateTags(string tagName, string description, bool isChapterOnly = false) : base(value++, tagName)
{
Description = description;
IsChapterOnly = isChapterOnly;
}
public static TemplateTags Id { get; } = new TemplateTags("id", "Audible ID");
public static TemplateTags Title { get; } = new TemplateTags("title", "Full title");
public static TemplateTags TitleShort { get; } = new TemplateTags("title short", "Title. Stop at first colon");
public static TemplateTags Author { get; } = new TemplateTags("author", "Author(s)");
public static TemplateTags FirstAuthor { get; } = new TemplateTags("first author", "First author");
public static TemplateTags Narrator { get; } = new TemplateTags("narrator", "Narrator(s)");
public static TemplateTags FirstNarrator { get; } = new TemplateTags("first narrator", "First narrator");
public static TemplateTags SeriesName { get; } = new TemplateTags("series name", "Name of series");
// can't also have a leading zeros version. Too many weird edge cases. Eg: "1-4"
public static TemplateTags SeriesNumber { get; } = new TemplateTags("series#", "Number order in series");
public static TemplateTags ChCount { get; } = new TemplateTags("ch count", "Number of chapters", true);
public static TemplateTags ChTitle { get; } = new TemplateTags("ch title", "Chapter title", true);
public static TemplateTags ChNumber { get; } = new TemplateTags("ch#", "Chapter number", true);
public static TemplateTags ChNumber0 { get; } = new TemplateTags("ch# 0", "Chapter number with leading zeros", true);
}
}

View File

@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace LibationFileManager
{
public abstract class Templates
{
public static Templates Folder { get; } = new FolderTemplate();
public static Templates File { get; } = new FileTemplate();
public static Templates ChapterFile { get; } = new ChapterFileTemplate();
public abstract string DefaultTemplate { get; }
public abstract bool IsValid(string template);
public abstract bool IsRecommended(string template);
public abstract int TagCount(string template);
public static bool ContainsChapterOnlyTags(string template)
=> TemplateTags.GetAll()
.Where(t => t.IsChapterOnly)
.Any(t => ContainsTag(template, t.TagName));
public static bool ContainsTag(string template, string tag) => template.Contains($"<{tag}>");
protected static bool fileIsValid(string template)
// File name only; not path. all other path chars are valid enough to pass this check and will be handled on final save.
// null is invalid. whitespace is valid but not recommended
=> template is not null
&& !template.Contains(':')
&& !template.Contains(System.IO.Path.DirectorySeparatorChar)
&& !template.Contains(System.IO.Path.AltDirectorySeparatorChar);
protected bool isRecommended(string template, bool isChapter)
=> IsValid(template)
&& !string.IsNullOrWhiteSpace(template)
&& TagCount(template) > 0
&& ContainsChapterOnlyTags(template) == isChapter;
protected static int tagCount(string template, Func<TemplateTags, bool> func)
=> TemplateTags.GetAll()
.Where(func)
// for <id><id> == 1, use:
// .Count(t => template.Contains($"<{t.TagName}>"))
// .Sum() impl: <id><id> == 2
.Sum(t => template.Split($"<{t.TagName}>").Length - 1);
private class FolderTemplate : Templates
{
public override string DefaultTemplate { get; } = "<title short> [<id>]";
public override bool IsValid(string template)
// must be relative. no colons. all other path chars are valid enough to pass this check and will be handled on final save.
// null is invalid. whitespace is valid but not recommended
=> template is not null
&& !template.Contains(':');
public override bool IsRecommended(string template) => isRecommended(template, false);
public override int TagCount(string template) => tagCount(template, t => !t.IsChapterOnly);
}
private class FileTemplate : Templates
{
public override string DefaultTemplate { get; } = "<title> [<id>]";
public override bool IsValid(string template) => fileIsValid(template);
public override bool IsRecommended(string template) => isRecommended(template, false);
public override int TagCount(string template) => tagCount(template, t => !t.IsChapterOnly);
}
private class ChapterFileTemplate : Templates
{
public override string DefaultTemplate { get; } = "<title> [<id>] - <ch# 0> - <ch title>";
public override bool IsValid(string template) => fileIsValid(template);
public override bool IsRecommended(string template)
=> isRecommended(template, true)
// recommended to incl. <ch#> or <ch# 0>
&& (ContainsTag(template, TemplateTags.ChNumber.TagName) || ContainsTag(template, TemplateTags.ChNumber0.TagName));
public override int TagCount(string template) => tagCount(template, t => true);
}
}
}

View File

@ -0,0 +1 @@
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(nameof(LibationFileManager) + ".Tests")]

View File

@ -35,6 +35,7 @@ namespace FileUtilityTests
[DataRow(@"a\b:c\d.txt", "ZZZ", @"a\bZZZc\d.txt")] [DataRow(@"a\b:c\d.txt", "ZZZ", @"a\bZZZc\d.txt")]
// remove empty directories // remove empty directories
[DataRow(@"C:\a\\\b\c\\\d.txt", "ZZZ", @"C:\a\b\c\d.txt")] [DataRow(@"C:\a\\\b\c\\\d.txt", "ZZZ", @"C:\a\b\c\d.txt")]
[DataRow(@"C:\""foo\<id>", "ZZZ", @"C:\ZZZfoo\ZZZidZZZ")]
public void Tests(string inStr, string replacement, string outStr) => Assert.AreEqual(outStr, FileUtility.GetSafePath(inStr, replacement)); public void Tests(string inStr, string replacement, string outStr) => Assert.AreEqual(outStr, FileUtility.GetSafePath(inStr, replacement));
} }

View File

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.7" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.7" />
<PackageReference Include="coverlet.collector" Version="3.1.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\LibationFileManager\LibationFileManager.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,189 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Dinah.Core;
using FluentAssertions;
using LibationFileManager;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace TemplatesTests
{
[TestClass]
public class ContainsChapterOnlyTags
{
[TestMethod]
[DataRow("<ch>", false)]
[DataRow("<ch#>", true)]
[DataRow("<id>", false)]
[DataRow("<id><ch#>", true)]
public void Tests(string template, bool expected) => Templates.ContainsChapterOnlyTags(template).Should().Be(expected);
}
[TestClass]
public class ContainsTag
{
[TestMethod]
[DataRow("<ch#>", "ch#", true)]
[DataRow("<id>", "ch#", false)]
[DataRow("<id><ch#>", "ch#", true)]
public void Tests(string template, string tag, bool expected) => Templates.ContainsTag(template, tag).Should().Be(expected);
}
}
namespace Templates_Folder_Tests
{
[TestClass]
public class IsValid
{
[TestMethod]
public void null_is_invalid() => Tests(null, false);
[TestMethod]
public void empty_is_valid() => Tests("", true);
[TestMethod]
public void whitespace_is_valid() => Tests(" ", true);
[TestMethod]
[DataRow(@"C:\", false)]
[DataRow(@"foo", true)]
[DataRow(@"\foo", true)]
[DataRow(@"foo\", true)]
[DataRow(@"\foo\", true)]
[DataRow(@"foo\bar", true)]
[DataRow(@"<id>", true)]
[DataRow(@"<id>\<title>", true)]
public void Tests(string template, bool expected) => Templates.Folder.IsValid(template).Should().Be(expected);
}
[TestClass]
public class IsRecommended
{
[TestMethod]
public void null_is_not_recommended() => Tests(null, false);
[TestMethod]
public void empty_is_not_recommended() => Tests("", false);
[TestMethod]
public void whitespace_is_not_recommended() => Tests(" ", false);
[TestMethod]
[DataRow(@"no tags", false)]
[DataRow(@"<id>\foo\bar", true)]
[DataRow("<ch#> <id>", false)]
[DataRow("<ch#> chapter tag", false)]
public void Tests(string template, bool expected) => Templates.Folder.IsRecommended(template).Should().Be(expected);
}
[TestClass]
public class TagCount
{
[TestMethod]
public void null_is_not_recommended() => Assert.ThrowsException<NullReferenceException>(() => Tests(null, -1));
[TestMethod]
public void empty_is_not_recommended() => Tests("", 0);
[TestMethod]
public void whitespace_is_not_recommended() => Tests(" ", 0);
[TestMethod]
[DataRow("no tags", 0)]
[DataRow(@"<id>\foo\bar", 1)]
[DataRow("<id> <id>", 2)]
[DataRow("<id <id> >", 1)]
[DataRow("<id> <title>", 2)]
[DataRow("id> <title incomplete tags", 0)]
[DataRow("<not a real tag>", 0)]
[DataRow("<ch#> non-folder tag", 0)]
[DataRow("<ID> case specific", 0)]
public void Tests(string template, int expected) => Templates.Folder.TagCount(template).Should().Be(expected);
}
}
namespace Templates_File_Tests
{
[TestClass]
public class IsValid
{
[TestMethod]
public void null_is_invalid() => Tests(null, false);
[TestMethod]
public void empty_is_valid() => Tests("", true);
[TestMethod]
public void whitespace_is_valid() => Tests(" ", true);
[TestMethod]
[DataRow(@"C:\", false)]
[DataRow(@"foo", true)]
[DataRow(@"\foo", false)]
[DataRow(@"/foo", false)]
[DataRow(@"<id>", true)]
public void Tests(string template, bool expected) => Templates.File.IsValid(template).Should().Be(expected);
}
// same as Templates.Folder.IsRecommended
//[TestClass]
//public class IsRecommended { }
// same as Templates.Folder.TagCount
//[TestClass]
//public class TagCount { }
}
namespace Templates_ChapterFile_Tests
{
// same as Templates.File.IsValid
//[TestClass]
//public class IsValid { }
[TestClass]
public class IsRecommended
{
[TestMethod]
public void null_is_not_recommended() => Tests(null, false);
[TestMethod]
public void empty_is_not_recommended() => Tests("", false);
[TestMethod]
public void whitespace_is_not_recommended() => Tests(" ", false);
[TestMethod]
[DataRow(@"no tags", false)]
[DataRow(@"<id>\foo\bar", false)]
[DataRow("<ch#> <id>", true)]
[DataRow("<ch#> -- chapter tag", true)]
[DataRow("<chapter count> -- chapter tag but not ch# or ch_#", false)]
public void Tests(string template, bool expected) => Templates.ChapterFile.IsRecommended(template).Should().Be(expected);
}
[TestClass]
public class TagCount
{
[TestMethod]
public void null_is_not_recommended() => Assert.ThrowsException<NullReferenceException>(() => Tests(null, -1));
[TestMethod]
public void empty_is_not_recommended() => Tests("", 0);
[TestMethod]
public void whitespace_is_not_recommended() => Tests(" ", 0);
[TestMethod]
[DataRow("no tags", 0)]
[DataRow(@"<id>\foo\bar", 1)]
[DataRow("<id> <id>", 2)]
[DataRow("<id <id> >", 1)]
[DataRow("<id> <title>", 2)]
[DataRow("id> <title incomplete tags", 0)]
[DataRow("<not a real tag>", 0)]
[DataRow("<ch#> non-folder tag", 1)]
[DataRow("<ID> case specific", 0)]
public void Tests(string template, int expected) => Templates.ChapterFile.TagCount(template).Should().Be(expected);
}
}