mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-08 14:10:52 -07:00
Add prefix and suffix text modifiers to notifications
This commit is contained in:
parent
2f8d2f23fe
commit
c1fd798fe9
3 changed files with 128 additions and 13 deletions
|
@ -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;
|
||||||
|
}
|
|
@ -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<</span> and <span class="inline-pre">>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: <rating} --> Rating: 8.9
|
||||||
|
{rating>/10} --> 8.9/10
|
||||||
|
{Rating: <rating>/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: <rating>/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 <actors!c:[0]> 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">
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue