Add <has-> template tag
This commit is contained in:
parent
4ea7f04921
commit
3b86fc405f
@ -81,17 +81,22 @@ Anything between the opening tag (`<tagname->`) and closing tag (`<-tagname>`) w
|
||||
|\<if podcast-\>...\<-if podcast\>|Only include if part of a podcast|Conditional|
|
||||
|\<if bookseries-\>...\<-if bookseries\>|Only include if part of a book series|Conditional|
|
||||
|\<if podcastparent-\>...\<-if podcastparent\>**†**|Only include if item is a podcast series parent|Conditional|
|
||||
|\<has PROPERTY-\>...\<-has\>|Only include if the PROPERTY has a value (i.e. not null or empty)|Conditional|
|
||||
|
||||
**†** Only affects the podcast series folder naming if "Save all podcast episodes to the series parent folder" option is checked.
|
||||
|
||||
For example, <if podcast-\>\<series\>\<-if podcast\> will evaluate to the podcast's series name if the file is a podcast. For audiobooks that are not podcasts, that tag will be blank.
|
||||
For example, `<if podcast-><series><-if podcast>` will evaluate to the podcast's series name if the file is a podcast. For audiobooks that are not podcasts, that tag will be blank.
|
||||
|
||||
You can invert the condition (instead of displaying the text when the condition is true, display the text when it is false) by playing a '!' symbol before the opening tag name.
|
||||
You can invert the condition (instead of displaying the text when the condition is true, display the text when it is false) by playing a `!` symbol before the opening tag name.
|
||||
|
||||
As an example, this folder template will place all Liberated podcasts into a "Podcasts" folder and all liberated books (not podcasts) into a "Books" folder.
|
||||
|
||||
\<if podcast-\>Podcasts<-if podcast\>\<!if podcast-\>Books\<-if podcast\>\\\<title\>
|
||||
`<if podcast->Podcasts<-if podcast><!if podcast->Books<-if podcast>\<title>`
|
||||
|
||||
This example will add a number if the `<series#\>` tag has a value:
|
||||
`<has series#><series#><-has>`
|
||||
And this example will customize the title based on whether the book has a subtitle:
|
||||
`<audible title><has audible subtitle->-<audible subtitle><-has>`
|
||||
|
||||
# Tag Formatters
|
||||
**Text**, **Name List**, **Number**, and **DateTime** tags can be optionally formatted using format text in square brackets after the tag name. Below is a list of supported formatters for each tag type.
|
||||
|
||||
@ -22,6 +22,8 @@ internal interface IClosingPropertyTag : IPropertyTag
|
||||
bool StartsWithClosing(string templateString, [NotNullWhen(true)] out string? exactName, [NotNullWhen(true)] out IClosingPropertyTag? propertyTag);
|
||||
}
|
||||
|
||||
public delegate bool Conditional<T>(ITemplateTag templateTag, T value, string condition);
|
||||
|
||||
public class ConditionalTagCollection<TClass> : TagCollection
|
||||
{
|
||||
public ConditionalTagCollection(bool caseSensative = true) :base(typeof(TClass), caseSensative) { }
|
||||
@ -32,21 +34,49 @@ public class ConditionalTagCollection<TClass> : TagCollection
|
||||
/// <param name="propertyGetter">A Func to get the condition's <see cref="bool"/> value from <see cref="TClass"/></param>
|
||||
public void Add(ITemplateTag templateTag, Func<TClass, bool> propertyGetter)
|
||||
{
|
||||
var expr = Expression.Call(Expression.Constant(propertyGetter.Target), propertyGetter.Method, Parameter);
|
||||
|
||||
var target = propertyGetter.Target is null ? null : Expression.Constant(propertyGetter.Target);
|
||||
var expr = Expression.Call(target, propertyGetter.Method, Parameter);
|
||||
AddPropertyTag(new ConditionalTag(templateTag, Options, expr));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register a conditional tag.
|
||||
/// </summary>
|
||||
/// <param name="conditional">A <see cref="Conditional{TClass}"/> to get the condition's <see cref="bool"/> value</param>
|
||||
public void Add(ITemplateTag templateTag, Conditional<TClass> conditional)
|
||||
{
|
||||
AddPropertyTag(new ConditionalTag(templateTag, Options, Parameter, conditional));
|
||||
}
|
||||
|
||||
private class ConditionalTag : TagBase, IClosingPropertyTag
|
||||
{
|
||||
public override Regex NameMatcher { get; }
|
||||
public Regex NameCloseMatcher { get; }
|
||||
|
||||
private Func<string?, Expression> CreateConditionExpression { get; }
|
||||
|
||||
public ConditionalTag(ITemplateTag templateTag, RegexOptions options, Expression conditionExpression)
|
||||
: base(templateTag, conditionExpression)
|
||||
{
|
||||
NameMatcher = new Regex($"^<(!)?{templateTag.TagName}->", options);
|
||||
NameMatcher = new Regex(@$"^<(!)?{templateTag.TagName}->", options);
|
||||
NameCloseMatcher = new Regex($"^<-{templateTag.TagName}>", options);
|
||||
CreateConditionExpression = _ => conditionExpression;
|
||||
}
|
||||
|
||||
public ConditionalTag(ITemplateTag templateTag, RegexOptions options, ParameterExpression parameter, Conditional<TClass> conditional)
|
||||
: base(templateTag, Expression.Constant(false))
|
||||
{
|
||||
NameMatcher = new Regex(@$"^<(!)?{templateTag.TagName}(?:\s+?(.*?)\s*?)?->", options);
|
||||
NameCloseMatcher = new Regex($"^<-{templateTag.TagName}>", options);
|
||||
|
||||
var target = conditional.Target is null ? null : Expression.Constant(conditional.Target);
|
||||
CreateConditionExpression = condition
|
||||
=> Expression.Call(
|
||||
conditional.Target is null ? null : Expression.Constant(conditional.Target),
|
||||
conditional.Method,
|
||||
Expression.Constant(templateTag),
|
||||
parameter,
|
||||
Expression.Constant(condition));
|
||||
}
|
||||
|
||||
public bool StartsWithClosing(string templateString, [NotNullWhen(true)] out string? exactName, [NotNullWhen(true)] out IClosingPropertyTag? propertyTag)
|
||||
@ -64,6 +94,13 @@ public class ConditionalTagCollection<TClass> : TagCollection
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override Expression GetTagExpression(string exactName, string formatter) => formatter == "!" ? Expression.Not(ValueExpression) : ValueExpression;
|
||||
protected override Expression GetTagExpression(string exactName, string[] extraData)
|
||||
{
|
||||
if (extraData.Length is not (1 or 2) || extraData[0] is not ("!" or "") || extraData.Length == 2 && string.IsNullOrWhiteSpace(extraData[1]))
|
||||
return Expression.Constant(false);
|
||||
|
||||
var getBool = extraData.Length == 2 ? CreateConditionExpression(extraData[1]) : CreateConditionExpression(null);
|
||||
return extraData[0] == "!" ? Expression.Not(getBool) : getBool;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ public class NamingTemplate
|
||||
/// Invoke the <see cref="NamingTemplate"/>
|
||||
/// </summary>
|
||||
/// <param name="propertyClasses">Instances of the TClass used in <see cref="PropertyTagCollection{TClass}"/> and <see cref="ConditionalTagCollection{TClass}"/></param>
|
||||
public TemplatePart Evaluate(params object[] propertyClasses)
|
||||
public TemplatePart Evaluate(params object?[] propertyClasses)
|
||||
{
|
||||
if (templateToString is null)
|
||||
throw new InvalidOperationException();
|
||||
@ -38,8 +38,8 @@ public class NamingTemplate
|
||||
// Match propertyClasses to the arguments required by templateToString.DynamicInvoke().
|
||||
// First parameter is "this", so ignore it.
|
||||
var delegateArgTypes = templateToString.Method.GetParameters().Skip(1);
|
||||
|
||||
object[] args = delegateArgTypes.Join(propertyClasses, o => o.ParameterType, i => i.GetType(), (_, i) => i).ToArray();
|
||||
|
||||
object?[] args = delegateArgTypes.Join(propertyClasses, o => o.ParameterType, i => i?.GetType(), (_, i) => i).ToArray();
|
||||
|
||||
if (args.Length != delegateArgTypes.Count())
|
||||
throw new ArgumentException($"This instance of {nameof(NamingTemplate)} requires the following arguments: {string.Join(", ", delegateArgTypes.Select(t => t.Name).Distinct())}");
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
using Dinah.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text.RegularExpressions;
|
||||
@ -109,6 +110,25 @@ public class PropertyTagCollection<TClass> : TagCollection
|
||||
catch { return null; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to get the default (unformatted) value of a property tag.
|
||||
/// </summary>
|
||||
/// <param name="tagName">Name of the tag value to get</param>
|
||||
/// <param name="object">The property class from which the tag's value is read</param>
|
||||
/// <param name="value"><paramref name="tagName"/>'s string value if it is in this collection, otherwise null</param>
|
||||
/// <returns>True if the <paramref name="tagName"/> is in this collection, otherwise false</returns>
|
||||
public bool TryGetValue(string tagName, TClass @object, [NotNullWhen(true)] out string? value)
|
||||
{
|
||||
value = null;
|
||||
|
||||
if (!StartsWith($"<{tagName}>", out var exactName, out var propertyTag, out var valueExpression))
|
||||
return false;
|
||||
|
||||
var func = Expression.Lambda<Func<TClass, string>>(valueExpression, Parameter).Compile();
|
||||
value = func(@object);
|
||||
return true;
|
||||
}
|
||||
|
||||
private class PropertyTag<TPropertyValue> : TagBase
|
||||
{
|
||||
public override Regex NameMatcher { get; }
|
||||
@ -138,8 +158,13 @@ public class PropertyTagCollection<TClass> : TagCollection
|
||||
expVal);
|
||||
}
|
||||
|
||||
protected override Expression GetTagExpression(string exactName, string formatString)
|
||||
protected override Expression GetTagExpression(string exactName, string[] extraData)
|
||||
{
|
||||
if (extraData.Length is not (0 or 1))
|
||||
return Expression.Constant(exactName);
|
||||
|
||||
string formatString = extraData.Length == 1 ? extraData[0] : "";
|
||||
|
||||
Expression toStringExpression
|
||||
= !ReturnType.IsValueType
|
||||
? Expression.Condition(
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
@ -42,8 +43,8 @@ internal abstract class TagBase : IPropertyTag
|
||||
|
||||
/// <summary>Create an <see cref="Expression"/> that returns the property's value.</summary>
|
||||
/// <param name="exactName">The exact string that was matched to <see cref="ITemplateTag"/></param>
|
||||
/// <param name="formatter">The optional format string in the match inside the square brackets</param>
|
||||
protected abstract Expression GetTagExpression(string exactName, string formatter);
|
||||
/// <param name="extraData">Optional extra data parsed from the tag, such as a format string in the match the square brackets, logical negation, and conditional options</param>
|
||||
protected abstract Expression GetTagExpression(string exactName, string[] extraData);
|
||||
|
||||
public bool StartsWith(string templateString, [NotNullWhen(true)] out string? exactName, [NotNullWhen(true)] out Expression? propertyValue)
|
||||
{
|
||||
@ -51,7 +52,7 @@ internal abstract class TagBase : IPropertyTag
|
||||
if (match.Success)
|
||||
{
|
||||
exactName = match.Value;
|
||||
propertyValue = GetTagExpression(exactName, match.Groups.Count == 2 ? match.Groups[1].Value.Trim() : "");
|
||||
propertyValue = GetTagExpression(exactName, match.Groups.Values.Skip(1).Select(v => v.Value.Trim()).ToArray());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ public abstract class TagCollection : IEnumerable<ITemplateTag>
|
||||
/// <summary>The <see cref="ParameterExpression"/> of the <see cref="TagCollection"/>'s TClass type.</summary>
|
||||
internal ParameterExpression Parameter { get; }
|
||||
protected RegexOptions Options { get; } = RegexOptions.Compiled;
|
||||
private List<IPropertyTag> PropertyTags { get; } = new();
|
||||
internal List<IPropertyTag> PropertyTags { get; } = new();
|
||||
|
||||
protected TagCollection(Type classType, bool caseSensative = true)
|
||||
{
|
||||
|
||||
15
Source/LibationFileManager/Templates/CombinedDto.cs
Normal file
15
Source/LibationFileManager/Templates/CombinedDto.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using AaxDecrypter;
|
||||
|
||||
#nullable enable
|
||||
namespace LibationFileManager.Templates;
|
||||
|
||||
public class CombinedDto
|
||||
{
|
||||
public LibraryBookDto LibraryBook { get; }
|
||||
public MultiConvertFileProperties? MultiConvert { get; }
|
||||
public CombinedDto(LibraryBookDto libraryBook, MultiConvertFileProperties? multiConvert = null)
|
||||
{
|
||||
LibraryBook = libraryBook;
|
||||
MultiConvert = multiConvert;
|
||||
}
|
||||
}
|
||||
@ -56,5 +56,6 @@ namespace LibationFileManager.Templates
|
||||
public static TemplateTags IfPodcast { get; } = new TemplateTags("if podcast", "Only include if part of a podcast", "<if podcast-><-if podcast>", "<if podcast->...<-if podcast>");
|
||||
public static TemplateTags IfPodcastParent { get; } = new TemplateTags("if podcastparent", "Only include if item is a podcast series parent", "<if podcastparent-><-if podcastparent>", "<if podcastparent->...<-if podcastparent>");
|
||||
public static TemplateTags IfBookseries { get; } = new TemplateTags("if bookseries", "Only include if part of a book series", "<if bookseries-><-if bookseries>", "<if bookseries->...<-if bookseries>");
|
||||
public static TemplateTags Has { get; } = new TemplateTags("has", "Only include if PROPERTY has a value (i.e. not null or empty)", "<has -><-has>", "<has PROPERTY->...<-has>");
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,7 +111,7 @@ namespace LibationFileManager.Templates
|
||||
{
|
||||
ArgumentValidator.EnsureNotNull(libraryBookDto, nameof(libraryBookDto));
|
||||
ArgumentValidator.EnsureNotNull(multiChapProps, nameof(multiChapProps));
|
||||
return string.Concat(NamingTemplate.Evaluate(libraryBookDto, multiChapProps).Select(p => p.Value));
|
||||
return string.Concat(NamingTemplate.Evaluate(libraryBookDto, multiChapProps, new CombinedDto(libraryBookDto, multiChapProps)).Select(p => p.Value));
|
||||
}
|
||||
|
||||
public LongPath GetFilename(LibraryBookDto libraryBookDto, string baseDir, string fileExtension, ReplacementCharacters? replacements = null, bool returnFirstExisting = false)
|
||||
@ -138,11 +138,11 @@ namespace LibationFileManager.Templates
|
||||
protected virtual IEnumerable<string> GetTemplatePartsStrings(List<TemplatePart> parts, ReplacementCharacters replacements)
|
||||
=> parts.Select(p => replacements.ReplaceFilenameChars(p.Value));
|
||||
|
||||
private LongPath GetFilename(string baseDir, string fileExtension, ReplacementCharacters replacements, bool returnFirstExisting, params object[] dtos)
|
||||
private LongPath GetFilename(string baseDir, string fileExtension, ReplacementCharacters replacements, bool returnFirstExisting, LibraryBookDto lbDto, MultiConvertFileProperties? multiDto = null)
|
||||
{
|
||||
fileExtension = FileUtility.GetStandardizedExtension(fileExtension);
|
||||
|
||||
var parts = NamingTemplate.Evaluate(dtos).ToList();
|
||||
var parts = NamingTemplate.Evaluate(lbDto, multiDto, new CombinedDto(lbDto, multiDto)).ToList();
|
||||
var pathParts = GetPathParts(GetTemplatePartsStrings(parts, replacements));
|
||||
|
||||
//Remove 1 character from the end of the longest filename part until
|
||||
@ -323,6 +323,35 @@ namespace LibationFileManager.Templates
|
||||
{ TemplateTags.IfBookseries, lb => lb.IsSeries && !lb.IsPodcast && !lb.IsPodcastParent },
|
||||
};
|
||||
|
||||
private static readonly ConditionalTagCollection<CombinedDto> combinedConditionalTags = new()
|
||||
{
|
||||
{ TemplateTags.Has, HasValue}
|
||||
};
|
||||
|
||||
private static bool HasValue(ITemplateTag tag, CombinedDto dtos, string condition)
|
||||
{
|
||||
foreach (var c in chapterPropertyTags.OfType<PropertyTagCollection<LibraryBookDto>>().Append(filePropertyTags).Append(audioFilePropertyTags))
|
||||
{
|
||||
if (c.TryGetValue(condition, dtos.LibraryBook, out var value))
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(value);
|
||||
}
|
||||
}
|
||||
|
||||
if (dtos.MultiConvert is null)
|
||||
return false;
|
||||
|
||||
foreach (var c in chapterPropertyTags.OfType<PropertyTagCollection<MultiConvertFileProperties>>())
|
||||
{
|
||||
if (c.TryGetValue(condition, dtos.MultiConvert, out var value))
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(value);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static readonly ConditionalTagCollection<LibraryBookDto> folderConditionalTags = new()
|
||||
{
|
||||
{ TemplateTags.IfPodcastParent, lb => lb.IsPodcastParent }
|
||||
@ -388,7 +417,7 @@ namespace LibationFileManager.Templates
|
||||
public static string Name { get; } = "Folder Template";
|
||||
public static string Description { get; } = Configuration.GetDescription(nameof(Configuration.FolderTemplate)) ?? "";
|
||||
public static string DefaultTemplate { get; } = "<title short> [<id>]";
|
||||
public static IEnumerable<TagCollection> TagCollections { get; } = [filePropertyTags, audioFilePropertyTags, conditionalTags, folderConditionalTags];
|
||||
public static IEnumerable<TagCollection> TagCollections { get; } = [filePropertyTags, audioFilePropertyTags, conditionalTags, folderConditionalTags, combinedConditionalTags];
|
||||
|
||||
public override IEnumerable<string> Errors
|
||||
=> TemplateText?.Length >= 2 && Path.IsPathFullyQualified(TemplateText) ? base.Errors.Append(ERROR_FULL_PATH_IS_INVALID) : base.Errors;
|
||||
@ -407,7 +436,7 @@ namespace LibationFileManager.Templates
|
||||
public static string Name { get; } = "File Template";
|
||||
public static string Description { get; } = Configuration.GetDescription(nameof(Configuration.FileTemplate)) ?? "";
|
||||
public static string DefaultTemplate { get; } = "<title> [<id>]";
|
||||
public static IEnumerable<TagCollection> TagCollections { get; } = [filePropertyTags, audioFilePropertyTags, conditionalTags];
|
||||
public static IEnumerable<TagCollection> TagCollections { get; } = [filePropertyTags, audioFilePropertyTags, conditionalTags, combinedConditionalTags];
|
||||
}
|
||||
|
||||
public class ChapterFileTemplate : Templates, ITemplate
|
||||
@ -416,7 +445,7 @@ namespace LibationFileManager.Templates
|
||||
public static string Description { get; } = Configuration.GetDescription(nameof(Configuration.ChapterFileTemplate)) ?? "";
|
||||
public static string DefaultTemplate { get; } = "<title> [<id>] - <ch# 0> - <ch title>";
|
||||
public static IEnumerable<TagCollection> TagCollections { get; }
|
||||
= chapterPropertyTags.Append(filePropertyTags).Append(audioFilePropertyTags).Append(conditionalTags);
|
||||
= chapterPropertyTags.Append(filePropertyTags).Append(audioFilePropertyTags).Append(conditionalTags).Append(combinedConditionalTags);
|
||||
|
||||
public override IEnumerable<string> Warnings
|
||||
=> NamingTemplate.TagsInUse.Any(t => t.TagName.In(TemplateTags.ChNumber.TagName, TemplateTags.ChNumber0.TagName))
|
||||
@ -429,7 +458,7 @@ namespace LibationFileManager.Templates
|
||||
public static string Name { get; } = "Chapter Title Template";
|
||||
public static string Description { get; } = Configuration.GetDescription(nameof(Configuration.ChapterTitleTemplate)) ?? "";
|
||||
public static string DefaultTemplate => "<ch#> - <title short>: <ch title>";
|
||||
public static IEnumerable<TagCollection> TagCollections { get; } = chapterPropertyTags.Append(conditionalTags);
|
||||
public static IEnumerable<TagCollection> TagCollections { get; } = chapterPropertyTags.Append(conditionalTags).Append(combinedConditionalTags);
|
||||
|
||||
protected override IEnumerable<string> GetTemplatePartsStrings(List<TemplatePart> parts, ReplacementCharacters replacements)
|
||||
=> parts.Select(p => p.Value);
|
||||
|
||||
@ -9,7 +9,7 @@ public static class AssertionExtensions
|
||||
|
||||
[StackTraceHidden]
|
||||
public static void Be<T>(this T? value, T? expectedValue) where T : IEquatable<T>
|
||||
=> Assert.AreEqual(value, expectedValue);
|
||||
=> Assert.AreEqual(expectedValue, value);
|
||||
|
||||
[StackTraceHidden]
|
||||
public static void BeNull<T>(this T? value) where T : class
|
||||
@ -17,7 +17,7 @@ public static class AssertionExtensions
|
||||
|
||||
[StackTraceHidden]
|
||||
public static void BeSameAs<T>(this T? value, T? otherValue)
|
||||
=> Assert.AreSame(value, otherValue);
|
||||
=> Assert.AreSame(otherValue, value);
|
||||
|
||||
[StackTraceHidden]
|
||||
public static void BeFalse(this bool value)
|
||||
@ -33,5 +33,5 @@ public static class AssertionExtensions
|
||||
|
||||
[StackTraceHidden]
|
||||
public static void BeEquivalentTo<T>(this IEnumerable<T?>? value, IEnumerable<T?>? expectedValue)
|
||||
=> CollectionAssert.AreEquivalent(value, expectedValue, EqualityComparer<T?>.Default);
|
||||
=> CollectionAssert.AreEquivalent(expectedValue, value, EqualityComparer<T?>.Default);
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ namespace NamingTemplateTests
|
||||
public string Item1 { get; set; }
|
||||
public string Item2 { get; set; }
|
||||
public string Item3 { get; set; }
|
||||
public string NullItem { get; set; }
|
||||
public int Int1 { get; set; }
|
||||
public bool Condition { get; set; }
|
||||
}
|
||||
@ -25,6 +26,7 @@ namespace NamingTemplateTests
|
||||
public string Item2 { get; set; }
|
||||
public string Item3 { get; set; }
|
||||
public string Item4 { get; set; }
|
||||
public string NullItem { get; set; }
|
||||
public bool Condition { get; set; }
|
||||
}
|
||||
class PropertyClass3
|
||||
@ -33,6 +35,7 @@ namespace NamingTemplateTests
|
||||
public string Item2 { get; set; }
|
||||
public string Item3 { get; set; }
|
||||
public string Item4 { get; set; }
|
||||
public string NullItem { get; set; }
|
||||
public ReferenceType RefType { get; set; }
|
||||
public int? Int2 { get; set; }
|
||||
public bool Condition { get; set; }
|
||||
@ -49,41 +52,54 @@ namespace NamingTemplateTests
|
||||
[TestClass]
|
||||
public class GetPortionFilename
|
||||
{
|
||||
PropertyTagCollection<PropertyClass1> props1 = new()
|
||||
static PropertyTagCollection<PropertyClass1> props1 = new()
|
||||
{
|
||||
{ new TemplateTag { TagName = "item1" }, i => i.Item1 },
|
||||
{ new TemplateTag { TagName = "item2" }, i => i.Item2 },
|
||||
{ new TemplateTag { TagName = "item3" }, i => i.Item3 }
|
||||
{ new TemplateTag { TagName = "item3" }, i => i.Item3 },
|
||||
{ new TemplateTag { TagName = "null_1" }, i => i.NullItem }
|
||||
};
|
||||
|
||||
PropertyTagCollection<PropertyClass2> props2 = new()
|
||||
static PropertyTagCollection<PropertyClass2> props2 = new()
|
||||
{
|
||||
{ new TemplateTag { TagName = "item1" }, i => i.Item1 },
|
||||
{ new TemplateTag { TagName = "item2" }, i => i.Item2 },
|
||||
{ new TemplateTag { TagName = "item3" }, i => i.Item3 },
|
||||
{ new TemplateTag { TagName = "item4" }, i => i.Item4 },
|
||||
{ new TemplateTag { TagName = "null_2" }, i => i.NullItem }
|
||||
};
|
||||
PropertyTagCollection<PropertyClass3> props3 = new(true, GetVal)
|
||||
static PropertyTagCollection<PropertyClass3> props3 = new(true, GetVal)
|
||||
{
|
||||
{ new TemplateTag { TagName = "item3_1" }, i => i.Item1 },
|
||||
{ new TemplateTag { TagName = "item3_2" }, i => i.Item2 },
|
||||
{ new TemplateTag { TagName = "item3_3" }, i => i.Item3 },
|
||||
{ new TemplateTag { TagName = "item3_4" }, i => i.Item4 },
|
||||
{ new TemplateTag { TagName = "null_3" }, i => i.NullItem },
|
||||
{ new TemplateTag { TagName = "reftype" }, i => i.RefType },
|
||||
};
|
||||
ConditionalTagCollection<PropertyClass1> conditional1 = new()
|
||||
{
|
||||
{ new TemplateTag { TagName = "ifc1" }, i => i.Condition },
|
||||
{ new TemplateTag { TagName = "has1" }, HasValue }
|
||||
};
|
||||
ConditionalTagCollection<PropertyClass2> conditional2 = new()
|
||||
{
|
||||
{ new TemplateTag { TagName = "ifc2" }, i => i.Condition },
|
||||
{ new TemplateTag { TagName = "has2" }, HasValue }
|
||||
};
|
||||
ConditionalTagCollection<PropertyClass3> conditional3 = new()
|
||||
{
|
||||
{ new TemplateTag { TagName = "ifc3" }, i => i.Condition },
|
||||
{ new TemplateTag { TagName = "has3" }, HasValue }
|
||||
};
|
||||
|
||||
private static bool HasValue(ITemplateTag templateTag, PropertyClass1 referenceType, string condition)
|
||||
=> props1.TryGetValue(condition, referenceType, out var value) && !string.IsNullOrEmpty(value);
|
||||
private static bool HasValue(ITemplateTag templateTag, PropertyClass2 referenceType, string condition)
|
||||
=> props2.TryGetValue(condition, referenceType, out var value) && !string.IsNullOrEmpty(value);
|
||||
private static bool HasValue(ITemplateTag templateTag, PropertyClass3 referenceType, string condition)
|
||||
=> props3.TryGetValue(condition, referenceType, out var value) && !string.IsNullOrEmpty(value);
|
||||
|
||||
PropertyClass1 propertyClass1 = new()
|
||||
{
|
||||
Item1 = "prop1_item1",
|
||||
@ -123,6 +139,8 @@ namespace NamingTemplateTests
|
||||
[DataRow("<ifc1-><ifc3-><item1><ifc2-><item4><-ifc2><item3_2><-ifc3><-ifc1>", "prop1_item1prop3_item2", 3)]
|
||||
[DataRow("<ifc2-><ifc1-><ifc3-><item1><item4><item3_2><-ifc3><-ifc1><-ifc2>", "", 3)]
|
||||
[DataRow("<!ifc2-><ifc1-><ifc3-><item1><item4><item3_2><-ifc3><-ifc1><-ifc2>", "prop1_item1prop2_item4prop3_item2", 3)]
|
||||
[DataRow("<!has1 null_1-><has2 item1-><has3 item3_2-><item1><item4><item3_2><-has3><-has2><-has1>", "prop1_item1prop2_item4prop3_item2", 3)]
|
||||
[DataRow("<!has1 null_1->null_1 is null, <-has1><has2 item1-><item1><-has2><has3 item3_2-><item3_2><-has3>", "null_1 is null, prop1_item1prop3_item2", 2)]
|
||||
public void test(string inStr, string outStr, int numTags)
|
||||
{
|
||||
var template = NamingTemplate.Parse(inStr, new TagCollection[] { props1, props2, props3, conditional1, conditional2, conditional3 });
|
||||
@ -136,8 +154,63 @@ namespace NamingTemplateTests
|
||||
templateText.Should().Be(outStr);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow("<has1->true<-has1>", "" )]
|
||||
[DataRow("<has2->true<-has2>", "" )]
|
||||
[DataRow("<has3->true<-has3>", "" )]
|
||||
[DataRow("<has4->true<-has4>", "<has4->true<-has4>")]
|
||||
[DataRow("<has1 null_1->true<-has1>", "")]
|
||||
[DataRow("<has2 null_2->true<-has2>", "")]
|
||||
[DataRow("<has3 null_3->true<-has3>", "")]
|
||||
[DataRow("<!has1 null_1->true<-has1>", "true")]
|
||||
[DataRow("<!has2 null_2->true<-has2>", "true")]
|
||||
[DataRow("<!has3 null_3->true<-has3>", "true")]
|
||||
[DataRow("<has1 item1->true<-has1>", "true")]
|
||||
[DataRow("<has2 item1->true<-has2>", "true")]
|
||||
[DataRow("<has3 item3_1->true<-has3>", "true")]
|
||||
[DataRow("<!has1 item1->true<-has1>", "")]
|
||||
[DataRow("<!has2 item1->true<-has2>", "")]
|
||||
[DataRow("<!has3 item3_1->true<-has3>", "")]
|
||||
[DataRow("<has3 item3_1 ->true<-has3>", "true")]
|
||||
public void Has_test(string inStr, string outStr)
|
||||
{
|
||||
var template = NamingTemplate.Parse(inStr, [props1, props2, props3, conditional1, conditional2, conditional3]);
|
||||
|
||||
template.Warnings.Should().HaveCount(1);
|
||||
template.Errors.Should().HaveCount(0);
|
||||
|
||||
var templateText = string.Concat(template.Evaluate(propertyClass3, propertyClass2, propertyClass1).Select(v => v.Value));
|
||||
|
||||
templateText.Should().Be(outStr);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow("<has3item3_1->true<-has3>", "<has3item3_1->true")]
|
||||
[DataRow("< has3 item3_1->true<-has3>", "< has3 item3_1->true")]
|
||||
[DataRow("<has3 item3_1- >true<-has3>", "<has3 item3_1- >true")]
|
||||
[DataRow("<has3 item3_1 >true<-has3>", "<has3 item3_1 >true")]
|
||||
[DataRow("<has3 item3_1>true<-has3>", "<has3 item3_1>true")]
|
||||
[DataRow("<has3 item3_1->true<- has3>", "true<- has3>")]
|
||||
[DataRow("<has3 item3_1->true< has3>", "true< has3>")]
|
||||
[DataRow("<has3 item3_1->true<!has3>", "true<!has3>")]
|
||||
[DataRow("<has3 item3_1->true<has3>", "true<has3>")]
|
||||
[DataRow("<has3 item3_1->true<has3 >", "true<has3 >")]
|
||||
[DataRow("<has3 item3_1->true< -has3>", "true< -has3>")]
|
||||
public void Has_invalid(string inStr, string outStr)
|
||||
{
|
||||
var template = NamingTemplate.Parse(inStr, [props1, props2, props3, conditional1, conditional2, conditional3]);
|
||||
|
||||
template.Warnings.Should().HaveCount(2);
|
||||
template.Errors.Should().HaveCount(0);
|
||||
|
||||
var templateText = string.Concat(template.Evaluate(propertyClass3, propertyClass2, propertyClass1).Select(v => v.Value));
|
||||
|
||||
templateText.Should().Be(outStr);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow("<ifc2-><ifc1-><ifc3-><item1><item4><item3_2><-ifc3><-ifc1><ifc2->", new string[] { "Missing <-ifc2> closing conditional.", "Missing <-ifc2> closing conditional." })]
|
||||
[DataRow("<has2-><has1-><has3-><item1><item4><item3_2><-has3><-has1><has2->", new string[] { "Missing <-has2> closing conditional.", "Missing <-has2> closing conditional." })]
|
||||
[DataRow("<ifc2-><ifc1-><ifc3-><-ifc3><-ifc1><-ifc2>", new string[] { "Should use tags. Eg: <title>" })]
|
||||
[DataRow("<ifc1-><ifc3-><item1><-ifc3><-ifc1><-ifc2>", new string[] { "Missing <ifc2-> open conditional." })]
|
||||
[DataRow("<ifc1-><ifc3-><-ifc3><-ifc1><-ifc2>", new string[] { "Missing <ifc2-> open conditional.", "Should use tags. Eg: <title>" })]
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using AaxDecrypter;
|
||||
using AssertionHelper;
|
||||
using FileManager;
|
||||
using FileManager.NamingTemplate;
|
||||
@ -52,8 +53,13 @@ namespace TemplatesTests
|
||||
BitRate = 128,
|
||||
SampleRate = 44100,
|
||||
Channels = 2,
|
||||
Language = "English"
|
||||
};
|
||||
Language = "English",
|
||||
Subtitle = "An Audible Original Drama",
|
||||
TitleWithSubtitle = "A Study in Scarlet: An Audible Original Drama",
|
||||
Codec = "AAC-LC",
|
||||
FileVersion = "1.0",
|
||||
LibationVersion = "1.0.0",
|
||||
};
|
||||
}
|
||||
|
||||
[TestClass]
|
||||
@ -373,6 +379,55 @@ namespace TemplatesTests
|
||||
.Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow("<has id->true<-has>", "true")]
|
||||
[DataRow("<has title->true<-has>", "true")]
|
||||
[DataRow("<has title short->true<-has>", "true")]
|
||||
[DataRow("<has audible title->true<-has>", "true")]
|
||||
[DataRow("<has audible subtitle->true<-has>", "true")]
|
||||
[DataRow("<has author->true<-has>", "true")]
|
||||
[DataRow("<has first author->true<-has>", "true")]
|
||||
[DataRow("<has narrator->true<-has>", "true")]
|
||||
[DataRow("<has first narrator->true<-has>", "true")]
|
||||
[DataRow("<has series->true<-has>", "true")]
|
||||
[DataRow("<has first series->true<-has>", "true")]
|
||||
[DataRow("<has series#->true<-has>", "true")]
|
||||
[DataRow("<has bitrate->true<-has>", "true")]
|
||||
[DataRow("<has samplerate->true<-has>", "true")]
|
||||
[DataRow("<has channels->true<-has>", "true")]
|
||||
[DataRow("<has codec->true<-has>", "true")]
|
||||
[DataRow("<has file version->true<-has>", "true")]
|
||||
[DataRow("<has libation version->true<-has>", "true")]
|
||||
[DataRow("<has account->true<-has>", "true")]
|
||||
[DataRow("<has account nickname->true<-has>", "true")]
|
||||
[DataRow("<has locale->true<-has>", "true")]
|
||||
[DataRow("<has year->true<-has>", "true")]
|
||||
[DataRow("<has language->true<-has>", "true")]
|
||||
[DataRow("<has language short->true<-has>", "true")]
|
||||
[DataRow("<has file date->true<-has>", "true")]
|
||||
[DataRow("<has pub date->true<-has>", "true")]
|
||||
[DataRow("<has date added->true<-has>", "true")]
|
||||
[DataRow("<has ch count->true<-has>", "true")]
|
||||
[DataRow("<has ch title->true<-has>", "true")]
|
||||
[DataRow("<has ch#->true<-has>", "true")]
|
||||
[DataRow("<has ch# 0->true<-has>", "true")]
|
||||
[DataRow("<has FAKE->true<-has>", "")]
|
||||
public void HasValue_test(string template, string expected)
|
||||
{
|
||||
var bookDto = GetLibraryBook();
|
||||
var multiDto = new MultiConvertFileProperties
|
||||
{
|
||||
PartsPosition = 1,
|
||||
PartsTotal = 2,
|
||||
Title = bookDto.Title,
|
||||
};
|
||||
|
||||
Templates.TryGetTemplate<Templates.FileTemplate>(template, out var fileTemplate).Should().BeTrue();
|
||||
fileTemplate
|
||||
.GetFilename(bookDto, multiDto, "", "", Replacements)
|
||||
.PathWithoutPrefix
|
||||
.Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow("<series>", "Series A, Series B, Series C, Series D")]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user