Template error and warning checks return specific errors/warnings in addition to bools

This commit is contained in:
Robert McRackan 2021-10-29 17:05:57 -04:00
parent 904665da7f
commit cae8ca7ef3
5 changed files with 273 additions and 53 deletions

View File

@ -185,6 +185,9 @@ namespace DataLayer
{ {
get get
{ {
if (_seriesLink is null)
return "";
// first: alphabetical by name // first: alphabetical by name
var withNames = _seriesLink var withNames = _seriesLink
.Where(s => !string.IsNullOrWhiteSpace(s.Series.Name)) .Where(s => !string.IsNullOrWhiteSpace(s.Series.Name))

View File

@ -6,6 +6,19 @@ namespace LibationFileManager
{ {
public abstract class Templates public abstract class Templates
{ {
protected static string[] Valid => Array.Empty<string>();
public const string ERROR_NULL_IS_INVALID = "Null template is invalid.";
public const string ERROR_FULL_PATH_IS_INVALID = @"No full paths allowed. Eg: should not start with C:\";
public const string ERROR_INVALID_FILE_NAME_CHAR = @"Only file name friendly characters allowed. Eg: no colons or slashes";
public const string WARNING_EMPTY = "Template is empty.";
public const string WARNING_WHITE_SPACE = "Template is white space.";
public const string WARNING_NO_TAGS = "Should use tags. Eg: <title>";
public const string WARNING_HAS_CHAPTER_TAGS = "Chapter tags should only be used in the template used for naming files which are split by chapter. Eg: <ch title>";
public const string WARNING_NO_CHAPTER_NUMBER_TAG = "Should include chapter number tag in template used for naming files which are split by chapter. Ie: <ch#> or <ch# 0>";
// actual possible to se?
public const string WARNING_NO_CHAPTER_TAGS = "Should include chapter tags in template used for naming files which are split by chapter. Eg: <ch title>";
public static Templates Folder { get; } = new FolderTemplate(); public static Templates Folder { get; } = new FolderTemplate();
public static Templates File { get; } = new FileTemplate(); public static Templates File { get; } = new FileTemplate();
public static Templates ChapterFile { get; } = new ChapterFileTemplate(); public static Templates ChapterFile { get; } = new ChapterFileTemplate();
@ -14,8 +27,12 @@ namespace LibationFileManager
public abstract string Description { get; } public abstract string Description { get; }
public abstract string DefaultTemplate { get; } public abstract string DefaultTemplate { get; }
public abstract bool IsValid(string template); public abstract IEnumerable<string> GetErrors(string template);
public abstract bool IsRecommended(string template); public bool IsValid(string template) => !GetErrors(template).Any();
public abstract IEnumerable<string> GetWarnings(string template);
public bool HasWarnings(string template) => GetWarnings(template).Any();
public abstract int TagCount(string template); public abstract int TagCount(string template);
public static bool ContainsChapterOnlyTags(string template) public static bool ContainsChapterOnlyTags(string template)
@ -25,19 +42,45 @@ namespace LibationFileManager
public static bool ContainsTag(string template, string tag) => template.Contains($"<{tag}>"); public static bool ContainsTag(string template, string tag) => template.Contains($"<{tag}>");
protected static bool fileIsValid(string template) protected static string[] getFileErrors(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. // 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) // null is invalid. whitespace is valid but not recommended
=> IsValid(template) if (template is null)
&& !string.IsNullOrWhiteSpace(template) return new[] { ERROR_NULL_IS_INVALID };
&& TagCount(template) > 0
&& ContainsChapterOnlyTags(template) == isChapter; if (template.Contains(':')
|| template.Contains(System.IO.Path.DirectorySeparatorChar)
|| template.Contains(System.IO.Path.AltDirectorySeparatorChar)
)
return new[] { ERROR_INVALID_FILE_NAME_CHAR };
return Valid;
}
protected IEnumerable<string> getWarnings(string template, bool isChapter)
{
var warnings = GetErrors(template).ToList();
if (template is null)
return warnings;
if (string.IsNullOrEmpty(template))
warnings.Add(WARNING_EMPTY);
else if (string.IsNullOrWhiteSpace(template))
warnings.Add(WARNING_WHITE_SPACE);
if (TagCount(template) == 0)
warnings.Add(WARNING_NO_TAGS);
var containsChapterOnlyTags = ContainsChapterOnlyTags(template);
if (isChapter && !containsChapterOnlyTags)
warnings.Add(WARNING_NO_CHAPTER_TAGS);
if (!isChapter && containsChapterOnlyTags)
warnings.Add(WARNING_HAS_CHAPTER_TAGS);
return warnings;
}
protected static int tagCount(string template, Func<TemplateTags, bool> func) protected static int tagCount(string template, Func<TemplateTags, bool> func)
=> TemplateTags.GetAll() => TemplateTags.GetAll()
@ -53,13 +96,20 @@ namespace LibationFileManager
public override string Description => Configuration.GetDescription(nameof(Configuration.FolderTemplate)); public override string Description => Configuration.GetDescription(nameof(Configuration.FolderTemplate));
public override string DefaultTemplate { get; } = "<title short> [<id>]"; public override string DefaultTemplate { get; } = "<title short> [<id>]";
public override bool IsValid(string template) public override IEnumerable<string> GetErrors(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 // null is invalid. whitespace is valid but not recommended
=> template is not null if (template is null)
&& !template.Contains(':'); return new[] { ERROR_NULL_IS_INVALID };
public override bool IsRecommended(string template) => isRecommended(template, false); // must be relative. no colons. all other path chars are valid enough to pass this check and will be handled on final save.
if (template.Contains(':'))
return new[] { ERROR_FULL_PATH_IS_INVALID };
return Valid;
}
public override IEnumerable<string> GetWarnings(string template) => getWarnings(template, false);
public override int TagCount(string template) => tagCount(template, t => !t.IsChapterOnly); public override int TagCount(string template) => tagCount(template, t => !t.IsChapterOnly);
} }
@ -70,9 +120,9 @@ namespace LibationFileManager
public override string Description => Configuration.GetDescription(nameof(Configuration.FileTemplate)); public override string Description => Configuration.GetDescription(nameof(Configuration.FileTemplate));
public override string DefaultTemplate { get; } = "<title> [<id>]"; public override string DefaultTemplate { get; } = "<title> [<id>]";
public override bool IsValid(string template) => fileIsValid(template); public override IEnumerable<string> GetErrors(string template) => getFileErrors(template);
public override bool IsRecommended(string template) => isRecommended(template, false); public override IEnumerable<string> GetWarnings(string template) => getWarnings(template, false);
public override int TagCount(string template) => tagCount(template, t => !t.IsChapterOnly); public override int TagCount(string template) => tagCount(template, t => !t.IsChapterOnly);
} }
@ -82,13 +132,24 @@ namespace LibationFileManager
public override string Name => "Chapter File Template"; public override string Name => "Chapter File Template";
public override string Description => Configuration.GetDescription(nameof(Configuration.ChapterFileTemplate)); public override string Description => Configuration.GetDescription(nameof(Configuration.ChapterFileTemplate));
public override string DefaultTemplate { get; } = "<title> [<id>] - <ch# 0> - <ch title>"; public override string DefaultTemplate { get; } = "<title> [<id>] - <ch# 0> - <ch title>";
public override IEnumerable<string> GetErrors(string template) => getFileErrors(template);
public override bool IsValid(string template) => fileIsValid(template); public override IEnumerable<string> GetWarnings(string template)
{
var warnings = getWarnings(template, true).ToList();
if (template is null)
return warnings;
public override bool IsRecommended(string template)
=> isRecommended(template, true)
// recommended to incl. <ch#> or <ch# 0> // recommended to incl. <ch#> or <ch# 0>
&& (ContainsTag(template, TemplateTags.ChNumber.TagName) || ContainsTag(template, TemplateTags.ChNumber0.TagName)); if (!ContainsTag(template, TemplateTags.ChNumber.TagName) && !ContainsTag(template, TemplateTags.ChNumber0.TagName))
{
warnings.Remove(WARNING_NO_CHAPTER_TAGS);
warnings.Add(WARNING_NO_CHAPTER_NUMBER_TAG);
}
return warnings;
}
public override int TagCount(string template) => tagCount(template, t => true); public override int TagCount(string template) => tagCount(template, t => true);
} }

View File

@ -60,12 +60,35 @@ namespace LibationWinForms.Dialogs
while (path.Contains(dbl)) while (path.Contains(dbl))
path = path.Replace(dbl, $"{Path.DirectorySeparatorChar}"); path = path.Replace(dbl, $"{Path.DirectorySeparatorChar}");
var book = new DataLayer.Book(
new DataLayer.AudibleProductId("123456789"),
"A Study in Scarlet",
"Fake description",
1234,
DataLayer.ContentType.Product,
new List<DataLayer.Contributor>
{
new("Arthur Conan Doyle"),
new("Stephen Fry - introductions")
},
new List<DataLayer.Contributor> { new("Stephen Fry") },
new DataLayer.Category(new DataLayer.AudibleCategoryId("cat12345"), "Mystery"),
"us"
);
var libraryBook = new DataLayer.LibraryBook(book, DateTime.Now, "my account");
outputTb.Text = @$" outputTb.Text = @$"
{books} {books}
{folderTemplate} {folderTemplate}
{fileTemplate} {fileTemplate}
{ext} {ext}
{path} {path}
{book.AudibleProductId}
{book.Title}
{book.AuthorNames}
{book.NarratorNames}
series: {"Sherlock Holmes"}
"; ";
} }

View File

@ -108,8 +108,10 @@ namespace LibationWinForms.Dialogs
private void chapterFileTemplateBtn_Click(object sender, EventArgs e) => editTemplate(Templates.ChapterFile, chapterFileTemplateTb); private void chapterFileTemplateBtn_Click(object sender, EventArgs e) => editTemplate(Templates.ChapterFile, chapterFileTemplateTb);
private static void editTemplate(Templates template, TextBox textBox) private static void editTemplate(Templates template, TextBox textBox)
{ {
#if !DEBUG
TEMP_TEMP_TEMP(); TEMP_TEMP_TEMP();
return; return;
#endif
var form = new EditTemplateDialog(template, textBox.Text); var form = new EditTemplateDialog(template, textBox.Text);
if (form.ShowDialog() == DialogResult.OK) if (form.ShowDialog() == DialogResult.OK)
@ -124,31 +126,34 @@ namespace LibationWinForms.Dialogs
var newBooks = booksSelectControl.SelectedDirectory; var newBooks = booksSelectControl.SelectedDirectory;
#region validation #region validation
static void validationError(string text, string caption)
=> MessageBox.Show(text, caption, MessageBoxButtons.OK, MessageBoxIcon.Error);
if (string.IsNullOrWhiteSpace(newBooks)) if (string.IsNullOrWhiteSpace(newBooks))
{ {
MessageBox.Show("Cannot set Books Location to blank", "Location is blank", MessageBoxButtons.OK, MessageBoxIcon.Error); validationError("Cannot set Books Location to blank", "Location is blank");
return; return;
} }
if (!Directory.Exists(newBooks) && booksSelectControl.SelectedDirectoryIsCustom) if (!Directory.Exists(newBooks) && booksSelectControl.SelectedDirectoryIsCustom)
{ {
MessageBox.Show($"Not saving change to Books location. This folder does not exist:\r\n{newBooks}", "Folder does not exist", MessageBoxButtons.OK, MessageBoxIcon.Error); validationError($"Not saving change to Books location. This folder does not exist:\r\n{newBooks}", "Folder does not exist");
return; return;
} }
// these 3 should do nothing. Configuration will only init these with a valid value. EditTemplateDialog ensures valid before returning
if (!Templates.Folder.IsValid(folderTemplateTb.Text)) if (!Templates.Folder.IsValid(folderTemplateTb.Text))
{ {
MessageBox.Show($"Not saving change to folder naming template. Invalid format.", "Invalid folder template", MessageBoxButtons.OK, MessageBoxIcon.Error); validationError($"Not saving change to folder naming template. Invalid format.", "Invalid folder template");
return; return;
} }
if (!Templates.File.IsValid(fileTemplateTb.Text)) if (!Templates.File.IsValid(fileTemplateTb.Text))
{ {
MessageBox.Show($"Not saving change to file naming template. Invalid format.", "Invalid file template", MessageBoxButtons.OK, MessageBoxIcon.Error); validationError($"Not saving change to file naming template. Invalid format.", "Invalid file template");
return; return;
} }
if (!Templates.ChapterFile.IsValid(chapterFileTemplateTb.Text)) if (!Templates.ChapterFile.IsValid(chapterFileTemplateTb.Text))
{ {
MessageBox.Show($"Not saving change to chapter file naming template. Invalid format.", "Invalid chapter file template", MessageBoxButtons.OK, MessageBoxIcon.Error); validationError($"Not saving change to chapter file naming template. Invalid format.", "Invalid chapter file template");
return; return;
} }
#endregion #endregion

View File

@ -33,6 +33,38 @@ namespace TemplatesTests
namespace Templates_Folder_Tests namespace Templates_Folder_Tests
{ {
[TestClass]
public class GetErrors
{
[TestMethod]
public void null_is_invalid() => Tests(null, new[] { Templates.ERROR_NULL_IS_INVALID });
[TestMethod]
public void empty_is_valid() => valid_tests("");
[TestMethod]
public void whitespace_is_valid() => valid_tests(" ");
[TestMethod]
[DataRow(@"foo")]
[DataRow(@"\foo")]
[DataRow(@"foo\")]
[DataRow(@"\foo\")]
[DataRow(@"foo\bar")]
[DataRow(@"<id>")]
[DataRow(@"<id>\<title>")]
public void valid_tests(string template) => Tests(template, Array.Empty<string>());
[TestMethod]
[DataRow(@"C:\", Templates.ERROR_FULL_PATH_IS_INVALID)]
public void Tests(string template, params string[] expected)
{
var result = Templates.Folder.GetErrors(template);
result.Count().Should().Be(expected.Length);
result.Should().BeEquivalentTo(expected);
}
}
[TestClass] [TestClass]
public class IsValid public class IsValid
{ {
@ -58,36 +90,64 @@ namespace Templates_Folder_Tests
} }
[TestClass] [TestClass]
public class IsRecommended public class GetWarnings
{ {
[TestMethod] [TestMethod]
public void null_is_not_recommended() => Tests(null, false); public void null_is_invalid() => Tests(null, new[] { Templates.ERROR_NULL_IS_INVALID });
[TestMethod] [TestMethod]
public void empty_is_not_recommended() => Tests("", false); public void empty_has_warnings() => Tests("", Templates.WARNING_EMPTY, Templates.WARNING_NO_TAGS);
[TestMethod] [TestMethod]
public void whitespace_is_not_recommended() => Tests(" ", false); public void whitespace_has_warnings() => Tests(" ", Templates.WARNING_WHITE_SPACE, Templates.WARNING_NO_TAGS);
[TestMethod] [TestMethod]
[DataRow(@"no tags", false)] [DataRow(@"<id>\foo\bar")]
[DataRow(@"<id>\foo\bar", true)] public void valid_tests(string template) => Tests(template, Array.Empty<string>());
[DataRow("<ch#> <id>", false)]
[DataRow("<ch#> chapter tag", false)] [TestMethod]
public void Tests(string template, bool expected) => Templates.Folder.IsRecommended(template).Should().Be(expected); [DataRow(@"no tags", Templates.WARNING_NO_TAGS)]
[DataRow("<ch#> <id>", Templates.WARNING_HAS_CHAPTER_TAGS)]
[DataRow("<ch#> chapter tag", Templates.WARNING_NO_TAGS, Templates.WARNING_HAS_CHAPTER_TAGS)]
public void Tests(string template, params string[] expected)
{
var result = Templates.Folder.GetWarnings(template);
result.Count().Should().Be(expected.Length);
result.Should().BeEquivalentTo(expected);
}
}
[TestClass]
public class HasWarnings
{
[TestMethod]
public void null_has_warnings() => Tests(null, true);
[TestMethod]
public void empty_has_warnings() => Tests("", true);
[TestMethod]
public void whitespace_has_warnings() => Tests(" ", true);
[TestMethod]
[DataRow(@"no tags", true)]
[DataRow(@"<id>\foo\bar", false)]
[DataRow("<ch#> <id>", true)]
[DataRow("<ch#> chapter tag", true)]
public void Tests(string template, bool expected) => Templates.Folder.HasWarnings(template).Should().Be(expected);
} }
[TestClass] [TestClass]
public class TagCount public class TagCount
{ {
[TestMethod] [TestMethod]
public void null_is_not_recommended() => Assert.ThrowsException<NullReferenceException>(() => Tests(null, -1)); public void null_throws() => Assert.ThrowsException<NullReferenceException>(() => Templates.Folder.TagCount(null));
[TestMethod] [TestMethod]
public void empty_is_not_recommended() => Tests("", 0); public void empty() => Tests("", 0);
[TestMethod] [TestMethod]
public void whitespace_is_not_recommended() => Tests(" ", 0); public void whitespace() => Tests(" ", 0);
[TestMethod] [TestMethod]
[DataRow("no tags", 0)] [DataRow("no tags", 0)]
@ -105,6 +165,37 @@ namespace Templates_Folder_Tests
namespace Templates_File_Tests namespace Templates_File_Tests
{ {
[TestClass]
public class GetErrors
{
[TestMethod]
public void null_is_invalid() => Tests(null, new[] { Templates.ERROR_NULL_IS_INVALID });
[TestMethod]
public void empty_is_valid() => valid_tests("");
[TestMethod]
public void whitespace_is_valid() => valid_tests(" ");
[TestMethod]
[DataRow(@"foo")]
[DataRow(@"<id>")]
public void valid_tests(string template) => Tests(template, Array.Empty<string>());
[TestMethod]
[DataRow(@"C:\", Templates.ERROR_INVALID_FILE_NAME_CHAR)]
[DataRow(@"\foo", Templates.ERROR_INVALID_FILE_NAME_CHAR)]
[DataRow(@"/foo", Templates.ERROR_INVALID_FILE_NAME_CHAR)]
[DataRow(@"C:\", Templates.ERROR_INVALID_FILE_NAME_CHAR)]
public void Tests(string template, params string[] expected)
{
var result = Templates.File.GetErrors(template);
result.Count().Should().Be(expected.Length);
result.Should().BeEquivalentTo(expected);
}
}
[TestClass] [TestClass]
public class IsValid public class IsValid
{ {
@ -126,9 +217,13 @@ namespace Templates_File_Tests
public void Tests(string template, bool expected) => Templates.File.IsValid(template).Should().Be(expected); public void Tests(string template, bool expected) => Templates.File.IsValid(template).Should().Be(expected);
} }
// same as Templates.Folder.IsRecommended // same as Templates.Folder.GetWarnings
//[TestClass] //[TestClass]
//public class IsRecommended { } //public class GetWarnings { }
// same as Templates.Folder.HasWarnings
//[TestClass]
//public class HasWarnings { }
// same as Templates.Folder.TagCount // same as Templates.Folder.TagCount
//[TestClass] //[TestClass]
@ -137,29 +232,62 @@ namespace Templates_File_Tests
namespace Templates_ChapterFile_Tests namespace Templates_ChapterFile_Tests
{ {
// same as Templates.File.GetErrors
//[TestClass]
//public class GetErrors { }
// same as Templates.File.IsValid // same as Templates.File.IsValid
//[TestClass] //[TestClass]
//public class IsValid { } //public class IsValid { }
[TestClass] [TestClass]
public class IsRecommended public class GetWarnings
{ {
[TestMethod] [TestMethod]
public void null_is_not_recommended() => Tests(null, false); public void null_is_invalid() => Tests(null, new[] { Templates.ERROR_NULL_IS_INVALID });
[TestMethod] [TestMethod]
public void empty_is_not_recommended() => Tests("", false); public void empty_has_warnings() => Tests("", Templates.WARNING_EMPTY, Templates.WARNING_NO_TAGS, Templates.WARNING_NO_CHAPTER_NUMBER_TAG);
[TestMethod] [TestMethod]
public void whitespace_is_not_recommended() => Tests(" ", false); public void whitespace_has_warnings() => Tests(" ", Templates.WARNING_WHITE_SPACE, Templates.WARNING_NO_TAGS, Templates.WARNING_NO_CHAPTER_NUMBER_TAG);
[TestMethod] [TestMethod]
[DataRow(@"no tags", false)] [DataRow("<ch#>")]
[DataRow(@"<id>\foo\bar", false)] [DataRow("<ch#> <id>")]
[DataRow("<ch#> <id>", true)] public void valid_tests(string template) => Tests(template, Array.Empty<string>());
[DataRow("<ch#> -- chapter tag", true)]
[DataRow("<chapter count> -- chapter tag but not ch# or ch_#", false)] [TestMethod]
public void Tests(string template, bool expected) => Templates.ChapterFile.IsRecommended(template).Should().Be(expected); [DataRow(@"no tags", Templates.WARNING_NO_TAGS, Templates.WARNING_NO_CHAPTER_NUMBER_TAG)]
[DataRow(@"<id>\foo\bar", Templates.ERROR_INVALID_FILE_NAME_CHAR, Templates.WARNING_NO_CHAPTER_NUMBER_TAG)]
[DataRow("<chapter count> -- chapter tag but not ch# or ch_#", Templates.WARNING_NO_TAGS, Templates.WARNING_NO_CHAPTER_NUMBER_TAG)]
public void Tests(string template, params string[] expected)
{
var result = Templates.ChapterFile.GetWarnings(template);
result.Count().Should().Be(expected.Length);
result.Should().BeEquivalentTo(expected);
}
}
[TestClass]
public class HasWarnings
{
[TestMethod]
public void null_has_warnings() => Tests(null, true);
[TestMethod]
public void empty_has_warnings() => Tests("", true);
[TestMethod]
public void whitespace_has_warnings() => Tests(" ", true);
[TestMethod]
[DataRow(@"no tags", true)]
[DataRow(@"<id>\foo\bar", true)]
[DataRow("<ch#> <id>", false)]
[DataRow("<ch#> -- chapter tag", false)]
[DataRow("<chapter count> -- chapter tag but not ch# or ch_#", true)]
public void Tests(string template, bool expected) => Templates.ChapterFile.HasWarnings(template).Should().Be(expected);
} }
[TestClass] [TestClass]