mirror of
https://github.com/caronc/apprise.git
synced 2024-11-08 09:14:53 +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)
|
||||
if schema in plugins.SCHEMA_MAP:
|
||||
entries = ConfigBase.__extract_special_tokens(
|
||||
entries = ConfigBase._special_token_handler(
|
||||
schema, entries)
|
||||
|
||||
# Extend our dictionary with our new entries
|
||||
@ -860,7 +860,7 @@ class ConfigBase(URLBase):
|
||||
elif isinstance(tokens, dict):
|
||||
# support our special tokens (if they're present)
|
||||
if schema in plugins.SCHEMA_MAP:
|
||||
tokens = ConfigBase.__extract_special_tokens(
|
||||
tokens = ConfigBase._special_token_handler(
|
||||
schema, tokens)
|
||||
|
||||
# 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)
|
||||
|
||||
@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
|
||||
include any special tokens such as +,-, and :
|
||||
@ -994,7 +994,7 @@ class ConfigBase(URLBase):
|
||||
# we're done with this entry
|
||||
continue
|
||||
|
||||
if not isinstance(tokens.get(kw, None), dict):
|
||||
if not isinstance(tokens.get(kw), dict):
|
||||
# Invalid; correct it
|
||||
tokens[kw] = dict()
|
||||
|
||||
@ -1005,6 +1005,88 @@ class ConfigBase(URLBase):
|
||||
# Update our entries
|
||||
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 tokens
|
||||
|
||||
|
@ -363,10 +363,6 @@ class NotifyEmail(NotifyBase):
|
||||
'type': 'string',
|
||||
'map_to': 'from_name',
|
||||
},
|
||||
'smtp_host': {
|
||||
'name': _('SMTP Server'),
|
||||
'type': 'string',
|
||||
},
|
||||
'cc': {
|
||||
'name': _('Carbon Copy'),
|
||||
'type': 'list:string',
|
||||
@ -375,6 +371,11 @@ class NotifyEmail(NotifyBase):
|
||||
'name': _('Blind Carbon Copy'),
|
||||
'type': 'list:string',
|
||||
},
|
||||
'smtp': {
|
||||
'name': _('SMTP Server'),
|
||||
'type': 'string',
|
||||
'map_to': 'smtp_host',
|
||||
},
|
||||
'mode': {
|
||||
'name': _('Secure Mode'),
|
||||
'type': 'choice:string',
|
||||
|
@ -1171,3 +1171,180 @@ def test_config_base_parse_yaml_file03(tmpdir):
|
||||
assert sum(1 for _ in a.find('test3')) == 1
|
||||
# Match test1 or test3 entry; (only matches test3)
|
||||
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