Implements conditional expressions for template system, #417

This commit is contained in:
Rhet Turnbull
2021-04-13 06:20:56 -07:00
parent e215c200c7
commit 03f8b2bc6e
16 changed files with 340 additions and 44 deletions

113
README.md
View File

@@ -791,8 +791,8 @@ example "{title}" which would resolve to the title of the photo.
Template statements may contain one or more modifiers. The full syntax is:
"pretext{delim+template_field:subfield|filter(path_sep)[find,replace]?bool_value
,default}posttext"
"pretext{delim+template_field:subfield|filter(path_sep)[find,replace]
conditional?bool_value,default}posttext"
Template statements are white-space sensitive meaning that white space (spaces,
tabs) changes the meaning of the template statement.
@@ -865,13 +865,68 @@ another find|replace pair. e.g. to replace both "/" and ":" in album name:
"{album[/,-|:,-]}". find/replace pairs are not limited to single characters.
The "|" character cannot be used in a find/replace pair.
?bool_value: Template fields may be evaluated as boolean by appending "?" after
the field name (and following "(path_sep)" or "[find/replace]". If a field is
True (e.g. photo is HDR and field is "{hdr}") or has any value, the value
following the "?" will be used to render the template instead of the actual
field value. If the template field evaluates to False (e.g. in above example,
photo is not HDR) or has no value (e.g. photo has no title and field is
"{title}") then the default value following a "," will be used.
conditional: optional conditional expression that is evaluated as boolean
(True/False) for use with the ?bool_value modifier. Conditional expressions
take the form ' not operator value' where not is an optional modifier that
negates the operator. Note: the space before the conditional expression is
required if you use a conditional expression. Valid comparison operators are:
• contains: template field contains value, similar to python's in
• matches: template field contains exactly value, unlike contains: does not
match partial matches
• startswith: template field starts with value
• endswith: template field ends with value
• <=: template field is less than or equal to value
• >=: template field is greater than or equal to value
• <: template field is less than value
• >: template field is greater than value
• ==: template field equals value
• !=: template field does not equal value
The value part of the conditional expression is treated as a bare (unquoted)
word/phrase. Multiple values may be separated by '|' (the pipe symbol). value
is itself a template statement so you can use one or more template fields in
value which will be resolved before the comparison occurs.
For example:
• {keyword matches Beach} resolves to True if 'Beach' is a keyword. It would
not match keyword 'BeachDay'.
• {keyword contains Beach} resolves to True if any keyword contains the word
'Beach' so it would match both 'Beach' and 'BeachDay'.
• {photo.score.overall > 0.7} resolves to True if the photo's overall aesthetic
score is greater than 0.7.
• {keyword|lower contains beach} uses the lower case filter to do
case-insensitive matching to match any keyword that contains the word
'beach'.
• {keyword|lower not contains beach} uses the not modifier to negate the
comparison so this resolves to True if there is no keyword that matches
'beach'.
Examples: to export photos that contain certain keywords with the osxphotos
export command's --directory option:
--directory "{keyword|lower matches
travel|vacation?Travel-Photos,Not-Travel-Photos}"
This exports any photo that has keywords 'travel' or 'vacation' into a directory
'Travel-Photos' and all other photos into directory 'Not-Travel-Photos'.
This can be used to rename files as well, for example: --filename
"{favorite?Favorite-{original_name},{original_name}}"
This renames any photo that is a favorite as 'Favorite-ImageName.jpg' (where
'ImageName.jpg' is the original name of the photo) and all other photos with the
unmodified original name.
?bool_value: Template fields may be evaluated as boolean (True/False) by
appending "?" after the field name (and following "(path_sep)" or
"[find/replace]". If a field is True (e.g. photo is HDR and field is "{hdr}")
or has any value, the value following the "?" will be used to render the
template instead of the actual field value. If the template field evaluates to
False (e.g. in above example, photo is not HDR) or has no value (e.g. photo has
no title and field is "{title}") then the default value following a "," will be
used.
e.g. if photo is an HDR image,
@@ -1156,6 +1211,7 @@ Substitution Description
{comma} A comma: ','
{semicolon} A semicolon: ';'
{questionmark} A question mark: '?'
{pipe} A vertical pipe: '|'
{openbrace} An open brace: '{'
{closebrace} A close brace: '}'
@@ -2101,7 +2157,7 @@ In its simplest form, a template statement has the form: `"{template_field}"`, f
Template statements may contain one or more modifiers. The full syntax is:
`"pretext{delim+template_field:subfield|filter(path_sep)[find,replace]?bool_value,default}posttext"`
`"pretext{delim+template_field:subfield|filter(path_sep)[find,replace] conditional?bool_value,default}posttext"`
Template statements are white-space sensitive meaning that white space (spaces, tabs) changes the meaning of the template statement.
@@ -2159,7 +2215,41 @@ e.g. If Photo is in `Album1` in `Folder1`:
`[find|replace]`: optional text replacement to perform on rendered template value. For example, to replace "/" in an album name, you could use the template `"{album[/,-]}"`. Multiple replacements can be made by appending "|" and adding another find|replace pair. e.g. to replace both "/" and ":" in album name: `"{album[/,-|:,-]}"`. find/replace pairs are not limited to single characters. The "|" character cannot be used in a find/replace pair.
`?bool_value`: Template fields may be evaluated as boolean by appending "?" after the field name (and following "(path_sep)" or "[find/replace]". If a field is True (e.g. photo is HDR and field is `"{hdr}"`) or has any value, the value following the "?" will be used to render the template instead of the actual field value. If the template field evaluates to False (e.g. in above example, photo is not HDR) or has no value (e.g. photo has no title and field is `"{title}"`) then the default value following a "," will be used.
`conditional`: optional conditional expression that is evaluated as boolean (True/False) for use with the `?bool_value` modifier. Conditional expressions take the form '` not operator value`' where `not` is an optional modifier that negates the `operator`. Note: the space before the conditional expression is required if you use a conditional expression. Valid comparison operators are:
- `contains`: template field contains value, similar to python's `in`
- `matches`: template field contains exactly value, unlike `contains`: does not match partial matches
- `startswith`: template field starts with value
- `endswith`: template field ends with value
- `<=`: template field is less than or equal to value
- `>=`: template field is greater than or equal to value
- `<`: template field is less than value
- `>`: template field is greater than value
- `==`: template field equals value
- `!=`: template field does not equal value
The `value` part of the conditional expression is treated as a bare (unquoted) word/phrase. Multiple values may be separated by '|' (the pipe symbol). `value` is itself a template statement so you can use one or more template fields in `value` which will be resolved before the comparison occurs.
For example:
- `{keyword matches Beach}` resolves to True if 'Beach' is a keyword. It would not match keyword 'BeachDay'.
- `{keyword contains Beach}` resolves to True if any keyword contains the word 'Beach' so it would match both 'Beach' and 'BeachDay'.
- `{photo.score.overall > 0.7}` resolves to True if the photo's overall aesthetic score is greater than 0.7.
- `{keyword|lower contains beach}` uses the lower case filter to do case-insensitive matching to match any keyword that contains the word 'beach'.
- `{keyword|lower not contains beach}` uses the `not` modifier to negate the comparison so this resolves to True if there is no keyword that matches 'beach'.
Examples: to export photos that contain certain keywords with the `osxphotos export` command's `--directory` option:
`--directory "{keyword|lower matches travel|vacation?Travel-Photos,Not-Travel-Photos}"`
This exports any photo that has keywords 'travel' or 'vacation' into a directory 'Travel-Photos' and all other photos into directory 'Not-Travel-Photos'.
This can be used to rename files as well, for example:
`--filename "{favorite?Favorite-{original_name},{original_name}}"`
This renames any photo that is a favorite as 'Favorite-ImageName.jpg' (where 'ImageName.jpg' is the original name of the photo) and all other photos with the unmodified original name.
`?bool_value`: Template fields may be evaluated as boolean (True/False) by appending "?" after the field name (and following "(path_sep)" or "[find/replace]". If a field is True (e.g. photo is HDR and field is `"{hdr}"`) or has any value, the value following the "?" will be used to render the template instead of the actual field value. If the template field evaluates to False (e.g. in above example, photo is not HDR) or has no value (e.g. photo has no title and field is `"{title}"`) then the default value following a "," will be used.
e.g. if photo is an HDR image,
@@ -2781,6 +2871,7 @@ The following template field substitutions are availabe for use with `PhotoInfo.
|{uuid}|Photo's internal universally unique identifier (UUID) for the photo, a 36-character string unique to the photo, e.g. '128FB4C6-0B16-4E7D-9108-FB2E90DA1546'|
|{comma}|A comma: ','|
|{semicolon}|A semicolon: ';'|
|{questionmark}|A question mark: '?'|
|{pipe}|A vertical pipe: '|'|
|{openbrace}|An open brace: '{'|
|{closebrace}|A close brace: '}'|

View File

@@ -1,4 +1,4 @@
# Sphinx build info version 1
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
config: 0179796ca3088aa7a0ec190387bfe684
config: 3a4409f9ef528dee2e4ce25074f0e795
tags: 645f666f9bcd5a90fca523b33c5a78b7

View File

@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Overview: module code &#8212; osxphotos 0.41.10 documentation</title>
<title>Overview: module code &#8212; osxphotos 0.42.00 documentation</title>
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="../_static/alabaster.css" type="text/css" />
<script id="documentation_options" data-url_root="../" src="../_static/documentation_options.js"></script>

View File

@@ -1,6 +1,6 @@
var DOCUMENTATION_OPTIONS = {
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
VERSION: '0.41.10',
VERSION: '0.42.00',
LANGUAGE: 'None',
COLLAPSE_INDEX: false,
BUILDER: 'html',

View File

@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>osxphotos command line interface (CLI) &#8212; osxphotos 0.41.10 documentation</title>
<title>osxphotos command line interface (CLI) &#8212; osxphotos 0.42.00 documentation</title>
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
<script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>

View File

@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Index &#8212; osxphotos 0.41.10 documentation</title>
<title>Index &#8212; osxphotos 0.42.00 documentation</title>
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
<script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>

View File

@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Welcome to osxphotoss documentation! &#8212; osxphotos 0.41.10 documentation</title>
<title>Welcome to osxphotoss documentation! &#8212; osxphotos 0.42.00 documentation</title>
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
<script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>

View File

@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>osxphotos &#8212; osxphotos 0.41.10 documentation</title>
<title>osxphotos &#8212; osxphotos 0.42.00 documentation</title>
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
<script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>

Binary file not shown.

View File

@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>osxphotos package &#8212; osxphotos 0.41.10 documentation</title>
<title>osxphotos package &#8212; osxphotos 0.42.00 documentation</title>
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
<script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>

View File

@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Search &#8212; osxphotos 0.41.10 documentation</title>
<title>Search &#8212; osxphotos 0.42.00 documentation</title>
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/alabaster.css" type="text/css" />

View File

@@ -1,3 +1,3 @@
""" version info """
__version__ = "0.41.11"
__version__ = "0.42.00"

View File

@@ -4,7 +4,7 @@ In its simplest form, a template statement has the form: `"{template_field}"`, f
Template statements may contain one or more modifiers. The full syntax is:
`"pretext{delim+template_field:subfield|filter(path_sep)[find,replace]?bool_value,default}posttext"`
`"pretext{delim+template_field:subfield|filter(path_sep)[find,replace] conditional?bool_value,default}posttext"`
Template statements are white-space sensitive meaning that white space (spaces, tabs) changes the meaning of the template statement.
@@ -62,7 +62,41 @@ e.g. If Photo is in `Album1` in `Folder1`:
`[find|replace]`: optional text replacement to perform on rendered template value. For example, to replace "/" in an album name, you could use the template `"{album[/,-]}"`. Multiple replacements can be made by appending "|" and adding another find|replace pair. e.g. to replace both "/" and ":" in album name: `"{album[/,-|:,-]}"`. find/replace pairs are not limited to single characters. The "|" character cannot be used in a find/replace pair.
`?bool_value`: Template fields may be evaluated as boolean by appending "?" after the field name (and following "(path_sep)" or "[find/replace]". If a field is True (e.g. photo is HDR and field is `"{hdr}"`) or has any value, the value following the "?" will be used to render the template instead of the actual field value. If the template field evaluates to False (e.g. in above example, photo is not HDR) or has no value (e.g. photo has no title and field is `"{title}"`) then the default value following a "," will be used.
`conditional`: optional conditional expression that is evaluated as boolean (True/False) for use with the `?bool_value` modifier. Conditional expressions take the form '` not operator value`' where `not` is an optional modifier that negates the `operator`. Note: the space before the conditional expression is required if you use a conditional expression. Valid comparison operators are:
- `contains`: template field contains value, similar to python's `in`
- `matches`: template field contains exactly value, unlike `contains`: does not match partial matches
- `startswith`: template field starts with value
- `endswith`: template field ends with value
- `<=`: template field is less than or equal to value
- `>=`: template field is greater than or equal to value
- `<`: template field is less than value
- `>`: template field is greater than value
- `==`: template field equals value
- `!=`: template field does not equal value
The `value` part of the conditional expression is treated as a bare (unquoted) word/phrase. Multiple values may be separated by '|' (the pipe symbol). `value` is itself a template statement so you can use one or more template fields in `value` which will be resolved before the comparison occurs.
For example:
- `{keyword matches Beach}` resolves to True if 'Beach' is a keyword. It would not match keyword 'BeachDay'.
- `{keyword contains Beach}` resolves to True if any keyword contains the word 'Beach' so it would match both 'Beach' and 'BeachDay'.
- `{photo.score.overall > 0.7}` resolves to True if the photo's overall aesthetic score is greater than 0.7.
- `{keyword|lower contains beach}` uses the lower case filter to do case-insensitive matching to match any keyword that contains the word 'beach'.
- `{keyword|lower not contains beach}` uses the `not` modifier to negate the comparison so this resolves to True if there is no keyword that matches 'beach'.
Examples: to export photos that contain certain keywords with the `osxphotos export` command's `--directory` option:
`--directory "{keyword|lower matches travel|vacation?Travel-Photos,Not-Travel-Photos}"`
This exports any photo that has keywords 'travel' or 'vacation' into a directory 'Travel-Photos' and all other photos into directory 'Not-Travel-Photos'.
This can be used to rename files as well, for example:
`--filename "{favorite?Favorite-{original_name},{original_name}}"`
This renames any photo that is a favorite as 'Favorite-ImageName.jpg' (where 'ImageName.jpg' is the original name of the photo) and all other photos with the unmodified original name.
`?bool_value`: Template fields may be evaluated as boolean (True/False) by appending "?" after the field name (and following "(path_sep)" or "[find/replace]". If a field is True (e.g. photo is HDR and field is `"{hdr}"`) or has any value, the value following the "?" will be used to render the template instead of the actual field value. If the template field evaluates to False (e.g. in above example, photo is not HDR) or has no value (e.g. photo has no title and field is `"{title}"`) then the default value following a "," will be used.
e.g. if photo is an HDR image,

View File

@@ -120,6 +120,7 @@ TEMPLATE_SUBSTITUTIONS = {
"{uuid}": "Photo's internal universally unique identifier (UUID) for the photo, a 36-character string unique to the photo, e.g. '128FB4C6-0B16-4E7D-9108-FB2E90DA1546'",
"{comma}": "A comma: ','",
"{semicolon}": "A semicolon: ';'",
"{questionmark}": "A question mark: '?'",
"{pipe}": "A vertical pipe: '|'",
"{openbrace}": "An open brace: '{'",
"{closebrace}": "A close brace: '}'",
@@ -191,6 +192,7 @@ PUNCTUATION = {
"closeparens": ")",
"openbracket": "[",
"closebracket": "]",
"questionmark": "?",
}
@@ -323,17 +325,6 @@ class PhotoTemplate:
unmatched=unmatched,
)
# process find/replace
if ts.template and ts.template.findreplace:
new_results = []
for result in results:
for pair in ts.template.findreplace.pairs:
find = pair.find or ""
repl = pair.replace or ""
result = result.replace(find, repl)
new_results.append(result)
results = new_results
rendered_strings = results
if filename:
@@ -430,6 +421,30 @@ class PhotoTemplate:
else:
default = []
# process conditional
if ts.template.conditional is not None:
operator = ts.template.conditional.operator
negation = ts.template.conditional.negation
if ts.template.conditional.value is not None:
# conditional value is also a TemplateString
conditional_value, u = self._render_statement(
ts.template.conditional.value,
none_str=none_str,
path_sep=path_sep,
expand_inplace=expand_inplace,
inplace_sep=inplace_sep,
filename=filename,
dirname=dirname,
)
unmatched.extend(u)
else:
# this shouldn't happen
conditional_value = [""]
else:
operator = None
negation = None
conditional_value = []
vals = []
if field in SINGLE_VALUE_SUBSTITUTIONS:
vals = self.get_template_value(
@@ -458,14 +473,6 @@ class PhotoTemplate:
vals = [val for val in vals if val is not None]
if is_bool:
if not vals:
vals = default
else:
vals = bool_val
elif not vals:
vals = default or [none_str]
if expand_inplace or delim is not None:
sep = delim if delim is not None else inplace_sep
vals = [sep.join(sorted(vals))]
@@ -473,6 +480,99 @@ class PhotoTemplate:
for filter_ in filters:
vals = self.get_template_value_filter(filter_, vals)
# process find/replace
if ts.template.findreplace:
new_vals = []
for val in vals:
for pair in ts.template.findreplace.pairs:
find = pair.find or ""
repl = pair.replace or ""
val = val.replace(find, repl)
new_vals.append(val)
vals = new_vals
if operator:
# have a conditional operator
def string_test(test_function):
""" Perform string comparison using test_function; closure to capture conditional_value, vals, negation """
match = False
for c in conditional_value:
for v in vals:
if test_function(v, c):
match = True
break
if match:
break
if (match and not negation) or (negation and not match):
return ["True"]
else:
return []
def comparison_test(test_function):
""" Perform numerical comparisons using test_function; closure to capture conditional_val, vals, negation """
if len(vals) != 1 or len(conditional_value) != 1:
raise ValueError(
f"comparison operators may only be used with a single value: {vals} {conditional_value}"
)
try:
match = (
True
if test_function(
float(vals[0]), float(conditional_value[0])
)
else False
)
if (match and not negation) or (negation and not match):
return ["True"]
else:
return []
except ValueError as e:
raise ValueError(
f"comparison operators may only be used with values that can be converted to numbers: {vals} {conditional_value}"
)
if operator in ["contains", "matches", "startswith", "endswith"]:
# process any "or" values separated by "|"
temp_values = []
for c in conditional_value:
temp_values.extend(c.split("|"))
conditional_value = temp_values
if operator == "contains":
vals = string_test(lambda v, c: c in v)
elif operator == "matches":
vals = string_test(lambda v, c: v == c)
elif operator == "startswith":
vals = string_test(lambda v, c: v.startswith(c))
elif operator == "endswith":
vals = string_test(lambda v, c: v.endswith(c))
elif operator == "==":
match = sorted(vals) == sorted(conditional_value)
if (match and not negation) or (negation and not match):
vals = ["True"]
else:
vals = []
elif operator == "!=":
match = sorted(vals) != sorted(conditional_value)
if (match and not negation) or (negation and not match):
vals = ["True"]
else:
vals = []
elif operator == "<":
vals = comparison_test(lambda v, c: v < c)
elif operator == "<=":
vals = comparison_test(lambda v, c: v <= c)
elif operator == ">":
vals = comparison_test(lambda v, c: v > c)
elif operator == ">=":
vals = comparison_test(lambda v, c: v >= c)
if is_bool:
vals = default if not vals else bool_val
elif not vals:
vals = default or [none_str]
pre = ts.pre or ""
post = ts.post or ""

View File

@@ -1,6 +1,6 @@
// OSXPhotos Template Language (OTL)
// a TemplateString has format:
// pre{delim+template_field:subfield|filter(path_sep)[find,replace]?bool_value,default}post
// pre{delim+template_field:subfield|filter(path_sep)[find,replace] conditional?bool_value,default}post
// a TemplateStatement may contain zero or more TemplateStrings
// The pre and post are optional strings
// The template itself (inside the {}) is also optional but if present
@@ -25,6 +25,7 @@ Template:
filter=Filter
pathsep=PathSep
findreplace=FindReplace
conditional=Conditional
bool=Boolean
default=Default
"}"
@@ -32,7 +33,7 @@ Template:
;
NON_TEMPLATE_STRING:
/[^\{\},]*/
/[^\{\},\?]*/
;
Delim:
@@ -76,6 +77,24 @@ FILTER_WORD:
/[\.\w]+/
;
Conditional:
(
(" "+)-
(negation=NEGATION)?
(operator=OPERATOR)
(" "+)-
(value=Statement)
)?
;
NEGATION:
"not "
;
OPERATOR:
"contains" | "matches" | "startswith" | "endswith" | "<=" | ">=" | "<" | ">" | "==" | "!="
;
PathSep:
(
"("

View File

@@ -283,6 +283,50 @@ UUID_PHOTO = {
"E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51": {"{photo.favorite}": ["favorite"]},
}
UUID_CONDITIONAL = {
"3DD2C897-F19E-4CA6-8C22-B027D5A71907": {
"{title matches Elder Park?YES,NO}": ["YES"],
"{title matches not Elder Park?YES,NO}": ["NO"],
"{title contains Park?YES,NO}": ["YES"],
"{title not contains Park?YES,NO}": ["NO"],
"{title matches Park?YES,NO}": ["NO"],
"{title matches Elder Park?YES,NO}": ["YES"],
"{title == Elder Park?YES,NO}": ["YES"],
"{title != Elder Park?YES,NO}": ["NO"],
"{title[ ,] == ElderPark?YES,NO}": ["YES"],
"{title not != Elder Park?YES,NO}": ["YES"],
"{title not == Elder Park?YES,NO}": ["NO"],
"{title endswith Park?YES,NO}": ["YES"],
"{title endswith Elder?YES,NO}": ["NO"],
"{title startswith Elder?YES,NO}": ["YES"],
"{title endswith Elder?YES,NO}": ["NO"],
"{photo.place.name contains Adelaide?YES,NO}": ["YES"],
"{photo.place.name|lower contains adelaide?YES,NO}": ["YES"],
"{photo.place.name|lower not contains adelaide?YES,NO}": ["NO"],
"{photo.score.overall < 0.7?YES,NO}": ["YES"],
"{photo.score.overall <= 0.7?YES,NO}": ["YES"],
"{photo.score.overall > 0.7?YES,NO}": ["NO"],
"{photo.score.overall >= 0.7?YES,NO}": ["NO"],
"{photo.score.overall not < 0.7?YES,NO}": ["NO"],
"{folder_album(-) contains Folder1-SubFolder2-AlbumInFolder?YES,NO}": ["YES"],
"{folder_album(-)[In,] contains Folder1-SubFolder2-AlbumFolder?YES,NO}": [
"YES"
],
},
"DC99FBDD-7A52-4100-A5BB-344131646C30": {
"{keyword == {keyword}?YES,NO}": ["YES"],
"{keyword contains England?YES,NO}": ["YES"],
"{keyword contains Eng?YES,NO}": ["YES"],
"{keyword contains Foo?YES,NO}": ["NO"],
"{keyword matches England?YES,NO}": ["YES"],
"{keyword matches Eng?YES,NO}": ["NO"],
"{keyword contains Foo|Bar|England?YES,NO}": ["YES"],
"{keyword contains Foo|Bar?YES,NO}": ["NO"],
"{keyword matches Foo|Bar|England?YES,NO}": ["YES"],
"{keyword matches Foo|Bar?YES,NO}": ["NO"],
},
}
@pytest.fixture(scope="module")
def photosdb_places():
@@ -913,3 +957,11 @@ def test_photo_template(photosdb):
for template in UUID_PHOTO[uuid]:
rendered, _ = photo.render_template(template)
assert sorted(rendered) == sorted(UUID_PHOTO[uuid][template])
def test_conditional(photosdb):
for uuid in UUID_CONDITIONAL:
photo = photosdb.get_photo(uuid)
for template in UUID_CONDITIONAL[uuid]:
rendered, _ = photo.render_template(template)
assert sorted(rendered) == sorted(UUID_CONDITIONAL[uuid][template])