File naming is Configuration driven: Configuration, AudioFileStorageExt, Templates, TemplateTags,
This commit is contained in:
parent
a3d38e082d
commit
35f8c05106
@ -3,7 +3,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<Version>6.2.8.3</Version>
|
||||
<Version>6.2.9.1</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@ -59,6 +59,7 @@ namespace AppScaffolding
|
||||
Migrations.migrate_to_v5_7_1(config);
|
||||
Migrations.migrate_to_v6_1_2(config);
|
||||
Migrations.migrate_to_v6_2_0(config);
|
||||
Migrations.migrate_to_v6_2_9(config);
|
||||
}
|
||||
|
||||
/// <summary>Initialize logging. Run after migration</summary>
|
||||
@ -345,5 +346,18 @@ namespace AppScaffolding
|
||||
if (!config.Exists(nameof(config.SplitFilesByChapter)))
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,9 +11,8 @@ namespace FileLiberator
|
||||
{
|
||||
public static class AudioFileStorageExt
|
||||
{
|
||||
private static string TEMP_SINGLE_TEMPLATE { get; } = "<title> [<id>]";
|
||||
private static string TEMP_DIR_TEMPLATE { get; } = "<title short> [<id>]";
|
||||
private static string TEMP_MULTI_TEMPLATE { get; } = "<title> [<id>] - <ch# 0> - <ch title>";
|
||||
private static void AddParameterReplacement(this FileTemplate fileTemplate, TemplateTags templateTags, object value)
|
||||
=> fileTemplate.AddParameterReplacement(templateTags.TagName, value);
|
||||
|
||||
internal class MultipartRenamer
|
||||
{
|
||||
@ -22,16 +21,16 @@ namespace FileLiberator
|
||||
public MultipartRenamer(LibraryBook libraryBook) => this.libraryBook = libraryBook;
|
||||
|
||||
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)
|
||||
{
|
||||
var fileTemplate = GetFileTemplateSingle(template, libraryBook, fullDirPath, extension);
|
||||
|
||||
fileTemplate.AddParameterReplacement("ch count", partsTotal.ToString());
|
||||
fileTemplate.AddParameterReplacement("ch#", partsPosition.ToString());
|
||||
fileTemplate.AddParameterReplacement("ch# 0", FileUtility.GetSequenceFormatted(partsPosition, partsTotal));
|
||||
fileTemplate.AddParameterReplacement("ch title", chapterTitle);
|
||||
fileTemplate.AddParameterReplacement(TemplateTags.ChCount, partsTotal);
|
||||
fileTemplate.AddParameterReplacement(TemplateTags.ChNumber, partsPosition);
|
||||
fileTemplate.AddParameterReplacement(TemplateTags.ChNumber0, FileUtility.GetSequenceFormatted(partsPosition, partsTotal));
|
||||
fileTemplate.AddParameterReplacement(TemplateTags.ChTitle, chapterTitle);
|
||||
|
||||
return fileTemplate.GetFilePath();
|
||||
}
|
||||
@ -47,11 +46,11 @@ namespace FileLiberator
|
||||
=> GetCustomDirFilename(_, libraryBook, AudibleFileStorage.BooksDirectory, extension);
|
||||
|
||||
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();
|
||||
|
||||
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();
|
||||
|
||||
internal static FileTemplate GetFileTemplateSingle(string template, LibraryBook libraryBook, string dirFullPath, string extension)
|
||||
@ -65,9 +64,17 @@ namespace FileLiberator
|
||||
|
||||
var title = libraryBook.Book.Title ?? "";
|
||||
|
||||
fileTemplate.AddParameterReplacement("title", title);
|
||||
fileTemplate.AddParameterReplacement("title short", title.IndexOf(':') < 1 ? title : title.Substring(0, title.IndexOf(':')));
|
||||
fileTemplate.AddParameterReplacement("id", libraryBook.Book.AudibleProductId);
|
||||
fileTemplate.AddParameterReplacement(TemplateTags.Id, libraryBook.Book.AudibleProductId);
|
||||
fileTemplate.AddParameterReplacement(TemplateTags.Title, title);
|
||||
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;
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dinah.Core;
|
||||
|
||||
@ -16,10 +15,12 @@ namespace FileManager
|
||||
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"} => /<name>/ => /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>
|
||||
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>
|
||||
public int? ParameterMaxSize { get; set; } = 50;
|
||||
@ -38,14 +39,14 @@ namespace FileManager
|
||||
return FileUtility.GetValidFilename(filename, IllegalCharacterReplacements);
|
||||
}
|
||||
|
||||
private string formatKey(string key)
|
||||
private static string formatKey(string key)
|
||||
=> key
|
||||
.Replace("<", "")
|
||||
.Replace(">", "");
|
||||
|
||||
private string formatValue(string value)
|
||||
private string formatValue(object value)
|
||||
=> ParameterMaxSize.HasValue && ParameterMaxSize.Value > 0
|
||||
? value?.Truncate(ParameterMaxSize.Value)
|
||||
: value;
|
||||
? value?.ToString().Truncate(ParameterMaxSize.Value)
|
||||
: value?.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,6 +62,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileLiberator.Tests", "_Tes
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileManager.Tests", "_Tests\FileManager.Tests\FileManager.Tests.csproj", "{F2E04270-4551-41C4-99FF-E7125BED708C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibationFileManager.Tests", "_Tests\LibationFileManager.Tests\LibationFileManager.Tests.csproj", "{EB781571-8548-477E-82AD-FB9FAB548D2F}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
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}.Release|Any CPU.ActiveCfg = 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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@ -158,6 +164,7 @@ Global
|
||||
{788294BE-0D8E-40D4-9CEE-67896FBB52CE} = {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}
|
||||
{EB781571-8548-477E-82AD-FB9FAB548D2F} = {67E66E82-5532-4440-AFB3-9FB1DF9DEF53}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {615E00ED-BAEF-4E8E-A92A-9B82D87942A9}
|
||||
|
||||
@ -147,6 +147,38 @@ namespace LibationFileManager
|
||||
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
|
||||
|
||||
#region known directories
|
||||
|
||||
37
LibationFileManager/TemplateTags.cs
Normal file
37
LibationFileManager/TemplateTags.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
88
LibationFileManager/Templates.cs
Normal file
88
LibationFileManager/Templates.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
1
LibationFileManager/_InternalsVisible.cs
Normal file
1
LibationFileManager/_InternalsVisible.cs
Normal file
@ -0,0 +1 @@
|
||||
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(nameof(LibationFileManager) + ".Tests")]
|
||||
@ -35,6 +35,7 @@ namespace FileUtilityTests
|
||||
[DataRow(@"a\b:c\d.txt", "ZZZ", @"a\bZZZc\d.txt")]
|
||||
// remove empty directories
|
||||
[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));
|
||||
}
|
||||
|
||||
|
||||
@ -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>
|
||||
189
_Tests/LibationFileManager.Tests/TemplatesTests.cs
Normal file
189
_Tests/LibationFileManager.Tests/TemplatesTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user