File naming is Configuration driven: Configuration, AudioFileStorageExt, Templates, TemplateTags,
This commit is contained in:
parent
a3d38e082d
commit
35f8c05106
@ -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>
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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"} => /<name>/ => /Bill Gates/</summary>
|
/// <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>
|
/// <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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
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")]
|
[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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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