.. _how-to: ****** How To ****** Create a New Command Overlay ============================ :ref:`topics-overlays` are used to define the commands supported by a given application, service, or operating system. Commands are defined as a function. 1) Define a Module ------------------ The first step is to create a new module in which functions will be defined. .. code-block:: python # module_name.py from ..commands import Command For overlays that represent an operating system, the ``command_exists()`` function is required: .. code-block:: python def command_exists(name): return name in MAPPINGS 2) Define Command Function -------------------------- The purpose of each function is to provide an interface for instantiating a :py:class:`scripttease.library.commands.base.Command` instance. The example below is taken from the ``posix`` module. .. code-block:: python # module_name.py # ... def mkdir(path, mode=None, recursive=True, **kwargs): """Create a directory. - path (str): The path to be created. - mode (int | str): The access permissions of the new directory. - recursive (bool): Create all directories along the path. """ kwargs.setdefault("comment", "create directory %s" % path) statement = ["mkdir"] if mode is not None: statement.append("-m %s" % mode) if recursive: statement.append("-p") statement.append(path) return Command(" ".join(statement), **kwargs) The arguments and any specific keyword arguments are automatically used by the parser, but also serve as a simple interface for programmatic use. Each function *must* also accept ``**kwargs`` and should set a default for ``comment`` as above. .. important:: Rather than the usual Spinx-based documentation, define the docstring as shown above. This is used to automatically create the documentation for the command. 3) Add Functions to the Mapping ------------------------------- The final step adds the function to the mapping. This makes it available to the command factory. .. code-block:: python # module_name.py # ... MAPPINGS = { 'mkdir': mkdir, } For overlays that represent an operating system, ``MAPPINGS`` is required -- in addition to ``command_exists()`` above. For commands that are specific to service or application, the name of the dictionary may be anything that is appropriate. For example, ``DJANGO_MAPPINGS``. Additionally, for an operating system overlay, you may wish to import other mappings and incorporate them into ``MAPPINGS``. .. code-block:: python # module_name.py from ..commands import Command from .common import COMMON_MAPPINGS from .django import DJANGO_MAPPINGS from .pgsql import PGSQL_MAPPINGS MAPPINGS = { # ... } MAPPINGS.update(COMMON_MAPPINGS) MAPPINGS.update(DJANGO_MAPPINGS) MAPPINGS.update(PGSQL_MAPPINGS) 4) Update Documentation ----------------------- Add the command mappings to the ``docs/generate_command_signatures.py`` file. See the script for more details. Export Commands as a Script =========================== You can export commands as a read-to-use script. For example: .. code-block:: python config = Config("commands.ini") if not config.load(): print("Bummer!") exit() script = config.as_script() print(script) Post a Message to Slack ======================= The slack function may be used to send a message to a Slack channel. This uses the Incoming Webhooks feature, which requires some additional setup. .. note:: The following steps were accurate as of September 2020. **1.** Log in to Slack and go to `Your Apps`_. .. _Your Apps: https://api.slack.com/apps **2.** Create a new Slack app. **3.** On the next page, select Incoming Webhooks and then toggle activation. .. image:: /_static/images/slack-1.jpg **4.** Next click Add new Webhook to Workspace and select the channel to which the message will be posted. .. image:: /_static/images/slack-2.jpg .. image:: /_static/images/slack-3.jpg **5.** Copy the URL for the new webhook to use as the ``url`` parameter for the Slack command. .. code-block:: ini [send a message to slack] slack: "This is a test message." url: the URL you created goes here Post a Message to Twist ======================= The twist function may be used to send a message to Twist, which requires some additional setup. .. note:: The following steps were accurate as of September 2020. **1.** Log in to Twist and from the profile menu go to Add Integrations. Then click on Build and "Add a new integration". **2.** Provide the requested info. .. image:: _static/images/twist-1.png **3.** After submitting this info, go to Installation. Select a channel and who to notify. Then click "Install integration". .. image:: _static/images/twist-2.png **4.** Copy the "Post content manually" URL for use in your configuration file. .. code-block:: ini [post a message to twist] twist: "This is a test message." url: the URL you created goes here Use Script Tease With Common Kit ================================ Since the focus of Script Tease is to convert plain text instructions into valid command line statements, it does *not* provide support for executing those statements either locally or remotely. However, The shell component of `python-commonkit`_ *does* provide support for executing commands in local POSIX environments. .. _python-commonkit: https://docs.develmaycare.com/en/python-commonkit/stable/components/#module-commonkit.shell Here is an example of how to use these packages together: .. code-block:: python from commonkit.shell import Command from scripttease.parsers.utils import load_commands def execute(step): command = Command( step.statement, comment=step.comment, path=step.cd, prefix=step.prefix, shell=step.shell ) # Sudo is a different class, but identical in behavior. command.sudo = step.sudo if command.run(): print("[success] %s" % step.comment) else: print("[failure] %s" % step.comment) if step.stop: print("I can't go on: %s" % command.error) exit(command.code) # Load SCRIPT TEASE commands from an INI file. These are instances of either Command or ItemizedCommand found in # scripttease.library.commands steps = load_commands("path/to/steps.ini") # A failure to load results in None. if steps is None: print("Failed to load steps.") exit(1) # Iterate through each step to create a COMMON KIT command. for step in steps: # To preview ... # print(step.get_statement(cd=True)) if step.is_itemized: for substep in step.get_commands(): execute(substep) else: execute(step) Common Kit is already a dependency of Script Tease so it is installed by default. The ``execute()`` function is a shortcut that helps deal with itemized commands. The path (``step.cd``) is automatically handled by Common Kit's Command class.