Add prefix and suffix text modifiers to notifications

This commit is contained in:
JonnyWong16 2019-04-04 19:21:26 -07:00
parent 2f8d2f23fe
commit c1fd798fe9
3 changed files with 128 additions and 13 deletions

View file

@ -4209,3 +4209,8 @@ a[data-tab-destination] {
top: 0; top: 0;
z-index: 9999; z-index: 9999;
} }
.help-block li {
margin-top: 0;
color: #737373;
}

View file

@ -1588,7 +1588,7 @@
<div> <div>
<h4>List Slicing</h4> <h4>List Slicing</h4>
</div> </div>
<div> <div style="padding-bottom: 10px;">
<p class="help-block"> <p class="help-block">
Notification parameters which have a list of items can be sliced with a slice formatter <span class="inline-pre">:[X:Y]</span> to limit the number of items. Notification parameters which have a list of items can be sliced with a slice formatter <span class="inline-pre">:[X:Y]</span> to limit the number of items.
Note: the first item in the list is numbered <span class="inline-pre">0</span>. Note: the first item in the list is numbered <span class="inline-pre">0</span>.
@ -1599,6 +1599,41 @@
{actors:[2:]} --> Only the 3rd to last actors (Actors: 2, 3, 4, ...) {actors:[2:]} --> Only the 3rd to last actors (Actors: 2, 3, 4, ...)
{actors:[1:5]} --> Only the 2nd to 5th actors (Actors: 1, 2, 3, 4)</pre> {actors:[1:5]} --> Only the 2nd to 5th actors (Actors: 1, 2, 3, 4)</pre>
</div> </div>
<div>
<h4>Prefix and Suffix</h4>
</div>
<div style="padding-bottom: 10px;">
<p class="help-block">
A prefix or a suffix can be added to the notification parameters using <span class="inline-pre">Prefix&lt;</span> and <span class="inline-pre">&gt;Suffix</span>.
If the notification parameter is unavailable, the prefix or suffix will not be displayed.
</p>
<p><strong style="color: #fff;">Example:</strong></p>
<pre>{rating} --> 8.9
{Rating: &lt;rating} --> Rating: 8.9
{rating&gt;/10} --> 8.9/10
{Rating: &lt;rating&gt;/10} --> Rating: 8.9/10</pre>
<p><strong style="color: #fff;">Example with unavailable parameter:</strong></p>
<pre>{rating} -->
Rating: {rating}/10 --> Rating: /10
{Rating: &lt;rating&gt;/10} --> </pre>
</div>
<div>
<h4>Combined</h4>
</div>
<div>
<p class="help-block">
If combining multiple notification text modifiers, the order of the modifiers must be:
</p>
<ol class="help-block">
<li>Prefix</li>
<li>Parameter</li>
<li>Case Modifier</li>
<li>List Slicing</li>
<li>Suffix</li>
</ol>
<p><strong style="color: #fff;">Example:</strong></p>
<pre>{Starring &lt;actors!c:[0]&gt; as the main character.} --> Starring Arnold Schwarzenegger as the main character.
</pre>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
@ -1620,7 +1655,7 @@
<div class="modal-body"> <div class="modal-body">
<div> <div>
<p class="help-block"> <p class="help-block">
If the value for a selected parameter cannot be provided, it will display as blank. If the value for a selected parameter is unavailable, it will display as blank.
</p> </p>
% for category in common.NEWSLETTER_PARAMETERS: % for category in common.NEWSLETTER_PARAMETERS:
<table class="notification-params"> <table class="notification-params">

View file

@ -1456,9 +1456,8 @@ def get_themoviedb_info(rating_key=None, media_type=None, themoviedb_id=None):
class CustomFormatter(Formatter): class CustomFormatter(Formatter):
def __init__(self, default='{{{0}}}', default_format_spec='{{{0}:{1}}}'): def __init__(self, default='{{{0}}}'):
self.default = default self.default = default
self.default_format_spec = default_format_spec
def convert_field(self, value, conversion): def convert_field(self, value, conversion):
if conversion is None: if conversion is None:
@ -1478,23 +1477,99 @@ class CustomFormatter(Formatter):
def format_field(self, value, format_spec): def format_field(self, value, format_spec):
if format_spec.startswith('[') and format_spec.endswith(']'): if format_spec.startswith('[') and format_spec.endswith(']'):
pattern = re.compile(r'\[(-?\d*):?(-?\d*)\]') pattern = re.compile(r'\[(?P<start>-?\d*)(?P<slice>:?)(?P<end>-?\d*)\]')
if re.match(pattern, format_spec): # slice match = re.match(pattern, format_spec)
if value and match:
groups = match.groupdict()
items = [x.strip() for x in unicode(value).split(',')] items = [x.strip() for x in unicode(value).split(',')]
slice_start, slice_end = re.search(pattern, format_spec).groups() start = groups['start'] or None
slice_start = helpers.cast_to_int(slice_start) or None end = groups['end'] or None
slice_end = helpers.cast_to_int(slice_end) or None if start is not None:
return ', '.join(items[slice(slice_start, slice_end)]) start = helpers.cast_to_int(start)
else: if end is not None:
return value end = helpers.cast_to_int(end)
if not groups['slice']:
end = start + 1
value = ', '.join(items[slice(start, end)])
return value
else: else:
try: try:
return super(CustomFormatter, self).format_field(value, format_spec) return super(CustomFormatter, self).format_field(value, format_spec)
except ValueError: except ValueError:
return self.default_format_spec.format(value[1:-1], format_spec) return value
def get_value(self, key, args, kwargs): def get_value(self, key, args, kwargs):
if isinstance(key, basestring): if isinstance(key, basestring):
return kwargs.get(key, self.default.format(key)) return kwargs.get(key, self.default.format(key))
else: else:
return super(CustomFormatter, self).get_value(key, args, kwargs) return super(CustomFormatter, self).get_value(key, args, kwargs)
def parse(self, format_string):
parsed = super(CustomFormatter, self).parse(format_string)
for literal_text, field_name, format_spec, conversion in parsed:
real_format_string = ''
if field_name:
real_format_string += field_name
if conversion:
real_format_string += '!' + conversion
if format_spec:
real_format_string += ':' + format_spec
prefix = None
suffix = None
if real_format_string != format_string[1:-1]:
prefix_split = real_format_string.split('<')
if len(prefix_split) == 2:
prefix = prefix_split[0].replace('\\n', '\n')
real_format_string = prefix_split[1]
suffix_split = real_format_string.split('>')
if len(suffix_split) == 2:
suffix = suffix_split[1].replace('\\n', '\n')
real_format_string = suffix_split[0]
if prefix or suffix:
real_format_string = '{' + real_format_string + '}'
_, field_name, format_spec, conversion, _, _ = self.parse(real_format_string).next()
yield literal_text, field_name, format_spec, conversion, prefix, suffix
def _vformat(self, format_string, args, kwargs, used_args, recursion_depth,
auto_arg_index=0):
if recursion_depth < 0:
raise ValueError('Max string recursion exceeded')
result = []
for literal_text, field_name, format_spec, conversion, prefix, suffix in self.parse(format_string):
# output the literal text
if literal_text:
result.append(literal_text)
# if there's a field, output it
if field_name is not None:
# this is some markup, find the object and do
# the formatting
# given the field_name, find the object it references
# and the argument it came from
obj, arg_used = self.get_field(field_name, args, kwargs)
used_args.add(arg_used)
# do any conversion on the resulting object
obj = self.convert_field(obj, conversion)
# expand the format spec, if needed
format_spec = self._vformat(format_spec, args, kwargs,
used_args, recursion_depth - 1)
# format the object and append to the result
formatted_field = self.format_field(obj, format_spec)
if formatted_field:
if prefix:
result.append(prefix)
result.append(formatted_field)
if suffix:
result.append(suffix)
# result.append(self.format_field(obj, format_spec))
return ''.join(result)