diff --git a/source/net/filebot/format/ExpressionFormatMethods.java b/source/net/filebot/format/ExpressionFormatMethods.java index 7c189e86..b155fd99 100644 --- a/source/net/filebot/format/ExpressionFormatMethods.java +++ b/source/net/filebot/format/ExpressionFormatMethods.java @@ -7,8 +7,8 @@ import static net.filebot.MediaTypes.*; import static net.filebot.WebServices.*; import static net.filebot.format.ExpressionFormatFunctions.*; import static net.filebot.media.MediaDetection.*; +import static net.filebot.similarity.Normalization.*; import static net.filebot.util.RegularExpressions.*; -import static net.filebot.util.StringUtilities.*; import java.io.File; import java.io.IOException; @@ -41,46 +41,44 @@ import java.util.stream.IntStream; import java.util.stream.Stream; import org.codehaus.groovy.runtime.DefaultGroovyMethods; +import org.codehaus.groovy.runtime.StringGroovyMethods; import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation; import com.ibm.icu.text.Transliterator; import groovy.lang.Closure; -import net.filebot.Language; -import net.filebot.similarity.Normalization; import net.filebot.util.FileUtilities; import net.filebot.web.Episode; import net.filebot.web.EpisodeInfo; import net.filebot.web.Movie; import net.filebot.web.Person; import net.filebot.web.SeriesInfo; -import net.filebot.web.SimpleDate; public class ExpressionFormatMethods { /** - * Convenience methods for String.toLowerCase() and String.toUpperCase() + * Convert all characters to lower case/ + * + * e.g. "Firelfy" ➔ "firefly" */ public static String lower(String self) { return self.toLowerCase(); } + /** + * Convert all characters to upper case. + * + * e.g. "Firelfy" ➔ "FIREFLY" + */ public static String upper(String self) { return self.toUpperCase(); } /** - * Pad strings or numbers with given characters ('0' by default). + * Pad to length using the given character. * - * e.g. "1" -> "01" + * e.g. "1" ➔ "01" */ - public static String pad(String self, int length, String padding) { - while (self.length() < length) { - self = padding + self; - } - return self; - } - public static String pad(String self, int length) { return pad(self, length, "0"); } @@ -89,12 +87,21 @@ public class ExpressionFormatMethods { return pad(self.toString(), length, "0"); } + public static String pad(CharSequence self, int length, CharSequence padding) { + return StringGroovyMethods.padLeft(self, length, padding); + } + + /** + * Round decimal number to precision. + * + * e.g. "3.14" ➔ "3.1" + */ public static double round(Number self, int precision) { return DefaultGroovyMethods.round(self.doubleValue(), precision); } /** - * Return a substring matching the given pattern or break. + * Match pattern and return or unwind if pattern cannot be found. */ public static String match(String self, String pattern) throws Exception { return match(self, pattern, -1); @@ -110,7 +117,7 @@ public class ExpressionFormatMethods { } /** - * Return a list of all matching patterns or break. + * Match all occurrences of the given pattern or unwind if pattern cannot be found. */ public static List matchAll(String self, String pattern) throws Exception { return matchAll(self, pattern, -1); @@ -129,7 +136,7 @@ public class ExpressionFormatMethods { return matches; } - public static String firstCapturingGroup(Matcher self, int matchGroup) throws Exception { + private static String firstCapturingGroup(Matcher self, int matchGroup) throws Exception { int g = matchGroup < 0 ? self.groupCount() > 0 ? 1 : 0 : matchGroup; // return the entire match @@ -143,73 +150,69 @@ public class ExpressionFormatMethods { }); } - public static String replaceAll(String self, String pattern) { - return self.replaceAll(pattern, ""); - } - public static String removeAll(String self, String pattern) { return compile(pattern, CASE_INSENSITIVE | UNICODE_CHARACTER_CLASS | MULTILINE).matcher(self).replaceAll("").trim(); } + /** + * Strip characters that aren't allowed on Windows from the given filename. + * + * e.g. "Sissi: The Young Empress" ➔ "Sissi The Young Empress" + */ public static String removeIllegalCharacters(String self) { - return FileUtilities.validateFileName(Normalization.normalizeQuotationMarks(self)); + return FileUtilities.validateFileName(normalizeQuotationMarks(self)); } /** - * Replace space characters with a given characters. + * Replace all spaces. * - * e.g. "Doctor Who" -> "Doctor_Who" + * e.g. "Doctor Who" ➔ "Doctor_Who" */ public static String space(String self, String replacement) { - return Normalization.normalizeSpace(self, replacement); + return normalizeSpace(self, replacement); } /** - * Replace colon to make the name more Windows friendly. + * Replace all colons. * - * e.g. "Sissi: The Young Empress" -> "Sissi - The Young Empress" + * e.g. "Sissi: The Young Empress" ➔ "Sissi - The Young Empress" */ public static String colon(String self, String colon) { return COLON.matcher(self).replaceAll(colon); } - /** - * Replace colon to make the name more Windows friendly. - * - * e.g. "12:00 A.M.-1:00 A.M." -> "12.00 A.M.-1.00 A.M." - */ public static String colon(String self, String ratio, String colon) { return COLON.matcher(RATIO.matcher(self).replaceAll(ratio)).replaceAll(colon); } /** - * Replace slash and backslash to make sure the result is not a file path. + * Replace all slashes. * - * e.g. "V_MPEG4/ISO/AVC" -> "V_MPEG4.ISO.AVC" + * e.g. "V_MPEG4/ISO/AVC" ➔ "V_MPEG4.ISO.AVC" */ public static String slash(String self, String replacement) { return SLASH.matcher(self).replaceAll(replacement); } /** - * Upper-case all initials. + * Convert all initial characters to upper case. * - * e.g. "The Day a new Demon was born" -> "The Day A New Demon Was Born" + * e.g. "The Day a new Demon was born" ➔ "The Day A New Demon Was Born" */ public static String upperInitial(String self) { return replaceHeadTail(self, String::toUpperCase, String::toString); } /** - * Lower-case all letters that are not initials. + * Convert all trailing characters to lower case. * - * e.g. "Gundam SEED" -> "Gundam Seed" + * e.g. "Gundam SEED" ➔ "Gundam Seed" */ public static String lowerTrail(String self) { return replaceHeadTail(self, String::toString, String::toLowerCase); } - public static String replaceHeadTail(String self, Function head, Function tail) { + private static String replaceHeadTail(String self, Function head, Function tail) { Matcher matcher = compile("\\b(['`´]|\\p{Alnum})(\\p{Alnum}*)\\b", UNICODE_CHARACTER_CLASS).matcher(self); StringBuffer buffer = new StringBuffer(); @@ -220,6 +223,11 @@ public class ExpressionFormatMethods { return matcher.appendTail(buffer).toString(); } + /** + * Convert to sort name. + * + * e.g. "The Walking Dead" ➔ "Walking Dead" + */ public static String sortName(String self) { return sortName(self, "$2"); } @@ -228,14 +236,6 @@ public class ExpressionFormatMethods { return compile("^(The|A|An)\\s(.+)", CASE_INSENSITIVE | UNICODE_CHARACTER_CLASS).matcher(self).replaceFirst(replacement).trim(); } - public static String initialName(String self) { - String[] words = SPACE.split(self); - for (int i = 0; i < words.length - 1; i++) { - words[i] = words[i].charAt(0) + "."; - } - return join(words, " "); - } - public static String sortInitial(String self) { // use primary initial, ignore The XY, A XY, etc char c = ascii(sortName(self)).charAt(0); @@ -250,12 +250,16 @@ public class ExpressionFormatMethods { } /** - * Get acronym, i.e. first letter of each word. + * Reduce first name to initials. * - * e.g. "Deep Space 9" -> "DS9" + * e.g. "James Cameron" ➔ "J. Cameron" */ - public static String acronym(String self) { - return compile("\\s|\\B\\p{Alnum}+", UNICODE_CHARACTER_CLASS).matcher(space(self, " ")).replaceAll(""); + public static String initialName(String self) { + String[] words = SPACE.split(self); + for (int i = 0; i < words.length - 1; i++) { + words[i] = words[i].charAt(0) + "."; + } + return String.join(" ", words); } public static String truncate(String self, int limit) { @@ -281,7 +285,7 @@ public class ExpressionFormatMethods { } /** - * Return substring before the given pattern. + * Match substring before the given pattern or return the original value. */ public static String before(String self, String pattern) { Matcher matcher = compile(pattern, CASE_INSENSITIVE | UNICODE_CHARACTER_CLASS).matcher(self); @@ -291,7 +295,7 @@ public class ExpressionFormatMethods { } /** - * Return substring after the given pattern. + * Match substring before the given pattern or return the original value. */ public static String after(String self, String pattern) { Matcher matcher = compile(pattern, CASE_INSENSITIVE | UNICODE_CHARACTER_CLASS).matcher(self); @@ -301,7 +305,7 @@ public class ExpressionFormatMethods { } /** - * Find a matcher that matches the given pattern (case-insensitive) + * Find match in case-insensitive mode. */ public static boolean findMatch(String self, String pattern) { if (pattern == null || pattern.isEmpty()) @@ -311,7 +315,7 @@ public class ExpressionFormatMethods { } /** - * Find a matcher that matches the given pattern (case-insensitive) but matches only if the pattern is enclosed in word-boundaries + * Find match in between word boundaries in case-insensitive mode. */ public static boolean findWordMatch(String self, String pattern) { if (pattern == null || pattern.isEmpty()) @@ -321,9 +325,9 @@ public class ExpressionFormatMethods { } /** - * Replace trailing parenthesis including any leading whitespace. + * Replace trailing parenthesis. * - * e.g. "The IT Crowd (UK)" -> "The IT Crowd" + * e.g. "The IT Crowd (UK)" ➔ "The IT Crowd" */ public static String replaceTrailingBrackets(String self) { return replaceTrailingBrackets(self, ""); @@ -334,9 +338,9 @@ public class ExpressionFormatMethods { } /** - * Replace 'part identifier'. + * Replace trailing part number. * - * e.g. "Today Is the Day: Part 1" -> "Today Is the Day, Part 1" or "Today Is the Day (1)" -> "Today Is the Day, Part 1" + * e.g. "Today Is the Day (1)" ➔ "Today Is the Day, Part 1" */ public static String replacePart(String self) { return replacePart(self, ""); @@ -358,9 +362,18 @@ public class ExpressionFormatMethods { } /** - * Replace numbers 1..12 with Roman numerals - * - * e.g. "Star Wars: Episode 4" -> "Star Wars: Episode IV" + * Convert to acronym. + * + * e.g. "Deep Space 9" ➔ "DS9" + */ + public static String acronym(String self) { + return compile("\\s|\\B\\p{Alnum}+", UNICODE_CHARACTER_CLASS).matcher(space(self, " ")).replaceAll(""); + } + + /** + * Replace numbers 1..12 with Roman numerals. + * + * e.g. "Star Wars: Episode 4" ➔ "Star Wars: Episode IV" */ public static String roman(String self) { TreeMap numerals = new TreeMap(); @@ -388,8 +401,10 @@ public class ExpressionFormatMethods { } /** - * Apply ICU transliteration + * Apply any ICU script transliteration. * + * e.g. "中国" ➔ "zhōng guó" + * * @see http://userguide.icu-project.org/transforms/general */ public static String transliterate(String self, String transformIdentifier) { @@ -397,9 +412,9 @@ public class ExpressionFormatMethods { } /** - * Convert Unicode to ASCII as best as possible. Works with most alphabets/scripts used in the world. + * Convert Unicode characters to ASCII. * - * e.g. "Österreich" -> "Osterreich" "カタカナ" -> "katakana" + * e.g. "カタカナ" ➔ "katakana" */ public static String ascii(String self) { return ascii(self, " "); @@ -410,7 +425,7 @@ public class ExpressionFormatMethods { } public static String asciiQuotes(String self) { - return Normalization.normalizeQuotationMarks(self); + return normalizeQuotationMarks(self); } public static boolean isLatin(String self) { @@ -418,7 +433,7 @@ public class ExpressionFormatMethods { } /** - * Replace multiple replacement pairs + * Apply multiple replacements. * * e.g. replace(ä:'ae', ö:'oe', ü:'ue') */ @@ -460,6 +475,10 @@ public class ExpressionFormatMethods { throw new Exception("Collection did not yield any values: " + self); } + public static List bounds(Iterable self) { + return Stream.of(DefaultGroovyMethods.min(self), DefaultGroovyMethods.max(self)).filter(Objects::nonNull).distinct().collect(toList()); + } + /** * Unwind if an object does not satisfy the given predicate * @@ -474,7 +493,9 @@ public class ExpressionFormatMethods { } /** - * File utilities + * Add values to the filename. + * + * e.g. "Avatar (2009).mp4" ➔ "Avatar (2009) [720p].mp4" */ public static File derive(File self, Object tag, Object... tagN) { // e.g. plex.derive{" by $director"}{" [$vc, $ac]"} @@ -493,25 +514,9 @@ public class ExpressionFormatMethods { return new File(self.getParentFile(), concat(name, slash(concat(tag, null, tagN), ""), extension)); } - public static File getRoot(File self) { - return FileUtilities.listPath(self).get(0); - } - - public static File getTail(File self) { - return FileUtilities.getRelativePathTail(self, FileUtilities.listPath(self).size() - 1); - } - - public static List listPath(File self) { - return FileUtilities.listPath(self); - } - - public static List listPath(File self, int tailSize) { - return FileUtilities.listPath(FileUtilities.getRelativePathTail(self, tailSize)); - } - - public static File getRelativePathTail(File self, int tailSize) { - return FileUtilities.getRelativePathTail(self, tailSize); - } + /** + * File utilities + */ public static long getDiskSpace(File self) { List list = FileUtilities.listPath(self); @@ -535,6 +540,26 @@ public class ExpressionFormatMethods { return attr.lastModifiedTime().toMillis(); } + public static File getRoot(File self) { + return FileUtilities.listPath(self).get(0); + } + + public static File getTail(File self) { + return FileUtilities.getRelativePathTail(self, FileUtilities.listPath(self).size() - 1); + } + + public static List listPath(File self) { + return FileUtilities.listPath(self); + } + + public static List listPath(File self, int tailSize) { + return FileUtilities.listPath(FileUtilities.getRelativePathTail(self, tailSize)); + } + + public static File getRelativePathTail(File self, int tailSize) { + return FileUtilities.getRelativePathTail(self, tailSize); + } + public static LocalDateTime toDate(Long self) { return LocalDateTime.ofInstant(Instant.ofEpochMilli(self), ZoneOffset.systemDefault()); } @@ -561,26 +586,9 @@ public class ExpressionFormatMethods { return Locale.forLanguageTag(self); } - public static String plus(String self, Closure other) { - return concat(self, other); - } - - public static String plus(Closure self, Object other) { - return concat(self, other); - } - - public static String plus(Language self, Object other) { - return concat(self, other); - } - - public static String plus(SimpleDate self, Object other) { - return concat(self, other); - } - - public static List bounds(Iterable self) { - return Stream.of(DefaultGroovyMethods.min(self), DefaultGroovyMethods.max(self)).filter(Objects::nonNull).distinct().collect(toList()); - } - + /** + * Date utilities + */ public static String format(Temporal self, String pattern) { return DateTimeFormatter.ofPattern(pattern).format(self); } @@ -598,7 +606,7 @@ public class ExpressionFormatMethods { } /** - * Episode utilities (EXPERIMENTAL) + * Episode utilities */ public static EpisodeInfo getInfo(Episode self) throws Exception { if (TheTVDB.getIdentifier().equals(self.getSeriesInfo().getDatabase())) {