Skip to content

Commit

Permalink
Implement trigger action
Browse files Browse the repository at this point in the history
Currently does not invoke recursively defined triggers!
  • Loading branch information
JakobGM committed Feb 15, 2018
1 parent 2e4feca commit 41b675d
Show file tree
Hide file tree
Showing 6 changed files with 276 additions and 3 deletions.
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ deploy:
user: jakobgm
password:
secure: G84gKVBTjQzm3kLc4VkyQwKL2MY9PSm9Yp6O1Q/FxaBzw5v+P8MGcljwwri0iRc3jppfsuoUFegFHmKg3ufNGINTlVPvSQBIm/UyMxsLgu7BV8aQK5o2E5XN/Q3ET4MlIz7Qpypy8iBs3omNRD+pgSgyRmkk2XOufMw+C9cSM+4udCrXE2HA+4mJVXNs63rmtLCwRQDJ/C5+LtwycuoyZeIvIwrOS9G/PtauUZu+Qv/KmRan7kwaC4zNJHw5GVGsUJ0ozj1PwQJ1uL/+Gb0oUiCWe1m79+XLDtiBgVwQmBeVO9flxJIET5ggZ/Vyku/6qidovMzkiVxX9bkoFd4dU7aFpD7SE8pHpWIguVnYhDE0Xzw3C0JxfUNDGKDPbWRgTzCyMWlTUTqxgXB2/CSaATGZ/DbVR5OBdQwPVF/+qKANxvtpSnJr4NhfjreK4zJ0q32hLz19KOH62yHZNeo7U0Z8EMx6JMPLYFv1FI+WZStkj0JrFJfQ/zG7PRUE2iuE4CXnh5egaRF/jmPsrm/Qljstz/PHtxHuETOxgUyRViscN+XtdCMimqFFKy7CV+kYc9ZzIRqRBa5KHEcMg7kqN5C8hs6ETVHpIAyp2XBeiX5NsJE+vnd0HHj0a7F1KEfz2pflORBnpfU8iz2dpDiMYfRQx1W3wDwuhnJNtMVJ/4g=
skip_cleanup: true
distributions: "sdist bdist_wheel"
on:
all_branches: true
tags: true
70 changes: 69 additions & 1 deletion astrality/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ def __init__(
self.name: str = section.split('/')[1]

self.module_config = module_config[section]
self.populate_event_blocks()

# Import trigger actions into their respective event blocks
self.import_trigger_actions()

self.config_directory = config_directory
self.temp_directory = temp_directory

Expand All @@ -77,6 +82,69 @@ def __init__(
# Find and prepare templates and compilation targets
self._prepare_templates()

def populate_event_blocks(self) -> None:
"""
Populate non-configured actions within event blocks.
This prevents us from having to use .get() all over the Module.
"""
for event_block in ('on_startup', 'on_period_change', 'on_exit', ):
configured_event_block = self.module_config.get(event_block, {})
self.module_config[event_block] = {
'import_context': [],
'compile': [],
'run': [],
}
self.module_config[event_block].update(configured_event_block)

if not 'on_modified' in self.module_config:
self.module_config['on_modified'] = {}
else:
for template_name in self.module_config['on_modified'].keys():
configured_event_block = self.module_config['on_modified'][template_name]
self.module_config['on_modified'][template_name] = {
'import_context': [],
'compile': [],
'run': [],
}
self.module_config['on_modified'][template_name].update(configured_event_block)

def import_trigger_actions(self) -> None:
"""If an event block defines trigger events, import those actions."""
event_blocks = (
self.module_config['on_startup'],
self.module_config['on_period_change'],
self.module_config['on_exit'],
self.module_config['on_modified'].values(),
)
for event_block in event_blocks:
if 'trigger' in event_block:
event_blocks_to_import = event_block['trigger']
if isinstance(event_blocks_to_import, str):
event_blocks_to_import = [event_blocks_to_import]

for event_block_to_import in event_blocks_to_import:
self._import_event_block(
from_event_block=event_block_to_import,
into=event_block,
)

def _import_event_block(
self,
from_event_block: str,
into: Dict[str, Any],
) -> None:
"""Merge one event block with another one."""
if 'on_modified.' in from_event_block:
template = from_event_block[12:]
from_event_block_dict = self.module_config['on_modified'].get(template, {})
else:
from_event_block_dict = self.module_config[from_event_block]

into['run'].extend(from_event_block_dict['run'])
into['import_context'].extend(from_event_block_dict['import_context'])
into['compile'].extend(from_event_block_dict['compile'])

def _prepare_templates(self) -> None:
"""Determine template sources and compilation targets."""
# config['templates'] is a dictionary with keys naming each template,
Expand Down Expand Up @@ -416,7 +484,7 @@ def compile_templates(self, trigger: str) -> None:
assert trigger in ('on_startup', 'on_period_change', 'on_exit',)

for module in self.modules.values():
for shortname in module.module_config.get(trigger, {}).get('compile', []):
for shortname in module.module_config[trigger]['compile']:
self.compile_template(module=module, shortname=shortname)


Expand Down
91 changes: 91 additions & 0 deletions astrality/tests/test_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -1009,3 +1009,94 @@ def test_that_only_startup_event_block_is_run_on_startup(
day=16,
hour=12,
)

def test_trigger_event_module_action(conf_path):
application_config = {
'module/A': {
'timer': {'type': 'weekday'},
'on_startup': {
'trigger': ['on_period_change', 'on_exit', 'on_modified.templateA'],
'run': ['echo startup'],
},
'on_period_change': {
'run': ['echo period_change'],
'import_context': [{
'from_file': 'contexts/file.yaml',
'from_section': 'section',
}],
},
'on_exit': {
'run': ['echo exit'],
},
'on_modified': {
'templateA': {
'run': ['echo modified.templateA'],
'compile': ['templateA'],
},
},
},
'_runtime': {
'config_directory': conf_path,
'temp_directory': Path('/tmp/astrality'),
},
}
module_manager = ModuleManager(application_config)

# Check that all run commands have been imported into startup block
assert module_manager.modules['A'].startup_commands() == (
'echo startup',
'echo period_change',
'echo exit',
'echo modified.templateA',
)

# Check that all context section imports are available in startup block
assert module_manager.modules['A'].context_section_imports('on_startup') == (
ContextSectionImport(
into_section='section',
from_section='section',
from_config_file=conf_path / 'contexts' / 'file.yaml',
),
)

# Check that all compile actions have been merged into startup block
assert module_manager.modules['A'].module_config['on_startup']['compile'] ==\
['templateA']

# Double check that the other sections are not affected
assert module_manager.modules['A'].period_change_commands() == (
'echo period_change',
)
assert module_manager.modules['A'].exit_commands() == (
'echo exit',
)

assert module_manager.modules['A'].context_section_imports('on_period_change') == (
ContextSectionImport(
into_section='section',
from_section='section',
from_config_file=conf_path / 'contexts' / 'file.yaml',
),
)

def test_not_using_list_when_specifiying_trigger_action(conf_path):
application_config = {
'module/A': {
'on_startup': {
'trigger': 'on_period_change',
},
'on_period_change': {
'run': ['echo period_change'],
},
},
'_runtime': {
'config_directory': conf_path,
'temp_directory': Path('/tmp/astrality'),
},
}
module_manager = ModuleManager(application_config)

# Check that all run commands have been imported into startup block
assert module_manager.modules['A'].startup_commands() == (
'echo period_change',
)
99 changes: 98 additions & 1 deletion docs/modules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ Example of module event blocks:
Actions
=======

Actions are tasks for Astrality to perform, and are placed within :ref:`event blocks <events>` in order to specify *when* to perform them. There are three available ``action`` types:
Actions are tasks for Astrality to perform, and are placed within :ref:`event blocks <events>` in order to specify *when* to perform them. There are four available ``action`` types:

:ref:`import_context <context_import_action>`:
Import a ``context`` section from a YAML formatted file. ``context`` variables are used as replacement values for placeholders in your :ref:`templates <module_templates>`. See :ref:`context <context>` for more information.
Expand All @@ -153,6 +153,9 @@ Actions are tasks for Astrality to perform, and are placed within :ref:`event bl
:ref:`run <run_action>`:
Execute a shell command, possibly referring to any compiled template and/or the current :ref:`period <timer_periods>` defined by the :ref:`module timer <timers>`.

:ref:`trigger <trigger_action>`:
Perform *all* actions specified within another :ref:`event block <events>`. With other words, this action *appends* all the actions within another event block to the actions already specified in the event block. Useful for not having to repeat yourself when you want the same actions to be performed during different events.


.. _context_import_action:

Expand Down Expand Up @@ -289,6 +292,100 @@ Example:
- rm ~/notes/notes_for_{period}.txt
.. _trigger_action:

Trigger events
--------------

You can trigger another module :ref:`event <events>` by specifying the ``trigger`` action.

The ``trigger`` option accepts ``on_startup``, ``on_period_change``, ``on_exit``, and ``on_modified.template_shortname``, either as a single string, or a list with any combination of these.

An example of a module using ``trigger`` actions:

.. code-block:: yaml
module/module_using_triggers:
templates:
timer:
type: weekday
templateA:
source: templates/A.template
on_startup:
run:
- startup_command
trigger:
- on_period_change
- on_modified.templateA
on_period_change:
import_context:
- from_file: contexts/A.yaml
from_section: '{period}'
to_section: a_stuff
trigger: on_modified.templateA
on_modified:
templateA:
compile:
- templateA
run:
- shell_command_dependent_on_templateA
This is equivalent to writing the following module:

.. code-block:: yaml
module/module_using_triggers:
templates:
timer:
type: weekday
templateA:
source: templates/A.template
on_startup:
import_context:
- from_file: contexts/A.yaml
from_section: '{period}'
to_section: a_stuff
compile:
- templateA
run:
- startup_command
- shell_command_dependent_on_templateA
on_period_change:
import_context:
- from_file: contexts/A.yaml
from_section: '{period}'
to_section: a_stuff
compile:
- templateA
run:
- shell_command_dependent_on_templateA
on_modified:
templateA:
compile:
- templateA
run:
- shell_command_dependent_on_templateA
.. hint::
You can use ``trigger: on_period_change`` in order to consider Astrality startup as a ``period change`` event.

The ``trigger`` action can also help you reduce the degree of repetition in your configuration.

.. caution::
Astrality does not invoke recursive trigger events at the moment.
You have to specify them manually instead, as shown in the example above.



The execution order of module actions
-------------------------------------

Expand Down
15 changes: 15 additions & 0 deletions docs/timers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,21 @@ Now Astrality will set the appropriate wallpaper on startup. We still have a sma
run:
- feh --bg-fill modules/weekday_wallpaper/{period}.*
Or, alternatively, we can just :ref:`trigger <trigger_action>` startup event when the period changes:

.. code-block:: yaml
module/weekday_wallpaper:
timer:
type: weekday
on_startup:
run:
- feh --bg-fill modules/weekday_wallpaper/{period}.*
on_period_change:
trigger: on_startup
Timer types
===========
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def readme():

setup(
name='astrality',
version='0.2',
version='0.3',
packages=find_packages(),
install_requires=[
'astral',
Expand Down

0 comments on commit 41b675d

Please sign in to comment.