mirror of
https://github.com/caronc/apprise.git
synced 2025-02-12 08:19:39 +01:00
Fixed argument parsing within YAML files (#404)
This commit is contained in:
parent
8a455695ba
commit
ab6b6b51c7
@ -848,7 +848,7 @@ class ConfigBase(URLBase):
|
|||||||
|
|
||||||
# support our special tokens (if they're present)
|
# support our special tokens (if they're present)
|
||||||
if schema in plugins.SCHEMA_MAP:
|
if schema in plugins.SCHEMA_MAP:
|
||||||
entries = ConfigBase.__extract_special_tokens(
|
entries = ConfigBase._special_token_handler(
|
||||||
schema, entries)
|
schema, entries)
|
||||||
|
|
||||||
# Extend our dictionary with our new entries
|
# Extend our dictionary with our new entries
|
||||||
@ -860,7 +860,7 @@ class ConfigBase(URLBase):
|
|||||||
elif isinstance(tokens, dict):
|
elif isinstance(tokens, dict):
|
||||||
# support our special tokens (if they're present)
|
# support our special tokens (if they're present)
|
||||||
if schema in plugins.SCHEMA_MAP:
|
if schema in plugins.SCHEMA_MAP:
|
||||||
tokens = ConfigBase.__extract_special_tokens(
|
tokens = ConfigBase._special_token_handler(
|
||||||
schema, tokens)
|
schema, tokens)
|
||||||
|
|
||||||
# Copy ourselves a template of our parsed URL as a base to
|
# Copy ourselves a template of our parsed URL as a base to
|
||||||
@ -962,7 +962,7 @@ class ConfigBase(URLBase):
|
|||||||
return self._cached_servers.pop(index)
|
return self._cached_servers.pop(index)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __extract_special_tokens(schema, tokens):
|
def _special_token_handler(schema, tokens):
|
||||||
"""
|
"""
|
||||||
This function takes a list of tokens and updates them to no longer
|
This function takes a list of tokens and updates them to no longer
|
||||||
include any special tokens such as +,-, and :
|
include any special tokens such as +,-, and :
|
||||||
@ -994,7 +994,7 @@ class ConfigBase(URLBase):
|
|||||||
# we're done with this entry
|
# we're done with this entry
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not isinstance(tokens.get(kw, None), dict):
|
if not isinstance(tokens.get(kw), dict):
|
||||||
# Invalid; correct it
|
# Invalid; correct it
|
||||||
tokens[kw] = dict()
|
tokens[kw] = dict()
|
||||||
|
|
||||||
@ -1005,6 +1005,88 @@ class ConfigBase(URLBase):
|
|||||||
# Update our entries
|
# Update our entries
|
||||||
tokens[kw].update(matches)
|
tokens[kw].update(matches)
|
||||||
|
|
||||||
|
# Now map our tokens accordingly to the class templates defined by
|
||||||
|
# each service.
|
||||||
|
#
|
||||||
|
# This is specifically used for YAML file parsing. It allows a user to
|
||||||
|
# define an entry such as:
|
||||||
|
#
|
||||||
|
# urls:
|
||||||
|
# - mailto://user:pass@domain:
|
||||||
|
# - to: user1@hotmail.com
|
||||||
|
# - to: user2@hotmail.com
|
||||||
|
#
|
||||||
|
# Under the hood, the NotifyEmail() class does not parse the `to`
|
||||||
|
# argument. It's contents needs to be mapped to `targets`. This is
|
||||||
|
# defined in the class via the `template_args` and template_tokens`
|
||||||
|
# section.
|
||||||
|
#
|
||||||
|
# This function here allows these mappings to take place within the
|
||||||
|
# YAML file as independant arguments.
|
||||||
|
class_templates = \
|
||||||
|
plugins.details(plugins.SCHEMA_MAP[schema])
|
||||||
|
|
||||||
|
for key in list(tokens.keys()):
|
||||||
|
|
||||||
|
if key not in class_templates['args']:
|
||||||
|
# No need to handle non-arg entries
|
||||||
|
continue
|
||||||
|
|
||||||
|
# get our `map_to` and/or 'alias_of' value (if it exists)
|
||||||
|
map_to = class_templates['args'][key].get(
|
||||||
|
'alias_of', class_templates['args'][key].get('map_to', ''))
|
||||||
|
|
||||||
|
if map_to == key:
|
||||||
|
# We're already good as we are now
|
||||||
|
continue
|
||||||
|
|
||||||
|
if map_to in class_templates['tokens']:
|
||||||
|
meta = class_templates['tokens'][map_to]
|
||||||
|
|
||||||
|
else:
|
||||||
|
meta = class_templates['args'].get(
|
||||||
|
map_to, class_templates['args'][key])
|
||||||
|
|
||||||
|
# Perform a translation/mapping if our code reaches here
|
||||||
|
value = tokens[key]
|
||||||
|
del tokens[key]
|
||||||
|
|
||||||
|
# Detect if we're dealign with a list or not
|
||||||
|
is_list = re.search(
|
||||||
|
r'^(list|choice):.*',
|
||||||
|
meta.get('type'),
|
||||||
|
re.IGNORECASE)
|
||||||
|
|
||||||
|
if map_to not in tokens:
|
||||||
|
tokens[map_to] = [] if is_list \
|
||||||
|
else meta.get('default')
|
||||||
|
|
||||||
|
elif is_list and not isinstance(tokens.get(map_to), list):
|
||||||
|
# Convert ourselves to a list if we aren't already
|
||||||
|
tokens[map_to] = [tokens[map_to]]
|
||||||
|
|
||||||
|
# Type Conversion
|
||||||
|
if re.search(
|
||||||
|
r'^(choice:)?string',
|
||||||
|
meta.get('type'),
|
||||||
|
re.IGNORECASE) \
|
||||||
|
and not isinstance(value, six.string_types):
|
||||||
|
|
||||||
|
# Ensure our format is as expected
|
||||||
|
value = str(value)
|
||||||
|
|
||||||
|
# Apply any further translations if required (absolute map)
|
||||||
|
# This is the case when an arg maps to a token which further
|
||||||
|
# maps to a different function arg on the class constructor
|
||||||
|
abs_map = meta.get('map_to', map_to)
|
||||||
|
|
||||||
|
# Set our token as how it was provided by the configuration
|
||||||
|
if isinstance(tokens.get(map_to), list):
|
||||||
|
tokens[abs_map].append(value)
|
||||||
|
|
||||||
|
else:
|
||||||
|
tokens[abs_map] = value
|
||||||
|
|
||||||
# Return our tokens
|
# Return our tokens
|
||||||
return tokens
|
return tokens
|
||||||
|
|
||||||
|
@ -363,10 +363,6 @@ class NotifyEmail(NotifyBase):
|
|||||||
'type': 'string',
|
'type': 'string',
|
||||||
'map_to': 'from_name',
|
'map_to': 'from_name',
|
||||||
},
|
},
|
||||||
'smtp_host': {
|
|
||||||
'name': _('SMTP Server'),
|
|
||||||
'type': 'string',
|
|
||||||
},
|
|
||||||
'cc': {
|
'cc': {
|
||||||
'name': _('Carbon Copy'),
|
'name': _('Carbon Copy'),
|
||||||
'type': 'list:string',
|
'type': 'list:string',
|
||||||
@ -375,6 +371,11 @@ class NotifyEmail(NotifyBase):
|
|||||||
'name': _('Blind Carbon Copy'),
|
'name': _('Blind Carbon Copy'),
|
||||||
'type': 'list:string',
|
'type': 'list:string',
|
||||||
},
|
},
|
||||||
|
'smtp': {
|
||||||
|
'name': _('SMTP Server'),
|
||||||
|
'type': 'string',
|
||||||
|
'map_to': 'smtp_host',
|
||||||
|
},
|
||||||
'mode': {
|
'mode': {
|
||||||
'name': _('Secure Mode'),
|
'name': _('Secure Mode'),
|
||||||
'type': 'choice:string',
|
'type': 'choice:string',
|
||||||
|
@ -1171,3 +1171,180 @@ def test_config_base_parse_yaml_file03(tmpdir):
|
|||||||
assert sum(1 for _ in a.find('test3')) == 1
|
assert sum(1 for _ in a.find('test3')) == 1
|
||||||
# Match test1 or test3 entry; (only matches test3)
|
# Match test1 or test3 entry; (only matches test3)
|
||||||
assert sum(1 for _ in a.find('test1, test3')) == 1
|
assert sum(1 for _ in a.find('test1, test3')) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_apprise_config_template_parse(tmpdir):
|
||||||
|
"""
|
||||||
|
API: AppriseConfig parsing of templates
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Create ourselves a config object
|
||||||
|
ac = AppriseConfig()
|
||||||
|
|
||||||
|
t = tmpdir.mkdir("template-testing").join("apprise.yml")
|
||||||
|
t.write("""
|
||||||
|
|
||||||
|
tag:
|
||||||
|
- company
|
||||||
|
|
||||||
|
# A comment line over top of a URL
|
||||||
|
urls:
|
||||||
|
- mailto://user:pass@example.com:
|
||||||
|
- to: user1@gmail.com
|
||||||
|
cc: test@hotmail.com
|
||||||
|
|
||||||
|
- to: user2@gmail.com
|
||||||
|
tag: co-worker
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Create ourselves a config object
|
||||||
|
ac = AppriseConfig(paths=str(t))
|
||||||
|
|
||||||
|
# 2 emails to be sent
|
||||||
|
assert len(ac.servers()) == 2
|
||||||
|
|
||||||
|
# The below checks are very customized for NotifyMail but just
|
||||||
|
# test that the content got passed correctly
|
||||||
|
assert (False, 'user1@gmail.com') in ac[0][0].targets
|
||||||
|
assert 'test@hotmail.com' in ac[0][0].cc
|
||||||
|
assert 'company' in ac[0][1].tags
|
||||||
|
|
||||||
|
assert (False, 'user2@gmail.com') in ac[0][1].targets
|
||||||
|
assert 'company' in ac[0][1].tags
|
||||||
|
assert 'co-worker' in ac[0][1].tags
|
||||||
|
|
||||||
|
#
|
||||||
|
# Specifically test _special_token_handler()
|
||||||
|
#
|
||||||
|
tokens = {
|
||||||
|
# This maps to itself (bcc); no change here
|
||||||
|
'bcc': 'user@test.com',
|
||||||
|
# This should get mapped to 'targets'
|
||||||
|
'to': 'user1@abc.com',
|
||||||
|
# white space and tab is intentionally added to the end to verify we
|
||||||
|
# do not play/tamper with information
|
||||||
|
'targets': 'user2@abc.com, user3@abc.com \t',
|
||||||
|
# If the end user provides a configuration for data we simply don't use
|
||||||
|
# this isn't a proble... we simply don't touch it either; we leave it
|
||||||
|
# as is.
|
||||||
|
'ignore': 'not-used'
|
||||||
|
}
|
||||||
|
|
||||||
|
result = ConfigBase._special_token_handler('mailto', tokens)
|
||||||
|
# to gets mapped to targets
|
||||||
|
assert 'to' not in result
|
||||||
|
|
||||||
|
# bcc is allowed here
|
||||||
|
assert 'bcc' in result
|
||||||
|
assert 'targets' in result
|
||||||
|
# Not used, but also not touched; this entry should still be in our result
|
||||||
|
# set
|
||||||
|
assert 'ignore' in result
|
||||||
|
# We'll concatinate all of our targets together
|
||||||
|
assert len(result['targets']) == 2
|
||||||
|
assert 'user1@abc.com' in result['targets']
|
||||||
|
# Content is passed as is
|
||||||
|
assert 'user2@abc.com, user3@abc.com \t' in result['targets']
|
||||||
|
|
||||||
|
# We re-do the simmiar test above. The very key difference is the
|
||||||
|
# `targets` is a list already (it's expected type) so `to` can properly be
|
||||||
|
# concatinated into the list vs the above (which tries to correct the
|
||||||
|
# situation)
|
||||||
|
tokens = {
|
||||||
|
# This maps to itself (bcc); no change here
|
||||||
|
'bcc': 'user@test.com',
|
||||||
|
# This should get mapped to 'targets'
|
||||||
|
'to': 'user1@abc.com',
|
||||||
|
# similar to the above test except targets is now a proper
|
||||||
|
# dictionary allowing the `to` (when translated to `targets`) to get
|
||||||
|
# appended to it
|
||||||
|
'targets': ['user2@abc.com', 'user3@abc.com'],
|
||||||
|
# If the end user provides a configuration for data we simply don't use
|
||||||
|
# this isn't a proble... we simply don't touch it either; we leave it
|
||||||
|
# as is.
|
||||||
|
'ignore': 'not-used'
|
||||||
|
}
|
||||||
|
|
||||||
|
result = ConfigBase._special_token_handler('mailto', tokens)
|
||||||
|
# to gets mapped to targets
|
||||||
|
assert 'to' not in result
|
||||||
|
|
||||||
|
# bcc is allowed here
|
||||||
|
assert 'bcc' in result
|
||||||
|
assert 'targets' in result
|
||||||
|
# Not used, but also not touched; this entry should still be in our result
|
||||||
|
# set
|
||||||
|
assert 'ignore' in result
|
||||||
|
|
||||||
|
# Now we'll see the new user added as expected (concatinated into our list)
|
||||||
|
assert len(result['targets']) == 3
|
||||||
|
assert 'user1@abc.com' in result['targets']
|
||||||
|
assert 'user2@abc.com' in result['targets']
|
||||||
|
assert 'user3@abc.com' in result['targets']
|
||||||
|
|
||||||
|
# Test providing a list
|
||||||
|
t.write("""
|
||||||
|
# A comment line over top of a URL
|
||||||
|
urls:
|
||||||
|
- mailtos://user:pass@example.com:
|
||||||
|
- smtp: smtp3-dev.google.gmail.com
|
||||||
|
to:
|
||||||
|
- John Smith <user1@gmail.com>
|
||||||
|
- Jason Tater <user2@gmail.com>
|
||||||
|
- user3@gmail.com
|
||||||
|
|
||||||
|
- to: Henry Fisher <user4@gmail.com>, Jason Archie <user5@gmail.com>
|
||||||
|
smtp_host: smtp5-dev.google.gmail.com
|
||||||
|
tag: drinking-buddy
|
||||||
|
|
||||||
|
# provide case where the URL includes some input too
|
||||||
|
# In both of these cases, the cc and targets (to) get over-ridden
|
||||||
|
# by values below
|
||||||
|
- mailtos://user:pass@example.com/arnold@imdb.com/?cc=bill@micro.com/:
|
||||||
|
to:
|
||||||
|
- override01@gmail.com
|
||||||
|
cc:
|
||||||
|
- override02@gmail.com
|
||||||
|
|
||||||
|
- sinch://:
|
||||||
|
- spi: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||||
|
token: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
||||||
|
|
||||||
|
# Test a case where we expect a string, but yaml reads it in as
|
||||||
|
# a number
|
||||||
|
from: 10005243890
|
||||||
|
to: +1(123)555-1234
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Create ourselves a config object
|
||||||
|
ac = AppriseConfig(paths=str(t))
|
||||||
|
|
||||||
|
# 2 emails to be sent and 1 Sinch service call
|
||||||
|
assert len(ac.servers()) == 4
|
||||||
|
|
||||||
|
# Verify our users got placed into the to
|
||||||
|
assert len(ac[0][0].targets) == 3
|
||||||
|
assert ("John Smith", 'user1@gmail.com') in ac[0][0].targets
|
||||||
|
assert ("Jason Tater", 'user2@gmail.com') in ac[0][0].targets
|
||||||
|
assert (False, 'user3@gmail.com') in ac[0][0].targets
|
||||||
|
assert ac[0][0].smtp_host == 'smtp3-dev.google.gmail.com'
|
||||||
|
|
||||||
|
assert len(ac[0][1].targets) == 2
|
||||||
|
assert ("Henry Fisher", 'user4@gmail.com') in ac[0][1].targets
|
||||||
|
assert ("Jason Archie", 'user5@gmail.com') in ac[0][1].targets
|
||||||
|
assert 'drinking-buddy' in ac[0][1].tags
|
||||||
|
assert ac[0][1].smtp_host == 'smtp5-dev.google.gmail.com'
|
||||||
|
|
||||||
|
# Our third test tests cases where some variables are defined inline
|
||||||
|
# and additional ones are defined below that share the same token space
|
||||||
|
assert len(ac[0][2].targets) == 1
|
||||||
|
assert len(ac[0][2].cc) == 1
|
||||||
|
assert (False, 'override01@gmail.com') in ac[0][2].targets
|
||||||
|
assert 'override02@gmail.com' in ac[0][2].cc
|
||||||
|
|
||||||
|
# Test our Since configuration now:
|
||||||
|
assert len(ac[0][3].targets) == 1
|
||||||
|
assert ac[0][3].service_plan_id == 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
|
||||||
|
assert ac[0][3].source == '+10005243890'
|
||||||
|
assert ac[0][3].targets[0] == '+11235551234'
|
||||||
|
Loading…
Reference in New Issue
Block a user