Compare commits

..

3 Commits

Author SHA1 Message Date
Shawn Davis c8c998b6ed Updated help. 2 years ago
Shawn Davis aad47de6a1 Updated help. 2 years ago
Shawn Davis 29c40cf1f1 Updated docs. 2 years ago
  1. 2
      .gitignore
  2. 16
      Makefile
  3. 10
      README.markdown
  4. 4
      docs/Makefile
  5. 139
      docs/generate_command_signatures.py
  6. 5
      docs/source/_data/cloc.csv
  7. 6
      docs/source/_includes/dependencies.rst
  8. 1294
      docs/source/_includes/overlays.rst
  9. 12
      docs/source/_includes/tests.rst
  10. BIN
      docs/source/_static/images/architecture-diagram.png
  11. 86
      docs/source/commands.rst
  12. 4
      docs/source/contact.rst
  13. 77
      docs/source/contributing.rst
  14. 32
      docs/source/getting-started.rst
  15. 236
      docs/source/how-to.rst
  16. 22
      docs/source/index.rst
  17. 125
      docs/source/introduction.rst
  18. 140
      docs/source/project.rst
  19. 56
      docs/source/reference.rst
  20. 29
      docs/source/releases.rst
  21. 115
      docs/source/topics-configuration.rst
  22. 24
      docs/source/topics-overlays.rst
  23. 11
      docs/source/topics.rst
  24. 70
      help/docs/commands/pgsql.md
  25. 148
      help/docs/commands/posix.md
  26. 20
      help/docs/commands/python.md
  27. 24
      help/docs/getting-started.md
  28. BIN
      help/docs/how-to/images/slack-1.jpg
  29. BIN
      help/docs/how-to/images/slack-2.jpg
  30. BIN
      help/docs/how-to/images/slack-3.jpg
  31. BIN
      help/docs/how-to/images/twist-1.png
  32. BIN
      help/docs/how-to/images/twist-2.png
  33. 18
      help/en/docs/cli.md
  34. 21
      help/en/docs/getting-started.md
  35. 0
      help/en/docs/how-to/create-executable-script.md
  36. 0
      help/en/docs/how-to/define-custom-command.md
  37. 0
      help/en/docs/how-to/images/slack-1.jpg
  38. 0
      help/en/docs/how-to/images/slack-2.jpg
  39. 0
      help/en/docs/how-to/images/slack-3.jpg
  40. 0
      help/en/docs/how-to/images/twist-1.png
  41. 0
      help/en/docs/how-to/images/twist-2.png
  42. 0
      help/en/docs/how-to/post-message-slack.md
  43. 0
      help/en/docs/how-to/post-message-twist.md
  44. 14
      help/en/docs/how-to/use-with-commonkit.md
  45. 13
      help/en/docs/index.md
  46. 0
      help/en/docs/topics/itemized-commands.md
  47. 4
      help/en/docs/topics/steps-file.md
  48. 12
      help/en/docs/topics/templates.md
  49. 17
      help/en/docs/topics/variables.md
  50. 14
      help/en/docs/usage/centos.md
  51. 24
      help/en/docs/usage/django.md
  52. 19
      help/en/docs/usage/messages.md
  53. 29
      help/en/docs/usage/mysql.md
  54. 119
      help/en/docs/usage/pgsql.md
  55. 6
      help/en/docs/usage/php.md
  56. 264
      help/en/docs/usage/posix.md
  57. 49
      help/en/docs/usage/python.md
  58. 22
      help/en/docs/usage/ubuntu.md
  59. 23
      help/en/mkdocs.yml
  60. 4
      scripttease/cli/initialize.py
  61. 67
      scripttease/lib/commands/base.py
  62. 52
      scripttease/lib/commands/centos.py
  63. 20
      scripttease/lib/commands/django.py
  64. 69
      scripttease/lib/commands/messages.py
  65. 94
      scripttease/lib/commands/mysql.py
  66. 136
      scripttease/lib/commands/pgsql.py
  67. 6
      scripttease/lib/commands/php.py
  68. 300
      scripttease/lib/commands/posix.py
  69. 22
      scripttease/lib/commands/python.py
  70. 62
      scripttease/lib/commands/ubuntu.py
  71. 3
      scripttease/lib/loaders/base.py
  72. 2
      setup.py
  73. 17
      tests/test_lib_commands_pgsql.py
  74. 2
      tests/test_lib_commands_posix.py

2
.gitignore vendored

@ -12,7 +12,7 @@ _scraps
build
dist
docs/build
help/site
help/*/site
htmlcov
tmp.*
tmp

@ -13,6 +13,8 @@ COVERAGE_PATH := docs/build/html/coverage
#RELEASE := $(strip $(file < RELEASE.txt))
RELEASE := $(strip `cat RELEASE.txt`)
VERSION := $(strip `cat VERSION.txt`)
DOCS_PATH := $(PROJECT_HOME)/docs_diff6_com
DOCS_WWW := $(DOCS_PATH)/www
# Attempt to load a local makefile which may override any of the values above.
-include local.makefile
@ -72,6 +74,20 @@ publish:
secure:
bandit -r $(PACKAGE_NAME);
#> support - Build support (help) and reference documentation.
support:
cd docs && make dirhtml;
cd help/en/ && mkdocs build;
test -d $(DOCS_WWW)/en/$(PROJECT_NAME)/$(RELEASE) || mkdir -p $(DOCS_WWW)/en/$(PROJECT_NAME)/$(RELEASE);
test -d $(DOCS_WWW)/en/$(PROJECT_NAME)/$(RELEASE)/reference || mkdir -p $(DOCS_WWW)/en/$(PROJECT_NAME)/$(RELEASE)/reference;
cp -R docs/build/dirhtml/* $(DOCS_WWW)/en/$(PROJECT_NAME)/$(RELEASE)/reference/;
cp -R help/en/site/* $(DOCS_WWW)/en/$(PROJECT_NAME)/$(RELEASE)/;
cp meta.ini $(DOCS_PATH)/config/$(PROJECT_NAME).ini;
cd $(DOCS_WWW)/en/$(PROJECT_NAME) && ln -fs $(RELEASE) latest;
cd $(DOCS_WWW)/en/$(PROJECT_NAME) && ln -fs $(RELEASE) stable; # if product is stable
cd $(DOCS_PATH) && make build;
#> tests - Run unit tests and generate coverage report.
tests:
coverage run --source=. -m pytest;

@ -1,7 +1,13 @@
# Python Script Tease
![](https://img.shields.io/badge/status-active-green.svg)
![](https://img.shields.io/badge/stage-development-blue.svg)
![](https://img.shields.io/badge/stage-stable-green.svg)
![](https://img.shields.io/badge/coverage-100%25-green.svg)
A collection of classes and commands for automated command line scripting using Python.
A collection of classes and commands for automated command line scripting using Python.
## Install
```bash
pip install python-scripttease;
```

@ -54,8 +54,8 @@ clean:
.PHONY: html
html:
resrc pkg docs -I ../packages.ini -O rst --no-heading > source/_includes/project-dependencies.rst;
./generate_command_signatures.py > source/_includes/overlays.rst
#resrc pkg docs -I ../packages.ini -O rst --no-heading > source/_includes/project-dependencies.rst;
#./generate_command_signatures.py > source/_includes/overlays.rst
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."

@ -1,139 +0,0 @@
#! /usr/bin/env python
# Imports
from collections import OrderedDict
import inspect
import sys
# Set path before importing overlays.
sys.path.append("../")
# Import overlays
from scripttease.library.overlays.common import COMMON_MAPPINGS
from scripttease.library.overlays.centos import MAPPINGS as CENTOS_MAPPINGS
from scripttease.library.overlays.django import DJANGO_MAPPINGS
from scripttease.library.overlays.mysql import MYSQL_MAPPINGS
from scripttease.library.overlays.pgsql import PGSQL_MAPPINGS
from scripttease.library.overlays.posix import POSIX_MAPPINGS
from scripttease.library.overlays.ubuntu import MAPPINGS as UBUNTU_MAPPINGS
# Functions
# https://stackoverflow.com/a/52003056/241720
def get_signature(fn):
params = inspect.signature(fn).parameters
args = []
kwargs = OrderedDict()
for p in params.values():
if p.default is p.empty:
args.append(p.name)
else:
kwargs[p.name] = p.default
return args, kwargs
def print_description(text):
print(text)
print("")
def print_heading(title):
print(title)
print("=" * len(title))
print("")
def print_mapping(commands, excludes=None):
keys = list(commands.keys())
keys.sort()
_excludes = excludes or dict()
for key in keys:
if key in _excludes:
continue
func = commands[key]
docstring = func.__doc__
if not docstring:
continue
print(key)
print("-" * len(key))
print("")
for i in docstring.split("\n"):
print(i.strip())
# print("")
print(".. code-block:: ini")
print("")
print(" [run %s command]" % key)
args, kwargs = get_signature(func)
line = list()
for a in args:
if a != "kwargs":
line.append(a)
print(" %s: %s" % (key, " ".join(line)))
for option, value in kwargs.items():
if value is True:
_value = "yes"
elif value is False:
_value = "no"
else:
_value = value
print(" %s: %s" % (option, value))
print("")
# Overlay output.
print(".. generated by generate_command_signatures.py")
print("")
print_heading("Common")
print_description("Common commands are available to all overlays.")
print_mapping(COMMON_MAPPINGS)
print_heading("Django")
print_description("Django commands are available to all overlays.")
print_mapping(DJANGO_MAPPINGS)
print_heading("MySQL")
print_description("MySQL commands.")
print_mapping(MYSQL_MAPPINGS)
print_heading("Postgres")
print_description("Postgres commands.")
print_mapping(PGSQL_MAPPINGS)
print_heading("POSIX")
print_description("Posix commands form the basis of overlays for *nix platforms.")
print_mapping(POSIX_MAPPINGS, excludes=["func"])
exclude_from_centos = COMMON_MAPPINGS.copy()
exclude_from_centos.update(DJANGO_MAPPINGS)
exclude_from_centos.update(MYSQL_MAPPINGS)
exclude_from_centos.update(PGSQL_MAPPINGS)
exclude_from_centos.update(POSIX_MAPPINGS)
print_heading("Cent OS")
print_description("The Cent OS overlay incorporates commands specific to that platform as well as commands from "
"common, Django, MySQL, Postgres, and POSIX.")
print_mapping(CENTOS_MAPPINGS, excludes=exclude_from_centos)
exclude_from_ubuntu = COMMON_MAPPINGS.copy()
exclude_from_ubuntu.update(DJANGO_MAPPINGS)
exclude_from_ubuntu.update(MYSQL_MAPPINGS)
exclude_from_ubuntu.update(PGSQL_MAPPINGS)
exclude_from_ubuntu.update(POSIX_MAPPINGS)
print_heading("Ubuntu")
print_description("The Ubuntu overlay incorporates commands specific to that platform as well as commands from "
"common, Django, MySQL, Postgres, and POSIX.")
print_mapping(UBUNTU_MAPPINGS, excludes=exclude_from_ubuntu)

@ -1,3 +1,4 @@
files,language,blank,comment,code
22,Python,1124,981,1847
22,SUM,1124,981,1847
23,Python,1230,1178,2094
12,INI,54,83,174
35,SUM,1284,1261,2268

1 files language blank comment code
2 22 23 Python 1124 1230 981 1178 1847 2094
3 22 12 SUM INI 1124 54 981 83 1847 174
4 35 SUM 1284 1261 2268

@ -1,10 +1,6 @@
Requirements
------------
jinja2
------
**Install**
.. code-block:: bash
@ -14,7 +10,6 @@ jinja2
pygments
--------
**Install**
.. code-block:: bash
@ -24,7 +19,6 @@ pygments
python-commonkit
----------------
**URLs**
- `Source Code <https://github.com/develmaycare/python-commonkit>`_

File diff suppressed because it is too large Load Diff

@ -1,7 +1,7 @@
Coverage Requirements
---------------------
.....................
100% coverage is required for the ``master`` branch.
We always strive for 100% coverage in the ``master`` branch. The CLI is currently ignored.
See `current coverage report <coverage/index.html>`_.
@ -9,7 +9,7 @@ See `current coverage report <coverage/index.html>`_.
:file: ../_data/cloc.csv
Set Up for Testing
------------------
..................
Install requirements:
@ -35,16 +35,16 @@ Run a specific test:
.. code-block:: bash
python -m pytest tests/units/path/to/test.py
python -m pytest tests/path/to/test.py
To allow output from print statements within a test method, add the ``-s`` switch:
.. code-block:: bash
python -m pytest -s tests/units/path/to/test.py
python -m pytest -s tests/path/to/test.py
Reference
---------
.........
- `coverage <https://coverage.readthedocs.io/en/v4.5.x/>`_
- `pytest <https://pytest.org>`_

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

@ -1,86 +0,0 @@
.. _commands:
********
Commands
********
.. code-block:: text
usage: tease [-h] [-c] [-C= VARIABLES] [-d] [-D] [-f= FILTERS] [-O= OPTIONS] [-s] [-T= TEMPLATE_LOCATIONS] [-w= OUTPUT_FILE] [-V= VARIABLES_FILE]
[-v] [--version]
[path]
positional arguments:
path The path to the configuration file.
optional arguments:
-h, --help show this help message and exit
-c, --color Enable code highlighting for terminal output.
-C= VARIABLES, --context= VARIABLES
Context variables for use in pre-parsing the config and templates. In the form of: name:value
-d, --docs Output documentation instead of code.
-D, --debug Enable debug output.
-f= FILTERS, --filter= FILTERS
Filter the commands in the form of: attribute:value
-O= OPTIONS, --option= OPTIONS
Common command options in the form of: name:value
-s, --script Output commands as a script.
-T= TEMPLATE_LOCATIONS, --template-path= TEMPLATE_LOCATIONS
The location of template files that may be used with the template command.
-w= OUTPUT_FILE, --write= OUTPUT_FILE
Write the output to disk.
-V= VARIABLES_FILE, --variables-file= VARIABLES_FILE
Load variables from a file.
-v Show version number and exit.
--version Show verbose version information and exit.
NOTES
This command is used to parse configuration files and output the commands.
Using the Tease Command
=======================
The ``tease`` command may be used to parse a configuration file, providing additional utilities for working with commands.
The ``path`` argument defaults to ``commands.ini``.
Context Variables May be Provided on the Command Line
-----------------------------------------------------
To supply context variables on the command line:
.. code-block:: bash
tease -C domain_name:example.com -C domain_tld:example_com
Loading Context Variables from a File
-------------------------------------
Context variables may be loaded from a file:
.. code-block:: ini
[domain]
name = example.com
tld = example_com
The variables above are available as ``section_key``. For example, ``domain_name`` is ``example.com``.
.. code-block:: bash
tease -V variables.ini
Setting Common Options for All Commands
---------------------------------------
Rather than include a common parameter in the configuration file, it is possible to specify a common option on the command line.
.. code-block:: bash
tease -O sudo:yes
The Difference Between Variables and Options
--------------------------------------------
Variables are used to pre-process configuration files as templates, while common options are passed to *all* command instances.

@ -10,14 +10,14 @@ General Contact
Please use the `contact form at develmaycare.com`_ to inquire about training, commercial development or support, to
report security issues, and other questions or communications.
.. _contact form at develmaycare.com: https://develmaycare.com/contact/?product=ScriptTease
.. _contact form at develmaycare.com: https://develmaycare.com/contact/?product=python-scripttease
Issue Tracker
=============
Bugs and feature requests are logged with the `issue tracker`_. Do *not* log security issues here.
.. _issue tracker: https://github.com/develmaycare/python-scripttease/issues
.. _issue tracker: https://gittraction.com/diff6/python-scripttease/issues
Support
=======

@ -0,0 +1,77 @@
************
Contributing
************
We welcome contributions to this and any of `our open source projects`_. There are a number of ways to participate and contribute. See :ref:`contact`.
.. _our open source projects: https://docs.diff6.com
Issue Management
================
Reporting Issues
----------------
If you have found a bug or error in the documentation, please submit a request. See :ref:`contact`.
.. important::
Do **not** report security issues using the issue tracker. Instead, send an email to security@develmaycare.com with details on the issue you've discovered.
Submitting Feature Requests
---------------------------
Although we reserve the right to decline new features, we welcome all feature requests. See :ref:`contact`.
Testing and Quality Control
---------------------------
Testing involves using Script Tease in real life or in development. Feel free to report any issues you find, or to improve the unit tests.
Pull Requests
-------------
Pull requests are welcome. Such requests should be associated with an issue. We may ignore pull requests that do not have a corresponding issue, so create an issue if one does not already exist.
Promotion
=========
You may help spread awareness of Script Tease by writing blog posts. We are happy to link out to reviews and tutorials from our web site. `Let us know if you've created a blog post`_ that we can share. Be sure to include a link to the post.
You may also provide us with a guest post to be included on our blog.
.. _Let us know if you've created a blog post: https://develmaycare.com/contact/?product=Script%20Tease
.. note::
We reserve the right to proof and approve or decline all content posted on our web site.
Development
===========
Setting Up For Development
--------------------------
1. Clone the repo at https://github.com/develmaycare/python-scripttease
2. Create a virtual environment and install the requirements from ``requirements.pip``
Style Guide
-----------
Script Tease follows `PEP8`_ and (where appropriate) the `Django style guide`_ and `JavaScript Standard Style`_.
.. _Django style guide: https://docs.djangoproject.com/en/stable/internals/contributing/writing-code/coding-style/
.. _JavaScript Standard Style: https://standardjs.com
.. _PEP8: https://www.python.org/dev/peps/pep-0008/
We *do* make a few exceptions and provide additional guidance which is documented in our `developer docs`_.
.. _developer docs: https://docs.develmaycare.com/en/developer/
Dependencies
------------
.. include:: _includes/dependencies.rst
Testing
-------
.. include:: _includes/tests.rst

@ -1,32 +0,0 @@
.. _getting-started:
***************
Getting Started
***************
System Requirements
===================
Python 3.6 or greater is required.
Install
=======
To install:
.. code-block:: bash
pip install python-scripttease;
Configuration
=============
See :ref:`topics-configuration` for creating a command configuration file.
FAQs
====
Have a question? `Just ask`_!
.. _Just ask: https://develmaycare.com/contact/?product=Script%20Tease

@ -1,236 +0,0 @@
.. _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.

@ -1,22 +1,20 @@
Script Tease
============
Script Tease Developer Documentation
====================================
.. raw:: html
Resources for turning plain text instructions (INI, YAML) into Bash scripting statements.
<p>
<a href="https://develmaycare.com/products/python/script-tease/">Project Home Page</a>
</p>
.. note::
This documentation is for developers interested in working with the Script Tease library to create a custom implementation, or to contribute to the product. For usage information, please refer to the `user help page`_.
.. _user help page: https://docs.diff6.com/en/python-scripttease
.. toctree::
:maxdepth: 2
Introduction <introduction>
Getting Started <getting-started>
How To <how-to>
Commands <commands>
Topics <topics>
Reference <reference>
Project <project>
Code Reference <reference>
Releases <releases>
Contributing <contributing>
Contact <contact>
Indices and tables

@ -7,30 +7,96 @@ Introduction
Overview
========
Script Tease is a library and command line tool for generating commands programmatically or (especially) using configuration files.
The Script Tease package takes plain text instructions in INI or YAML (currently untested) format and converts them into valid command line statements for a given platform (currently CentOS and Ubuntu are supported). It does *not* provide support for executing these statements.
The primary focus (and limit) is to convert plain text instructions into valid command line statements for a given platform (see `Overlays`_). It does *not* provide support for executing those statements.
Architecture
============
The provided command line interface (the ``tease`` command) loads configuration files containing instructions and may output these as a Bash script or as documentation.
However, it is possible to create your own implementation to provide input and gather output from the Script Tease library.
.. image:: _static/images/architecture-diagram.png
Concepts
========
Generating Commands
-------------------
Loaders
-------
A Loader instance is responsible for reading configuration files and parsing the contents into a) a command name, b) arguments, c) keyword arguments.
Command instances are *not* created by the loader. This is the job of the :py:func:`scriptease.lib.factories.command_factory`.
Separating Command generation from loading configuration files makes the loader classes simpler, and keeps the overall system more flexible.
Command Functions
-----------------
Script Tease may be used in two (2) ways:
Commands are represented by Python functions. All functions return either a :py:class:`scripttease.lib.commands.base.Command` instance, or in some cases an instance of :py:class:`scripttease.lib.commands.base.MultipleCommands`. (The Template command may also be returned; it is handled specially.)
1. Using the library to programmatically define commands and export them as command line statements. See :ref:`developer-reference`.
2. Using the ``tease`` command to generate commands from a configuration file. See :ref:`topics-configuration`.
.. code-block:: python
Overlays
def copy(from_path, to_path, overwrite=False, recursive=False, **kwargs):
# ...
You could of course use this function directly:
.. code-block:: python
command = copy("/etc", "/tmp/", recursive=True)
print(command.get_statement())
But the point of Script Tease is to take plain text instructions and turn them into command line statements.
Mappings
--------
An *overlay* is a set of command meta functions that define the capabilities of a specific operating system.
So the `copy` function is mapped to a name that may be used in a configuration file:
.. code-block:: python
MAPPINGS = {
# ...
'copy': copy,
# ...
}
.. note::
At present, the only fully defined operating system overlays are for Cent OS and Ubuntu.
Such mappings are used by the :py:func:`scriptease.lib.factories.command_factory` to match a command name in a configuration file to the Python function that instantiates the Command.
See :ref:`topics-overlays`.
Configuration File
------------------
The configuration that invokes this command would like like:
.. code-block:: ini
[create a copy of the etc directory]
copy: /etc /tmp/
recursive: yes
Note that the configuration is self-documenting, and in fact, it is possible to output commands as documentation rather than a script.
Variables File
--------------
Prior to a Loader processing commands, configuration files may be parsed as Jinja2 templates. This allows variables to be loaded from a file and passed to the loader as a Context instance.
.. code-block:: ini
# variables.ini
[db_name]
comment: The name of the database is the same as the domain name.
value: example_app
In the configuration file:
.. code-block:: ini
[create the database]
pgsql.create: {{ db_name }}
Variables are collected in a :py:class:`scripttease.lib.contexts.Context` instance and are passed to the Loader.
Terms and Definitions
=====================
@ -38,10 +104,43 @@ Terms and Definitions
command
When used in Script Tease documentation, this is a command instance which contains the properties and parameters for a command line statement.
profile
A profile represents the commands available to a specific operating system.
statement
A specific statement (string) to be executed. A *statement* is contained within a *command*.
A specific statement (string) to be executed. A *statement* is generated by a *command*.
License
=======
Script Tease is released under the BSD 3 clause license.
.. code-block:: text
Copyright (c) Pleasant Tents, LLC
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of Pleasant Tents, LLC nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

@ -1,140 +0,0 @@
*******
Project
*******
Contributing
============
We welcome contributions to this and any of `our open source projects`_. There are a number of ways to participate and contribute. See :ref:`contact`.
.. _our open source projects: https://develmaycare.com/products/
Reporting Issues
----------------
Perhaps the easiest way to contribute is to submit an issue. If you have found a bug or error in the documentation, please submit a request. See :ref:`contact`.
.. important::
Do **not** report security issues using the issue tracker. Instead, send an email to security@develmaycare.com with details on the issue you've discovered.
Submitting Feature Requests
---------------------------
Although we reserve the right to decline new features, we welcome all feature requests. See :ref:`contact`.
Testing and Quality Control
---------------------------
Testing involves using Script Tease in real life or in development. Feel free to report any issues you find, or to improve the unit tests.
Pull Requests
-------------
Pull requests are welcome. Such requests should be associated with an issue. We may ignore pull requests that do not have a corresponding issue, so create an issue if one does not already exist.
Blogging
--------
You may help spread awareness of Script Tease by writing blog posts. We are happy to link out to reviews and tutorials from our web site. `Let us know if you've created a blog post`_ that we can share. Be sure to include a link to the post.
You may also provide us with a guest post to be included on our blog.
.. _Let us know if you've created a blog post: https://develmaycare.com/contact/?product=Script%20Tease
.. note::
We reserve the right to proof and approve or decline all content posted on our web site.
Development
===========
Setting Up For Development
--------------------------
1. Clone the repo at https://github.com/develmaycare/python-scripttease
2. Create a virtual environment and install the requirements from ``requirements.pip``
3. See :ref:`how-to`.
Style Guide
-----------
Script Tease follows `PEP8`_ and (where appropriate) the `Django style guide`_ and `JavaScript Standard Style`_.
.. _Django style guide: https://docs.djangoproject.com/en/stable/internals/contributing/writing-code/coding-style/
.. _JavaScript Standard Style: https://standardjs.com
.. _PEP8: https://www.python.org/dev/peps/pep-0008/
We *do* make a few exceptions and provide additional guidance which is documented in our `developer docs`_.
.. _developer docs: https://docs.develmaycare.com/en/developer/
Dependencies
============
.. include:: _includes/project-dependencies.rst
Tests
=====
.. include:: _includes/project-tests.rst
Releasing
=========
Versioning
----------
Script Tease follows a loose form of `semantic versioning`_. The use of semantic versioning makes it clear when deprecation occurs and backward compatibility is removed. Documented incompatibilities may still exist where deprecation is not feasible (technically or financially).
.. _semantic versioning: https://semver.org/
Cadence
-------
New features (and especially new overlays) are planned for release every 3 months. Patch-level changes (to fix bugs or security issues) are always released as needed.
Long-Term Support
-----------------
Some releases may be designated as long-term support (LTS) releases. Such releases will have security and critical bug fixes applied for 6 months.
Deprecation Policy
------------------
Minor releases may deprecate features from a previous minor release. For example, if a feature is deprecated in release 1.1, it will continue to work in the 1.2 release, though warnings may be raised. However, the deprecated feature may be removed in release 1.3 and may not function as previously expected or will raise errors.
Major releases may *always* remove deprecated features.
Patch-level releases *never* remove deprecated features.
Legal
=====
.. code-block:: text
Copyright (c) Pleasant Tents, LLC
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of Pleasant Tents, LLC nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

@ -1,8 +1,8 @@
.. _developer-reference:
.. _code-reference:
*******************
Developer Reference
*******************
**************
Code Reference
**************
Constants
=========
@ -12,11 +12,19 @@ Constants
:show-inheritance:
:special-members: __init__
Library
=======
Exceptions
==========
.. automodule:: scripttease.exceptions
:members:
:show-inheritance:
:special-members: __init__
Commands
--------
========
Base
----
.. automodule:: scripttease.lib.commands.base
:members:
@ -24,7 +32,7 @@ Commands
:special-members: __init__
Centos
......
------
.. automodule:: scripttease.lib.commands.centos
:members:
@ -32,7 +40,7 @@ Centos
:special-members: __init__
Django
......
------
.. automodule:: scripttease.lib.commands.django
:members:
@ -40,7 +48,7 @@ Django
:special-members: __init__
Messages
........
--------
.. automodule:: scripttease.lib.commands.messages
:members:
@ -48,7 +56,7 @@ Messages
:special-members: __init__
MySQL
......
-----
.. automodule:: scripttease.lib.commands.mysql
:members:
@ -56,7 +64,7 @@ MySQL
:special-members: __init__
PHP
...
---
.. automodule:: scripttease.lib.commands.php
:members:
@ -64,7 +72,7 @@ PHP
:special-members: __init__
Postgres
........
--------
.. automodule:: scripttease.lib.commands.pgsql
:members:
@ -72,7 +80,7 @@ Postgres
:special-members: __init__
Posix
.....
-----
.. automodule:: scripttease.lib.commands.posix
:members:
@ -80,7 +88,7 @@ Posix
:special-members: __init__
Python
......
------
.. automodule:: scripttease.lib.commands.python
:members:
@ -88,7 +96,7 @@ Python
:special-members: __init__
Ubuntu
......
------
.. automodule:: scripttease.lib.commands.ubuntu
:members:
@ -98,7 +106,7 @@ Ubuntu
Contexts
========
.. automodule:: scripttease.contexts
.. automodule:: scripttease.lib.contexts
:members:
:show-inheritance:
:special-members: __init__
@ -106,7 +114,7 @@ Contexts
Factories
=========
.. automodule:: scripttease.factories
.. automodule:: scripttease.lib.factories
:members:
:show-inheritance:
:special-members: __init__
@ -122,10 +130,18 @@ Base
:show-inheritance:
:special-members: __init__
Config (INI)
------------
INI
---
.. automodule:: scripttease.lib.loaders.ini
:members:
:show-inheritance:
:special-members: __init__
YAML
----
.. automodule:: scripttease.lib.loaders.yaml
:members:
:show-inheritance:
:special-members: __init__

@ -0,0 +1,29 @@
********
Releases
********
Versioning
==========
Script Tease follows a loose form of `semantic versioning`_. The use of semantic versioning makes it clear when deprecation occurs and backward compatibility is removed. Documented incompatibilities may still exist where deprecation is not feasible (technically or financially).
.. _semantic versioning: https://semver.org/
Cadence
=======
New features (and especially new overlays) are planned for release every 3 months. Patch-level changes (to fix bugs or security issues) are always released as needed.
Long-Term Support
=================
Some releases may be designated as long-term support (LTS) releases. Such releases will have security and critical bug fixes applied for 6 months.
Deprecation Policy
==================
Minor releases may deprecate features from a previous minor release. For example, if a feature is deprecated in release 1.1, it will continue to work in the 1.2 release, though warnings may be raised. However, the deprecated feature may be removed in release 1.3 and may not function as previously expected or will raise errors.
Major releases may *always* remove deprecated features.
Patch-level releases *never* remove deprecated features.

@ -1,115 +0,0 @@
.. _topics-configuration:
*************
Configuration
*************
Generating Commands From a File
===============================
The :py:class:`scripttease.parsers.ini.Config` class may instantiate commands by loading a configuration file.
.. note::
Additional formats such as JSON or YAML may be supported in the future.
An example file:
.. code-block:: ini
[install apache]
install: apache2
[create the web site directory]
mkdir: /var/www/domains/example_com/www
recursive: yes
[set permissions on the website directory]
perms: /var/www/domains/example_com/www
group: www-data
mode: 775
owner: www-data
Notes regarding this format:
- This is the standard format for Python's ConfigParser. If you prefer, you may use ``=`` instead of ``:``.
- The first part of each command is the INI section and is used as the default comment.
- The command name *must* be the *first* option in the section.
- The arguments for the command appear as the value of the first option in the section. Arguments are separated by a
space.
- Arguments that should be treated as a single value should be enclosed in double quotes.
- ``yes`` and ``no`` are interpreted as boolean values.
- List values, where required, are separated by commas.
.. _topics-configuration-common-parameters:
Common Parameters
-----------------
All commands support the following common parameters:
- ``comment``: A comment regarding the command.
- ``condition``: A condition for execution. For example, ``! -f /path/to/some/file.txt``
- ``cd``: The path from which a command should be executed.
- ``environments``: A string or list of comma-separated strings indicating the operational environments in which the command runs. This is *not* used by default, but may be used to programmatically filter commands for a specific environment. For example, development versus live.
- ``prefix``: A statement to be added prior to executing the command.
- ``register``: A variable name to which the the success or failure (exit code) of the statement is captured.
- ``shell``: The shell used to run the commands. For example, ``/bin/bash``. This is generally not important, but can be a problem when attempting to execute some commands (such as Django management commands).
- ``stop``: ``True`` indicates no other commands should be executed if the given command fails.
- ``sudo``: ``True`` indicates the command should be automatically prefixed with ``sudo``. If provided as a string, the command is also prefixed with a specific user name.
- ``tags``: A list of tags used to classify the command.
Defining an "Itemized" Command
------------------------------
Certain command definitions may be repeated by defining a list of items.
Example of an "itemized" command:
.. code-block:: ini
[create multiple directories]
mkdir: /var/www/domains/example_com/$item
items: www, www/assets, www/content
recursive: yes
[touch a bunch of files]
touch: /var/www/domains/example_com/www/$item
items: index.html, assets/index.html, content/index.html
.. note::
Command itemization may vary with the command type.
Pre-Parsing Command Files as Templates
======================================
Configuration file may be pre-processed as a Jinja2 template by providing a context dictionary:
.. code-block:: ini
[install apache]
install: apache
[create the website directory]
mkdir: /var/www/domains/{{ domain_tld }}/www
recursive: yes
[set permissions on the website directory]
perms: /var/www/domains/{{ domain_tld }}/www
group: www-data
mode: 775
owner: www-data
Then with a config instance:
.. code-block:: python
context = {
'domain_tld': "example_com",
}
config = Config("commands.ini", context=context)
config.load()
for command in config.get_commands():
print(command.get_statement(cd=True))
print("")

@ -1,24 +0,0 @@
.. _topics-overlays:
********
Overlays
********
An overlay is a collection of functions that provide an interface to command creation. An overlay allows configuration files to specify commands in a generic way. When the file is loaded, an overlay may be specified which Script Tease uses to generate commands that are specific to a given operating system.
There are currently four (5) general and re-usable overlays:
- common
- django
- mysql
- pgsql
- posix
And two (2) overlays that are specific to operating systems:
- centos
- ubuntu
The examples that follow instantiate command instances from an INI file. Each example is shown with the defaults. All commands support a number of :ref:`topics-configuration-common-parameters`.
.. include:: _includes/overlays.rst

@ -1,11 +0,0 @@
.. _topics:
******
Topics
******
.. toctree::
:maxdepth: 2
Configuration <topics-configuration>
Overlays <topics-overlays>

@ -1,70 +0,0 @@
# PostgreSQL
Summary: Work with Postgres databases.
## Common Options
- `admin_pass`: The password off the admin-authorized user.
- `admin_user`: The user name of the admin-authorized user. Default: `postgres`
- `host`: The host name. Default: `localhost`
- `port`: The TCP port. Default: `5432`
## Available Commands
### pgsql.create
Create a database. Argument is the database name.
- `owner`: The user name that owns the database.
```ini
[create the database]
pgsql.create: database_name
```
### pgsql.drop
Drop a database. Argument is the database name.
### pgsql.dump
Dump the database schema. Argument is the database name.
- `path`: The path to the dump file. Default: `dump.sql`
### pgsql.exec
Execute an SQL statement. Argument is the SQL statement.
- `database`: The name of the database where the statement will be executed. Default: `default`
### pgsql.exists
Determine if a database exists. Argument is the database name.
### pgsql.user
Create a user. Argument is the user name.
- `password`: The user's password.
```ini
[create a database user]
pgsql.user: username
```
Remove a user.
```ini
[remove a database user]
pgsql.user: username
op: remove
```
Determine if a user exists.
```ini
[determine if database user exists]
pgsql.user: username
op: exists
```

@ -1,148 +0,0 @@
# POSIX
Summary: Work with common POSIX-compliant commands..
## Available Commands
### append
Append content to a file. Argument is the file name.
- `content`: The content to be appended.
### archive
Create an archive (tarball). Argument is the target file or directory.
- `absolute`: Don't strip leading slashes from file names.
- `view`: View the progress.
- `exclude`: Exclude file name patterns.
- `strip`: Strip component paths to the given depth (integer).
- `to`: The path to where the archive will be created.
### copy
Copy a file or directory. First argument is the target file/directory. Second argument is the destination.
- `overwrite`: Overwrite an existing target.
- `recursive`: Copy directories recursively.
### dir
Create a directory. Argument is the path.
- `group`: Set the group to the given group name.
- `mode`: Set the mode on the path.
- `owner`: Set the owner to the given owner name.
- `recursive`: Create the full path even if intermediate directories do not exist.
### extract
Extract an archive (tarball). Argument is the path to the archive file.
- `absolute`: Strip leading slashes from file names.
- `view`: View the progress.
- `exclude`: Exclude file name patterns.
- `strip`: Strip component paths to the given depth (integer).
- `to`: The path to where the archive will be extracted. Defaults to the current working directory.
### file
Create a file. Argument is the path.
- `content`: The content of the file. Otherwise, an empty file is created.
- `group`: Set the group to the given group name.
- `mode`: Set the mode on the path.
- `owner`: Set the owner to the given owner name.
### link
Create a symlink. First argument is the target. Second argument is the destination.
- `force`: Force creation of the link.
### move
Move a file or directory. First argument is the target. Second argument is the desitnation.
### perms
Set permissions on a file or directory. Argument is the path.
- `group`: Set the group to the given group name.
- `mode`: Set the mode on the path.
- `owner`: Set the owner to the given owner name.
- `recursive`: Apply permission recursively (directories only).
### push
Push (rsync) a path to a remote server. First argument is the local path. Second argument is the remote path.
- `delete`: Delete existing files/directories.
- `host`: The host name. Required.
- `key_file`: Use the given SSL (private) key. Required.
- `links`: Copy symlinks.
- `exclude`: Exclude patterns from the given (local) file.
- `port`: The TCP port on the host. Default: `22`
- `recursive`: Operate recursively on directories.
- `user`: The user name. Required.
### remove
Remove a file or directory. Argument is the path.
- `force`: Force the removal.
- `recursive`: Remove (directories) rescurisvely.
### rename
Rename a file or directory. First argument is the target. Second argument is the destination.
### replace
Replace something in a file. First argument is the path.
- `backup`: Create a backup.
- `delimiiter`: The sed delimiter. Default: `/`
- `find`: The text to be found. Required.
- `sub`: The text to be replaced. Required.
### scopy
Copy a file to a remote server. First argument is the local file name. Second argument is the remote destination.
- `key_file`: The private key file to use for the connection.
- `host`: The host name. Required.
- `port`: The TCP port. Default: `22`
- `user`: The user name. Required.
### ssl
Use Let's Encrypt (certbot) to acquire an SSL certificate. Argument is the domain name.
- `email`: The email address for "agree tos". Default: `webmaster@domain_name`
- `webroot`: The webroot to use. Default: `/var/www/maint/www`
### sync
Sync (rsync) local files and directories. First argument is the target. Second argument is the destination.
- `delete`: Delete existing files/directories.
- `links`: Copy symlinks.
- `exclude`: Exclude patterns from the given (local) file.
- `recursive`: Operate recursively on directories.
### touch
Touch a file, whether it exists or not. Argument is the path.
### wait
Wait for n number of seconds before continuing. Argument is the number of seconds.
### write
Write to a file. Argument is the path.
- `content`: The content to write to the file. Replaces existing content.

@ -1,20 +0,0 @@
# Python
Summary: Work with Python.
## Available Commands
### pip
Use the pip command. Argument is the package name.
- `op`: The operation; `install` (the default), `remove`, or `updgrade`.
- `venv`: The name of the virtual environment to use.
### pip3
Use Python3 pip. See pip above.
### virtualenv
Create a python virtual environment. Argument is the environment name.

@ -1,24 +0,0 @@
# Getting Started
## System Requirements
Python 3.6 or greater is required.
## Install
To install:
```bash
pip install python-scripttease;
```
## Configuration
See [Commands File](topics/command-file.md) for creating a configuration file.
## FAQs
Have a question? `Just ask`_!
.. _Just ask: https://develmaycare.com/contact/?product=Script%20Tease

Binary file not shown.

Before

Width:  |  Height:  |  Size: 301 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 393 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 181 KiB

@ -2,7 +2,7 @@
title: CLI
---
The `tease` command may be used to parse a configuration file, providing additional utilities for working with commands.
The `tease` command may be used to parse a steps file, providing additional utilities for working with commands.
## Getting Help
@ -36,7 +36,7 @@ A nice benefit of using configuration for commands is that the information may b
Additionally, Script Tease provides the [explain](commands/messages.md#explain) and [screenshot](commands/messages.md#screenshot) commands that help provide extra content for documentary output.
```text
usage: tease docs [-h] [-o= {html,md,plain,rst}] [-C= VARIABLES] [-i= COMMAND_FILE] [-O= OPTIONS] [-P= {centos,ubuntu}]
usage: tease docs [-h] [-o= {html,md,plain,rst}] [-C= VARIABLES] [-i= STEPS_FILE] [-O= OPTIONS] [-P= {centos,ubuntu}]
[-T= TEMPLATE_LOCATIONS] [-w= OUTPUT_FILE] [-V= VARIABLES_FILE] [-D] [-p]
optional arguments:
@ -45,7 +45,7 @@ optional arguments:
The output format; HTML, Markdown, plain text, or ReStructuredText.
-C= VARIABLES, --context= VARIABLES
Context variables for use in pre-parsing the config and templates. In the form of: name:value
-i= COMMAND_FILE, --input-file= COMMAND_FILE
-i= STEPS_FILE, --input-file= STEPS_FILE
The path to the configuration file.
-O= OPTIONS, --option= OPTIONS
Common command options in the form of: name:value
@ -91,13 +91,13 @@ optional arguments:
The `script` sub-command exports command configuration to actual Bash statements. Minimum usage is
```bash
tease -i commands.ini
tease -i steps.ini
```
This will output the statements represented in the specified configuration file. There are quite a few other parameters.
```text
usage: tease script [-h] [-c] [-s] [-C= VARIABLES] [-i= COMMAND_FILE] [-O= OPTIONS] [-P= {centos,ubuntu}] [-T= TEMPLATE_LOCATIONS]
usage: tease script [-h] [-c] [-s] [-C= VARIABLES] [-i= STEPS_FILE] [-O= OPTIONS] [-P= {centos,ubuntu}] [-T= TEMPLATE_LOCATIONS]
[-w= OUTPUT_FILE] [-V= VARIABLES_FILE] [-D] [-p]
optional arguments:
@ -106,7 +106,7 @@ optional arguments:
-s, --shebang Add the shebang to the beginning of the output.
-C= VARIABLES, --context= VARIABLES
Context variables for use in pre-parsing the config and templates. In the form of: name:value
-i= COMMAND_FILE, --input-file= COMMAND_FILE
-i= STEPS_FILE, --input-file= STEPS_FILE
The path to the configuration file.
-O= OPTIONS, --option= OPTIONS
Common command options in the form of: name:value
@ -146,14 +146,14 @@ value = example.com
value = example_com
```
The variables above are available as template variables in a commands file.
The variables above are available as template variables in a steps file.
For example, ``{{ domain_name }}`` becomes ``example.com``.
To load the variables, use the `-V` switch:
To load the variables file, use the `-V` switch:
```bash
tease -V variables.ini
tease -i steps.ini -V variables.ini
```

@ -0,0 +1,21 @@
# Getting Started
## System Requirements
Python 3.6 or greater is required.
## Install
To install:
```bash
pip install python-scripttease;
```
## Configuration
See [Steps File](topics/steps-file.md) for creating a configuration file.
## FAQs
**Does Script Tease execute commands?** No. Script Tease generates statements that may be [saved to a file for examination and execution](how-to/create-executable-script.md). Or a [custom implementation](how-to/use-with-commonkit.md) may be created to execute the generated statements.

Before

Width:  |  Height:  |  Size: 301 KiB

After

Width:  |  Height:  |  Size: 301 KiB

Before

Width:  |  Height:  |  Size: 393 KiB

After

Width:  |  Height:  |  Size: 393 KiB

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 111 KiB

Before

Width:  |  Height:  |  Size: 181 KiB

After

Width:  |  Height:  |  Size: 181 KiB

@ -1,11 +1,12 @@
# 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](https://docs.develmaycare.com/en/python-commonkit/stable/components/#module-commonkit.shell) *does* provide support for executing commands in local POSIX environments.
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](https://docs.develmaycare.com/en/python-commonkit/stable/components/#module-commonkit.shell) *does* provide support for executing commands in local POSIX environments.
Here is an example of how to use these packages together:
```python
from commonkit.shell import Command
from scripttease.exceptions import InvalidInput
from scripttease.lib.factories import command_factory
from scripttease.lib.loaders import INILoader
@ -30,16 +31,21 @@ def execute(step):
exit(command.code)
ini = INILoader("path/to/commands.ini")
ini = INILoader("path/to/steps.ini")
ini.load()
steps = command_factory(ini)
try:
steps = command_factory(ini)
except InvalidInput as e:
print("%s: I can't go on." % e)
exit(1)
# A failure to load results in None.
if steps is None:
print("Failed to load steps. Bummer.")
exit(1)
# Iterate through each step to create a COMMON KIT command instance.
# Iterate through each step to create a Common Kit command instance.
for step in steps:
# To preview ...

@ -7,7 +7,7 @@ Script Tease is a library and command line tool for generating Bash commands pro
The primary focus (and limit) is to convert plain text instructions (in INI or YAML format) into valid command line statements for a given platform. It does *not* provide support for executing those statements.
!!! warning
YAML support is untested.
YAML support is currently untested.
## Concepts
@ -16,10 +16,10 @@ The primary focus (and limit) is to convert plain text instructions (in INI or Y
Script Tease may be used in three (3) ways:
1. Using the library to programmatically define commands and export them as command line statements.
2. Using the `tease` command to generate commands from a configuration file. See [command file configuration](topics/command-file.md).
2. Using the `tease` command to generate commands from a configuration file. See [steps file](topics/steps-file.md).
3. Using the command file format and library to create a custom implementation.
This documentation focuses on the second method, but the developer docs may be used in your own implementation.
This documentation focuses on the second method, but the [developer docs](./reference) may be used to guide custom implementations for 1 and 3 above.
### Self-Documenting
@ -53,10 +53,13 @@ Profiles import and appropriate all other available commands.
## Terms and Definitions
command
: When used in Script Tease documentation, this is a command instance which contains the properties and parameters for assembling a command line statement.
: When used in Script Tease documentation, this is a command instance which contains the properties and parameters for assembling a command line statement.
profile
: A collection of commands that work for a specific operating system profile.
statement
: A specific statement (string) to be executed. A *statement* is generated from a *command*.
: A specific statement (string) to be executed. A *statement* is generated from a *command*.
## License

@ -1,6 +1,6 @@
# Command File
# Steps File
A command file contains the metadata about the commands to be generated. INI and YAML formats are supported.
A steps file contains the metadata about the commands to be generated. INI and YAML formats are supported.
In an INI file, each section is a command. With YAML, each top-level list item is a command.

@ -4,16 +4,22 @@ Script Tease supports processing of template files using Jinja2.
## Location of Templates
By default, the location of template files are reckoned as relative to the commands configuration file.
By default, the location of template files are reckoned as relative to the steps file.
```text
package_name/
|-- commands.ini
|-- steps.ini
|-- templates
| `-- httpd.conf
```
Upon loading the `commands.ini` file, any reference to a template file is assumed to be in the `templates/` directory in the same location.
Upon loading the `steps.ini` file, any reference to a template file is assumed to be in the `templates/` directory in the same location.
```ini
[create the apache config]
template: httpd.conf /etc/apache2/sites-available/example.app.conf
```
!!! tip
The `-T` switch of the `tease` command may be used to add template locations.

@ -1,8 +1,8 @@
# Variables File
A variables file contains variable definitions that may be used as the context for parsing a [command file](command-file.md) *before* the actual commands are generated.
A variables file contains variable definitions that may be used as the context for parsing a [steps file](steps-file.md) *before* the actual commands are generated.
Unlike a command file, the INI format is the only supported format for a variables file.
Unlike a steps file, the INI format is currently the only supported format for a variables file.
## The Variable Name
@ -35,7 +35,6 @@ You may define an environment for any given variable that may be used for filter
```ini
[database_host:development]
comment: Local host used in development.
value: localhost
[database_host:live]
@ -48,6 +47,16 @@ In this way, variables of the same name may be supported across different deploy
As demonstrated in the example above, you may comment on a variable by adding a `comment:` attribute to the section.
```ini
[database_host:testing]
comment: Local host used when testing.
value: localhost
[database_host:live]
comment: Separate server used when live.
value: db1.example.com
```
## Defining Tags
Tags may be defined for any variable as a comma separated list. This is useful for filtering.
@ -68,7 +77,7 @@ tags: application
## Other Attributes
Any other variable defined in the section is dynamically available.
Any other variable defined in the section is dynamically available. These are not used by Script Tease, but implementers may find this feature useful.
```ini
[domain_name]

@ -54,8 +54,6 @@ Run any shell command.
run: "ls -ls"
```
Note that commands with arguments will need to be in quotes.
### start
Start a service:
@ -82,6 +80,18 @@ Work with the system.
- `update`
- `upgrade`
```ini
[update package info]
update:
[upgrade the system]
upgrade:
[reboot the system]
reboot:
```
### uninstall
Uninstall a package.

@ -16,7 +16,7 @@ venv: ../python
## Automatic Conversion of Django Command Switches
Options provided in the command configuration file are automatically converted to command line switches.
Options provided in the steps file are automatically converted to command line switches.
```ini
[run database migrations]
@ -31,6 +31,8 @@ natural_primary: yes
```
`settings` becomes "--settings=tenants.example_com.settings". `indent` becomes "--indent=4". `natural_foreign` and `natural_primary` become "--natural-foreign" and "--natural-primary" respectively.
## Available Commands
### check
@ -69,9 +71,9 @@ Alias: dump
Dump fixture data.
- target (str): Required. The name of the app or `app.Model`.
- format (str): `json` (default) or `xml`.
- path (str): The path to the output file. When a model is provided, this defaults to `fixtures/app/model.json`. Otherwise, it is `fixtures/app/initial.json`.
- `target` (str): Required. The name of the app or `app.Model`.
- `format` (str): `json` (default) or `xml`.
- `path` (str): The path to the output file. When a model is provided, this defaults to `fixtures/app/model.json`. Otherwise, it is `fixtures/app/initial.json`.
```ini
[dump project data]
@ -89,9 +91,9 @@ Alias: load
Load fixture data.
- target (str): Required. The name of the app or `app.Model`.
- format (str): `json` (default) or `xml`
- path (str): The path to the JSON file. When a model is provided, this defaults to `fixtures/app/model.json`. Otherwise, it is `fixtures/app/initial.json`.
- `target` (str): Required. The name of the app or `app.Model`.
- `format` (str): `json` (default) or `xml`
- `path` (str): The path to the JSON file. When a model is provided, this defaults to `fixtures/app/model.json`. Otherwise, it is `fixtures/app/initial.json`.
```ini
[load project categories]
@ -117,14 +119,14 @@ It is possible to work with any Django management command provided the parameter
```ini
[run any django command]
django: command_name
first_option_name: asdf
second_option_name: 1234
third_option_name: yes
option_one: asdf
option_two: 1234
option_three: yes
```
This will generate a statement like:
```bash
./manage.py command_name --first-option-name="asdf" --second-option-name=1234 --third-option-name
./manage.py command_name --option-one="asdf" --option-two=1234 --option-three
```

@ -8,9 +8,9 @@ Summary: Send feedback to users.
Use the dialog CLI to display a message.
- `height`: The height of the dialog box. Default: `15`
- `title`: An optional title to display as part of the dialog box. Default: `Message`.
- `width`: The width of the dialog box. Default: `100`
- `height` (int): The height of the dialog box. Default: `15`
- `title` (str): An optional title to display as part of the dialog box. Default: `Message`.
- `width` (int): The width of the dialog box. Default: `100`
```ini
[send some feedback]
@ -56,7 +56,7 @@ echo: "This is a message."
Send a message via Slack.
- `url`: Required. The URL to which the message should be sent.
- `url` (str): Required. The URL to which the message should be sent.
```ini
[send some feedback]
@ -77,6 +77,11 @@ url: https://subdomain.slack.com/path/to/your/integration
Like `explain` above, a screenshot adds detail to comments or documentation, but does not produce a command statement.
- `caption` (str): An optional caption for the image.
- `css` (str): CSS class(es) to be apply to the image.
- `height` (int | str): The height of the image in pixels or as a percentage.
- `width` (int | str): The width of the image in pixels or as a percentage.
```ini
[login screenshot after successful install]
screenshot: images/login.png
@ -85,14 +90,14 @@ height: 50%
width: 50%
```
The value of `screenshot` may be relative to the command file or a full URL to the image. If `caption` is omitted the section (comment) is used.
The value of `screenshot` may be relative to the command file or a full URL to the image. If `caption` is omitted the comment is used.
### twist
Send a message via [Twist](https://twist.com).
- `title`: The title of the message. Default: `Notice`
- `url`: Required. The URL to which the message should be sent.
- `title` (str): The title of the message. Default: `Notice`
- `url` (str): Required. The URL to which the message should be sent.
```ini
[send some feedback]

@ -30,24 +30,39 @@ Drop a database. Argument is the database name.
Dump the database schema. Argument is the database name.
- `path`: The path to the dump file. Default: `dump.sql`
- `path`: The path to the dump file. Default: `database_name.sql`
### mysql.exec
Execute an SQL statement. Argument is the SQL statement.
```ini
[create a soft backup of the database]
mysql.dump: example_app
path: /tmp/example_app.sql
- `database`: The name of the database where the statement will be executed. Default: `default`
```
### mysql.exists
Determine if a database exists. Argument is the database name.
```ini
[determine if the database exists]
mysql.exists: example_app
```
### mysql.grant
Grant privileges to a user. Argument is the privileges to be granted.
Grant privileges to a user.
- `database`: The database name where privileges are granted.
- `user`: The user name for which the privileges are provided.
- `privileges`: The privileges to be granted. Default: `ALL`
```ini
[grant select privileges to bob]
mysql.grant: bob
privileges: select
```
### mysql.user

@ -0,0 +1,119 @@
# PostgreSQL
Summary: Work with Postgres databases.
## Common Options
- `host` (str): The host name. Default: `localhost`
- `password` (str): The password of the user executing the command.
- `port` (int): The TCP port. Default: `5432`
- `user` (str): The username of the user executing the command. Default: `postgres`
## Automatic Conversion of Postgres Command Switches
Options provided in the steps file are automatically converted to command line switches. For example:
```ini
[create a soft backup of the database schema]
pgsql.dump: example_app
schema_only: yes
path: /tmp/example_app.sql
```
`schema_only` becomes "--schema-only".
## Available Commands
### pgsql.create
Create a database. Argument is the database name.
- `owner` (str): The username that owns the database.
```ini
[create the database]
pgsql.create: database_name
```
### pgsql.drop
Drop a database. Argument is the database name.
```ini
[drop the testing database]
pgsql.drop: testing_example_app
```
### pgsql.dump
Dump the database schema. Argument is the database name.
- `path` (str): The path to the dump file. Default: `database_name.sql`
```ini
[create a soft backup of the database]
pgsql.dump: example_app
column_inserts: yes
path: /tmp/example_app.sql
```
### pgsql.exists
Determine if a database exists. Argument is the database name.
```ini
[determine if the database exists]
pgsql.exists: example_app
```
### pgsql.grant
Grant privileges to a user. Argument is the username. Database option is required.
- `database` (str): The name of the database where the target object exists.
- `privileges` (str): The privileges to be granted. Default `ALL` (see [Postgres docs](https://www.postgresql.org/docs/current/sql-grant.html))
- `schema` (str): The schema name to which the privileges apply.
- `table` (str): The table name to which privileges apply.
!!! note
A schema name or table name is required.
```ini
[grant select access to bob]
pgsql.grant: bob
database: example_app
privileges: select
schema: public
```
### pgsql.user
Create a user. Argument is the user name.
- `password` (str): The user's password.
```ini
[create a database user]
pgsql.user: username
```
Remove a user.
```ini
[remove a database user]
pgsql.user: username
op: remove
```
Determine if a user exists.
```ini
[determine if database user exists]
pgsql.user: username
op: exists
```

@ -7,3 +7,9 @@ Summary: Work with PHP.
### module
Enable a PHP module. Argument is the module name.
```ini
[enable postgres for PHP]
php.module: pdo_pgsql
```

@ -0,0 +1,264 @@
# POSIX
Summary: Work with common POSIX-compliant commands..
## Available Commands
### append
Append content to a file. Argument is the file name.
- `content` (str): The content to be appended.
```ini
[add to the log file]
append: /path/to/file.log
content: This is a test.
```
### archive
Create an archive (tarball). Argument is the target file or directory.
- `absolute` (bool): Don't strip leading slashes from file names. Default `False`
- `exclude` (str): Exclude file name patterns.
- `file_name` (str): The name of the archive file. Default `archive.tgz`
- `strip` (int): Strip component paths to the given depth.
- `to_path` (str): The path to where the archive will be created. Default `.`
- `view` (bool): View the progress. Default `False`
```ini
[create an archive of the site]
archive: /path/to/file_or_directory
file_name: testing.tgz
to: /tmp
```
### certbot
Alias: ssl
Use Let's Encrypt (certbot) to acquire an SSL certificate. Argument is the domain name.
- `email`: The email address for "agree tos". Default: `webmaster@domain_name`
- `webroot`: The webroot to use. Default: `/var/www/maint/www`
```ini
[get an SSL cert]
ssl: example.app
email: webmaster@example.app
```
### copy
Copy a file or directory. First argument is the target file/directory. Second argument is the destination.
- `overwrite` (bool): Overwrite an existing target.
- `recursive` (bool): Copy directories recursively.
```ini
[copy a directory]
copy: /path/to/directory /path/to/new_directory
overwrite: yes
recursive: yes
```
### dir
Create a directory. Argument is the path.
- `group` (str): Set the group to the given group name.
- `mode` (str): Set the mode on the path.
- `owner` (str): Set the owner to the given owner name.
- `recursive` (str): Create the full path even if intermediate directories do not exist.
```ini
[create a directory]
dir: /path/to/directory
group: www-data
mode: 755
owner: deploy
recursive: yes
```
### extract
Extract an archive (tarball). Argument is the path to the archive file.
- `absolute` (bool): Don't strip leading slashes from file names. Default `False`
- `exclude` (str): Exclude file name patterns.
- `strip` (int): Strip component paths to the given depth.
- `to_path` (str): The path to where the archive will be created. Default `./`
- `view` (bool): View the progress. Default `False`
```ini
[extract an archive]
extract: /path/to/archive.tgz
```
### link
Create a symlink. First argument is the source.
- `force` (bool): Force creation of the link.
- `target` (str): The location of the link. Defaults to the current directory and the base name of the source.
```ini
[create a symlink]
link: /path/to/project/releases/1.0
cd: /path/to/project
force: yes
target: current
```
### move
Move a file or directory. First argument is the target. Second argument is the desitnation.
```ini
[move a file]
move: /path/to/file.txt /new/path/to/file.txt
```
### perms
Set permissions on a file or directory. Argument is the path.
- `group` (str): Set the group to the given group name.
- `mode` (str): Set the mode on the path.
- `owner` (str): Set the owner to the given owner name.
- `recursive` (bool): Apply permission recursively (directories only).
```ini
[set permissions on the shared directory]
perms: /path/to/project/shared
group: www-data
mode: 775
owner: deploy
recursive: yes
```
### push
Alias: rsync
Push (rsync) a path to a remote server. First argument is the local path. Second argument is the remote path.
- `delete` (bool): Delete existing files/directories. Default `False`
- `host` (str): The host name. Required.
- `key_file` (str): Use the given SSL (private) key.
- `links` (bool): Copy symlinks. Default `True
- `exclude` (str): Exclude patterns from the given (local) file.
- `port` (int): The TCP port on the host. Default: `22`
- `recursive` (bool): Operate recursively on directories.
- `user` (str): The username.
```ini
[push the project to the server]
push: /path/to/project /path/on/server
key_file: ~/.ssh/example_app
host: example.app
user: deploy
```
### remove
Remove a file or directory. Argument is the path.
- `force` (bool): Force the removal. Default `False`
- `recursive` (bool): Remove all directories in the path. Default `False`
```ini
[remove a directory]
remove: /path/to/directory
force: yes
recusrive: yes
```
### replace
Replace something in a file. First argument is the path.
- `backup`: Backup file extension. Default `.b`
- `delimiiter`: The sed delimiter. Default: `/`
- `find`: The text to be found. Required.
- `sub`: The text to be replaced. Required.
```ini
[replace text in a file]
replace: /path/to/file.txt
find: testing
sub: 123
```
### scopy
Copy a file to a remote server. First argument is the local file name. Second argument is the remote destination.
- `key_file` (str): The private key file to use for the connection.
- `host` (str): The host name. Required.
- `port` (int): The TCP port. Default: `22`
- `user` (str): The username. Required.
```ini
[copy a file to the server]
scopy: /path/to/local.txt path/to/remove.txt
host: example.app
user: deploy
```
### sync
Sync (rsync) local files and directories. First argument is the target. Second argument is the destination.
- `delete` (bool): Delete existing files/directories.
- `links` (bool): Copy symlinks.
- `exclude` (str): Exclude patterns from the given (local) file.
- `recursive` (bool): Operate recursively on directories.
```ini
[syncrhonize files on the local machine]
sync: /path/to/project /path/to/sync/directory
```
### touch
Touch a file, whether it exists or not. Argument is the path.
```ini
[touch a file]
touch: /path/to/file.txt
```
### wait
Wait for n number of seconds before continuing. Argument is the number of seconds.
```ini
[wait just a minute]
wait: 60
```
### write
Write to a file. Argument is the path.
- `content` (str): The content to write to the file. Replaces existing content.
```ini
[replace an existing file]
write: /path/to/file.txt
content: This whole file has been replaced.
```

@ -0,0 +1,49 @@
# Python
Summary: Work with Python.
## Available Commands
### pip
Use the pip command. Argument is the package name.
- `op` (str): The operation; `install` (the default) or `remove`.
- `upgrade` (bool): Upgrade the package.
- `venv` (str): The name of the virtual environment to use.
- `version` (int): The pip version to use. Default `3`
```ini
[install django]
pip: django
cd: /path/to/project
venv: python
```
### pip_file
Alias: pipf
Install packages from a pip file.
- `venv` (str): The name of the virtual environment to use.
- `version` (int): The pip version to use. Default `3`
```ini
[install dependencies]
pip_file: deploy/packages/testing.pip
cd: path/to/project
venv: python
```
### virtualenv
Create a python virtual environment. Argument is the environment name.
```ini
[create the virtual environment]
virtualenv: python
cd: /path/to/project
```

@ -54,8 +54,6 @@ Run any shell command.
run: "ls -ls"
```
Note that commands with arguments will need to be in quotes.
### start
Start a service:
@ -82,6 +80,18 @@ Work with the system.
- `update`
- `upgrade`
```ini
[update package info]
update:
[upgrade the system]
upgrade:
[reboot the system]
reboot:
```
### uninstall
Uninstall a package.
@ -104,10 +114,10 @@ upgrade: libxyz-dev
Create a user:
- `groups`: A comma separated list of groups to which the user should be added.
- `home`: The user's home directory.
- `login`: The shell to use.
- `system`: Create as a system user.
- `groups` (str): A comma separated list of groups to which the user should be added.
- `home` (str): The user's home directory.
- `login` (str): The shell to assign.
- `system` (bool): Create as a system user.
```ini
[create the deploy user]

@ -11,7 +11,7 @@ nav:
- Introduction: index.md
- Getting Started: getting-started.md
- Topics:
- Command File: topics/command-file.md
- Steps File: topics/steps-file.md
- Variables File: topics/variables.md
- Itemized Commands: topics/itemized-commands.md
- Templates: topics/templates.md
@ -21,17 +21,16 @@ nav:
- Post a Message to Slack: how-to/post-message-slack.md
- Post a Message to Twist: how-to/post-message-twist.md
- Use Script Tease with Common Kit: how-to/use-with-commonkit.md
- Commands:
- Django: commands/django.md
- Messages: commands/messages.md
- MySQL: commands/mysql.md
- PHP: commands/php.md
- Postgres: commands/pgsql.md
- POSIX: commands/posix.md
- Python: commands/python.md
- Profiles:
- CentOS: profiles/centos.md
- Ubuntu: profiles/ubuntu.md
- Usage:
- CentOS: usage/centos.md
- Django: usage/django.md
- Messages: usage/messages.md
- MySQL: usage/mysql.md
- PHP: usage/php.md
- Postgres: usage/pgsql.md
- POSIX: usage/posix.md
- Python: usage/python.md
- Ubuntu: usage/ubuntu.md
- CLI: cli.md
repo_name: Git Traction
repo_url: https://gittraction.com/diff6/python-scripttease

@ -184,8 +184,8 @@ class SubCommands(object):
sub.add_argument(
"-i=",
"--input-file=",
default="commands.ini",
dest="command_file",
default="steps.ini",
dest="steps_file",
help="The path to the configuration file."
)

@ -24,8 +24,47 @@ __all__ = (
class Command(object):
"""A command collects parameters and establishes the standard interface for creating a command line statement."""
def __init__(self, statement, cd=None, comment=None, condition=None, name=None, prefix=None, register=None, shell=None, stop=False, sudo=None, tags=None, **kwargs):
"""Initialize a command.
:param statement: The command statement.
:type statement: str
:param cd: Change directory to this path.
:type cd: str
:param comment: A comment on the command.
:type comment: str
:param condition: A condition to be met before the command should execute. This must be in the form of a Bash
conditional. For example, ``-f path/to/file.txt``
:type condition: str
:param name: The canonical name of the command. Provided automatically when using the command factory.
:type name: str
:param prefix: Any valid statement that should come before the command statement.
:type prefix: str
:param register: Register the result of the command to this variable name.
:type register: str
:param shell: The shell to use. Not used by Script Tease, but may be important to integrators.
:type shell: str
:param stop: Don't continue with additional commands if this one fails.
:type stop: bool
:param sudo: A username or boolean that indicates sudo should be used.
:type sudo: bool | str
:param tags: A list of tags that help classify the command.
:type tags: list[str]
kwargs are added to the Command instance as ``options`` and are dynamically accessible.
"""
self.cd = cd
self.comment = comment
self.condition = condition
@ -124,6 +163,7 @@ class Command(object):
class Content(object):
"""A special command-like resource for capturing non-statement content such as explanations and screenshots."""
def __init__(self, content_type, caption=None, css=None, heading=None, height=None, image=None, message=None,
width=None, **kwargs):
@ -564,12 +604,34 @@ class Sudo(object):
class Template(object):
"""A command-like resource that may be used to process template files."""
PARSER_JINJA = "jinja2"
PARSER_PYTHON = "python"
PARSER_SIMPLE = "simple"
def __init__(self, source, target, backup=True, parser=PARSER_JINJA, **kwargs):
"""Initialize a template.
:param source: The path to the template file.
:type source: str
:param target: The path to where the rendered template should be saved.
:type target: str
:param backup: Indicates whether a backup of an existing target should be created.
:type backup: bool
:param parser: The parser to use; ``jinja``, ``python``, or ``simple``.
:type parser: str
kwargs are the same as that of :py:class:`scripttease.lib.commands.base.Command`
Python templates are parsed using string interpolation. For example, ``%(variable_name)s``.
Simple templates look for variables in the form of ``$variable_name$``.
"""
self.backup_enabled = backup
self.cd = kwargs.pop("cd", None)
self.comment = kwargs.pop("comment", "create template %s" % target)
@ -686,6 +748,11 @@ class Template(object):
return "\n".join(lines)
def get_target_language(self):
"""Get the target language of the template output. Used when generating documentation.
:rtype: str
"""
if self.language is not None:
return self.language

@ -39,9 +39,11 @@ __all__ = (
def apache(op, **kwargs):
"""Execute an Apache-related command.
- op (str): The operation to perform; reload, restart, start, stop, test.
:param op: The operation to perform; ``reload``, ``restart``, ``start``, ``stop``, ``test``.
:type op: str
"""
# See https://unix.stackexchange.com/questions/258854/disable-and-enable-modules-in-apache-centos7
if op == "reload":
return apache_reload(**kwargs)
elif op == "restart":
@ -57,6 +59,7 @@ def apache(op, **kwargs):
def apache_reload(**kwargs):
"""Reload the apache service."""
kwargs.setdefault("comment", "reload apache")
kwargs.setdefault("register", "apache_reloaded")
@ -64,6 +67,7 @@ def apache_reload(**kwargs):
def apache_restart(**kwargs):
"""Restart the apache service."""
kwargs.setdefault("comment", "restart apache")
kwargs.setdefault("register", "apache_restarted")
@ -71,6 +75,7 @@ def apache_restart(**kwargs):
def apache_start(**kwargs):
"""Start the apache service."""
kwargs.setdefault("comment", "start apache")
kwargs.setdefault("register", "apache_started")
@ -78,12 +83,14 @@ def apache_start(**kwargs):
def apache_stop(**kwargs):
"""Stop the apache service."""
kwargs.setdefault("comment", "stop apache")
return Command("apachectl –k stop", **kwargs)
def apache_test(**kwargs):
"""Run a configuration test on apache."""
kwargs.setdefault("comment", "check apache configuration")
kwargs.setdefault("register", "apache_checks_out")
@ -93,7 +100,8 @@ def apache_test(**kwargs):
def service_reload(service, **kwargs):
"""Reload a service.
- name (str): The service name.
:param service: The service name.
:type service: str
"""
kwargs.setdefault("comment", "reload %s service" % service)
@ -105,7 +113,8 @@ def service_reload(service, **kwargs):
def service_restart(service, **kwargs):
"""Restart a service.
- name (str): The service name.
:param service: The service name.
:type service: str
"""
kwargs.setdefault("comment", "restart %s service" % service)
@ -117,7 +126,8 @@ def service_restart(service, **kwargs):
def service_start(service, **kwargs):
"""Start a service.
- name (str): The service name.
:param service: The service name.
:type service: str
"""
kwargs.setdefault("comment", "start %s service" % service)
@ -129,7 +139,8 @@ def service_start(service, **kwargs):
def service_stop(service, **kwargs):
"""Stop a service.
- name (str): The service name.
:param service: The service name.
:type service: str
"""
kwargs.setdefault("comment", "stop %s service" % service)
@ -141,7 +152,8 @@ def service_stop(service, **kwargs):
def system(op, **kwargs):
"""Perform a system operation.
- op (str): The operation to perform; reboot, update, upgrade.
:param op: The operation to perform; ``reboot``, ``update``, ``upgrade``.
:type op: str
"""
if op == "reboot":
@ -157,7 +169,8 @@ def system(op, **kwargs):
def system_install(package, **kwargs):
"""Install a system-level package.
- name (str): The name of the package to install.
:param package: The name of the package to install.
:type package: str
"""
kwargs.setdefault("comment", "install system package %s" % package)
@ -166,6 +179,7 @@ def system_install(package, **kwargs):
def system_reboot(**kwargs):
"""Reboot the system."""
kwargs.setdefault("comment", "reboot the system")
return Command("reboot", **kwargs)
@ -174,7 +188,8 @@ def system_reboot(**kwargs):
def system_uninstall(package, **kwargs):
"""Uninstall a system-level package.
- name (str): The name of the package to uninstall.
:param package: The name of the package to remove.
:type package: str
"""
kwargs.setdefault("comment", "remove system package %s" % package)
@ -183,12 +198,14 @@ def system_uninstall(package, **kwargs):
def system_update(**kwargs):
"""Update the system's package info."""
kwargs.setdefault("comment", "update system package info")
return Command("yum check-update", **kwargs)
def system_upgrade(**kwargs):
"""updated the system."""
kwargs.setdefault("comment", "upgrade the system")
return Command("yum update -y", **kwargs)
@ -197,11 +214,20 @@ def system_upgrade(**kwargs):
def user(name, groups=None, home=None, op="add", password=None, **kwargs):
"""Create or remove a user.
- name (str): The user name.
- groups (str | list): A list of groups to which the user should belong.
- home (str): The path to the user's home directory.
- op (str); The operation to perform; ``add`` or ``remove``.
- password (str): The user's password. (NOT IMPLEMENTED)
:param name: The username.
:type name: str
:param groups: A list of groups to which the user should belong.
:type groups: list | str
:param home: The path to the user's home directory.
:type home: str
:param op: The operation to perform; ``add`` or ``remove``.
:type op:
:param password: The user's password. (NOT IMPLEMENTED)
:type password: str
"""
if op == "add":

@ -1,23 +1,3 @@
"""
[run django checks]
django: check
[export fixtures]
django: dump lookups.Category
[import fixtures]
django: load lookups.Category
[migrate the database]
django: migrate
[collect static files]
django: static
[create super user (ad hoc command)]
django: createsuperuser root
"""
from ...constants import EXCLUDED_KWARGS
from .base import Command

@ -3,6 +3,21 @@ from ...exceptions import InvalidInput
def dialog(message, height=15, title="Message", width=100, **kwargs):
"""Display a graphical feedback box to the user. Note that the ``dialog`` command must be available.
:param message: The message to be displayed.
:type message: str
:param height: The height of the dialog box.
:type height: int
:param title: The title displayed.
:type title: str
:param width: The width of the dialog box.
:type width: int
"""
statement = list()
statement.append("dialog --clear")
statement.append('--backtitle "%s"' % title)
@ -13,18 +28,60 @@ def dialog(message, height=15, title="Message", width=100, **kwargs):
def echo(message, **kwargs):
"""Display a message.
:param message: The message to be displayed.
:type message: str
"""
return Command('echo "%s"' % message, **kwargs)
def explain(message, heading=None, **kwargs):
"""Create an explanation for documentation.
:param message: The message to be displayed.
:type message: str
:param heading: Optional heading for the output.
:type heading: str
"""
return Content("explain", message=message, heading=heading, **kwargs)
def screenshot(image, caption=None, css=None, height=None, width=None, **kwargs):
"""Create a screenshot for documentation.
:param image: The URL or path to the image file.
:type image: str
:param caption: A caption for the image.
:type caption: str
:param css: CSS classes to be applied to the image tag.
:type css: str
:param height: The maximum height of the image.
:type height: int
:param width: The maximum widht of the image.
:type width: int
"""
return Content("screenshot", caption=caption, css=css, height=height, image=image, width=width, **kwargs)
def slack(message, url=None, **kwargs):
"""Send a message to Slack.
:param message: The message to be displayed.
:type message: str
:param url: The channel URL.
:type url: str
"""
if url is None:
raise InvalidInput("Slack command requires a url parameter.")
@ -37,6 +94,18 @@ def slack(message, url=None, **kwargs):
def twist(message, title="Notice", url=None, **kwargs):
"""Send a message to Twist.
:param message: The message to be displayed.
:type message: str
:param title: A title for the message.
:type title: str
:param url: The channel URL.
:type url: str
"""
if url is None:
raise InvalidInput("Twist command requires a url parameter.")

@ -18,7 +18,27 @@ __all__ = (
)
def mysql(command, *args, host="localhost", excluded_kwargs=None, password=None, port=3306, user="root", **kwargs):
def mysql(command, *args, excluded_kwargs=None, host="localhost", password=None, port=3306, user="root", **kwargs):
"""Get a mysql-related command using commonly required parameters.
:param command: The name of the command.
:type command: str
:param excluded_kwargs: Keyword arguments to exclude from automatic switch creation.
:type excluded_kwargs: list[str]
:param host: The host name.
:type host: str
:param password: The password to use.
:type password: str
:param port: The TCP port number.
:type port: int
:param user: The username that will be used to execute the command.
"""
# The excluded parameters (filtered below) may vary based on implementation. We do, however, need a default.
excluded_kwargs = excluded_kwargs or EXCLUDED_KWARGS
@ -63,6 +83,15 @@ def mysql(command, *args, host="localhost", excluded_kwargs=None, password=None,
def mysql_create(database, owner=None, **kwargs):
"""Create a MySQL database.
:param database: The database name.
:type database: str
:param owner: The owner (user/role name) of the new database.
:type owner: str
"""
kwargs.setdefault("comment", "create mysql database")
command = mysql("mysqladmin create", database, **kwargs)
@ -75,12 +104,27 @@ def mysql_create(database, owner=None, **kwargs):
def mysql_drop(database, **kwargs):
"""Remove a MySQL database.
:param database: The database name.
:type database: str
"""
kwargs.setdefault("comment", "drop %s mysql database" % database)
return mysql("mysqladmin drop", database, **kwargs)
def mysql_dump(database, path=None, **kwargs):
"""Export a MySQL database.
:param database: The database name.
:type database: str
:param path: The name/path of the export file. Defaults the database name plus ``.sql``.
:type path: str
"""
kwargs.setdefault("comment", "dump mysql database")
if path is None:
@ -90,6 +134,12 @@ def mysql_dump(database, path=None, **kwargs):
def mysql_exists(database, **kwargs):
"""Determine if a MySQL database exists.
:param database: The database name.
:type database: str
"""
kwargs.setdefault("comment", "determine if %s mysql database exists" % database)
kwargs.setdefault("register", "%s_exists" % database)
@ -105,13 +155,14 @@ def mysql_exists(database, **kwargs):
def mysql_grant(to, database=None, privileges="ALL", **kwargs):
"""Grant privileges to a user.
- to (str): The user name to which privileges are granted.
- database (str): The database name.
- host (str): The database host name or IP address.
- password (str): The password for the user with sufficient access privileges to execute the command.
- port (int): The TCP port number of the MySQL service running on the host.
- privileges (str): The privileges to be granted.
- user (str): The name of the user with sufficient access privileges to execute the command.
:param to: The username to which privileges are granted.
:type to: str
:param database: The database name.
:type database: str
:param privileges: The privileges to be granted.
:type privileges: str
"""
kwargs.setdefault("comment", "grant mysql privileges to %s" % to)
@ -134,12 +185,39 @@ def mysql_grant(to, database=None, privileges="ALL", **kwargs):
def mysql_load(database, path, **kwargs):
"""Load data into a MySQL database.
:param database: The database name.
:type database: str
:param path: The path to the file to be loaded.
:type path: str
"""
kwargs.setdefault("comment", "load data into a mysql database")
return mysql("mysql", database, "< %s" % path, **kwargs)
def mysql_user(name, admin_pass=None, admin_user="root", op="create", password=None, **kwargs):
"""Work with a MySQL user.
:param name: The username.
:type name: str
:param admin_pass: The password for the user with admin privileges.
:type admin_pass: str
:param admin_user: The username of the user with admin privileges.
:type admin_user: str
:param op: The operation to perform: ``create``, ``drop``, ``exists``.
:type op: str
:param password: The password for a new user.
:type password: str
"""
host = kwargs.get("host", "localhost")
if op == "create":

@ -12,12 +12,33 @@ __all__ = (
"pgsql_drop",
"pgsql_dump",
"pgsql_exists",
"pgsql_grant",
"pgsql_load",
"pgsql_user",
)
def pgsql(command, *args, host="localhost", excluded_kwargs=None, password=None, port=5432, user="postgres", **kwargs):
def pgsql(command, *args, excluded_kwargs=None, host="localhost", password=None, port=5432, user="postgres", **kwargs):
"""Get a postgres-related command using commonly required parameters.
:param command: The name of the command.
:type command: str
:param excluded_kwargs: Keyword arguments to exclude from automatic switch creation.
:type excluded_kwargs: list[str]
:param host: The host name.
:type host: str
:param password: The password to use.
:type password: str
:param port: The TCP port number.
:type port: int
:param user: The username that will be used to execute the command.
"""
# The excluded parameters (filtered below) may vary based on implementation. We do, however, need a default.
excluded_kwargs = excluded_kwargs or EXCLUDED_KWARGS
@ -59,6 +80,18 @@ def pgsql(command, *args, host="localhost", excluded_kwargs=None, password=None,
def pgsql_create(database, owner=None, template=None, **kwargs):
"""Create a PostgreSQL database.
:param database: The database name.
:type database: str
:param owner: The owner (user/role name) of the new database.
:type owner: str
:param template: The database template name to use, if any.
:type template: str
"""
kwargs.setdefault("comment", "create %s postgres database" % database)
if owner is not None:
@ -87,12 +120,27 @@ def pgsql_create(database, owner=None, template=None, **kwargs):
def pgsql_drop(database, **kwargs):
"""Remove a PostgreSQL database.
:param database: The database name.
:type database: str
"""
kwargs.setdefault("comment", "drop %s postgres database" % database)
return pgsql("dropdb", database, **kwargs)
def pgsql_dump(database, path=None, **kwargs):
"""Export a PostgreSQL database.
:param database: The database name.
:type database: str
:param path: The name/path of the export file. Defaults the database name plus ``.sql``.
:type path: str
"""
kwargs.setdefault("comment", "dump postgres database")
kwargs.setdefault("column_inserts", True)
@ -106,6 +154,12 @@ def pgsql_dump(database, path=None, **kwargs):
def pgsql_exists(database, **kwargs):
"""Determine if a PostgreSQL database exists.
:param database: The database name.
:type database: str
"""
kwargs.setdefault("comment", "determine if %s postgres database exists" % database)
kwargs.setdefault("register", "%s_exists" % database)
@ -115,7 +169,68 @@ def pgsql_exists(database, **kwargs):
return command
def pgsql_grant(to, database=None, privileges="ALL", schema=None, table=None, **kwargs):
"""Grant privileges to a user.
:param to: The username to which privileges are granted.
:type to: str
:param database: The database name. Required.
:type database: str
:param privileges: The privileges to be granted. See https://www.postgresql.org/docs/current/sql-grant.html
:type privileges: str
:param schema: The schema to which the privileges apply.
:type schema: str
:param table: The table name to which privileges apply.
:type table: str
.. note::
A schema or table is required and the privileges must be compatible with the target object.
"""
if database is None:
raise InvalidInput("Database is required.")
kwargs.setdefault("comment", "grant postgres privileges to %s" % to)
kwargs['dbname'] = database
if schema is not None:
target = "SCHEMA %s" % schema
elif table is not None:
target = "TABLE %s" % table
else:
raise InvalidInput("Either schema or table is required.")
_privileges = privileges
if privileges.lower() == "all":
_privileges = "ALL PRIVILEGES"
# See https://www.postgresql.org/docs/current/sql-grant.html
sql = "GRANT %(privileges)s ON %(target)s TO %(user)s" % {
'privileges': _privileges,
'target': target,
'user': to,
}
command = pgsql("psql", **kwargs)
command.statement += ' -c "%s"' % sql
return command
def pgsql_load(database, path, **kwargs):
"""Load data into a PostgreSQL database.
:param database: The database name.
:type database: str
:param path: The path to the file to be loaded.
:type path: str
"""
kwargs.setdefault("comment", "load data into a postgres database")
kwargs['dbname'] = database
@ -125,6 +240,24 @@ def pgsql_load(database, path, **kwargs):
def pgsql_user(name, admin_pass=None, admin_user="postgres", op="create", password=None, **kwargs):
"""Work with a PostgreSQL user.
:param name: The username.
:type name: str
:param admin_pass: The password for the user with admin privileges.
:type admin_pass: str
:param admin_user: The username of the user with admin privileges.
:type admin_user: str
:param op: The operation to perform: ``create``, ``drop``, ``exists``.
:type op: str
:param password: The password for a new user.
:type password: str
"""
if op == "create":
kwargs.setdefault("comment", "create %s postgres user" % name)
@ -160,6 +293,7 @@ PGSQL_MAPPINGS = {
'pgsql.drop': pgsql_drop,
'pgsql.dump': pgsql_dump,
'pgsql.exists': pgsql_exists,
'pgsql.grant': pgsql_grant,
# 'pgsql.sql': pgsql_exec,
'pgsql.user': pgsql_user,
}

@ -2,6 +2,12 @@ from .base import Command
def php_module(name, **kwargs):
"""Enable a PHP module.
:param name: The module name.
:type name: str
"""
statement = "phpenmod %s" % name
return Command(statement, **kwargs)

@ -5,8 +5,11 @@ from .base import Command, MultipleCommands, Prompt
def append(path, content=None, **kwargs):
"""Append content to a file.
- path (str): The path to the file.
- content (str): The content to be appended.
:param path: The path to the file.
:type path: str
:param content: The content to be appended.
:type content: str
"""
kwargs.setdefault("comment", "append to %s" % path)
@ -20,12 +23,26 @@ def archive(from_path, absolute=False, exclude=None, file_name="archive.tgz", st
**kwargs):
"""Create a file archive.
- from_path (str): The path that should be archived.
- absolute (bool): Set to ``True`` to preserve the leading slash.
- exclude (str): A pattern to be excluded from the archive.
- strip (int): Remove the specified number of leading elements from the path.
- to_path (str): Where the archive should be created. This should *not* include the file name.
- view (bool): View the output of the command as it happens.
:param from_path: The path that should be archived.
:type from_path: str
:param absolute: Set to ``True`` to preserve the leading slash.
:type absolute: bool
:param exclude: A pattern to be excluded from the archive.
:type exclude: str
:param file_name: The name of the archive file.
:type file_name: str
:param strip: Remove the specified number of leading elements from the path.
:type strip: int
:param to_path: Where the archive should be created. This should *not* include the file name.
:type to_path: str
:param view: View the output of the command as it happens.
:type view: bool
"""
tokens = ["tar"]
@ -56,9 +73,14 @@ def archive(from_path, absolute=False, exclude=None, file_name="archive.tgz", st
def certbot(domain_name, email=None, webroot=None, **kwargs):
"""Get new SSL certificate from Let's Encrypt.
- domain_name (str): The domain name for which the SSL certificate is requested.
- email (str): The email address of the requester sent to the certificate authority. Required.
- webroot (str): The directory where the challenge file will be created.
:param domain_name: The domain name for which the SSL certificate is requested.
:type domain_name: str
:param email: The email address of the requester sent to the certificate authority. Required.
:type email: str
:param webroot: The directory where the challenge file will be created.
:type webroot: str
"""
_email = email or os.environ.get("SCRIPTTEASE_CERTBOT_EMAIL", None)
@ -80,10 +102,17 @@ def certbot(domain_name, email=None, webroot=None, **kwargs):
def copy(from_path, to_path, overwrite=False, recursive=False, **kwargs):
"""Copy a file or directory.
- from_path (str): The file or directory to be copied.
- to_path (str): The location to which the file or directory should be copied.
- overwrite (bool): Indicates files and directories should be overwritten if they exist.
- recursive (bool): Copy sub-directories.
:param from_path: The file or directory to be copied.
:type from_path: str
:param to_path: The location to which the file or directory should be copied.
:type to_path: str
:param overwrite: Indicates files and directories should be overwritten if they exist.
:type overwrite: bool
:param recursive: Copy sub-directories.
:type recursive: bool
"""
kwargs.setdefault("comment", "copy %s to %s" % (from_path, to_path))
@ -106,9 +135,14 @@ def copy(from_path, to_path, overwrite=False, recursive=False, **kwargs):
def directory(path, group=None, mode=None, owner=None, recursive=False, **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.
:param path: The path to be created.
:type path: str
:param mode: The access permissions of the new directory.
:type mode: int | str
:param recursive: Create all directories along the path.
:type recursive: bool
"""
comment = kwargs.pop("comment", "create directory %s" % path)
@ -155,12 +189,23 @@ def directory(path, group=None, mode=None, owner=None, recursive=False, **kwargs
def extract(from_path, absolute=False, exclude=None, strip=None, to_path=None, view=False, **kwargs):
"""Extract a file archive.
- from_path (str): The path that should be archived.
- absolute (bool): Set to ``True`` to preserve the leading slash.
- exclude (str): A pattern to be excluded from the archive.
- strip (int): Remove the specified number of leading elements from the path.
- to_path (str): Where the archive should be extracted. This should *not* include the file name.
- view (bool): View the output of the command as it happens.
:param from_path: The path to be extracted.
:type from_path: str
:param absolute: Set to ``True`` to preserve the leading slash.
:type absolute: bool
:param exclude: A pattern to be excluded from the extraction.
:type exclude: str
:param strip: Remove the specified number of leading elements from the path.
:type strip: int
:param to_path: Where the extraction should occur.
:type to_path: str
:param view: View the output of the command as it happens.
:type view: bool
"""
_to_path = to_path or "./"
@ -192,9 +237,14 @@ def extract(from_path, absolute=False, exclude=None, strip=None, to_path=None, v
def link(source, force=False, target=None, **kwargs):
"""Create a symlink.
- source (str): The source of the link.
- force (bool): Force the creation of the link.
- target (str): The name or path of the target. Defaults to the base name of the source path.
:param source: The source of the link.
:type source: str
:param force: Force the creation of the link.
:type force: bool
:param target: The name or path of the target. Defaults to the base name of the source path.
:type target: str
"""
_target = target or os.path.basename(source)
@ -215,8 +265,11 @@ def link(source, force=False, target=None, **kwargs):
def move(from_path, to_path, **kwargs):
"""Move a file or directory.
- from_path (str): The current path.
- to_path (str): The new path.
:param from_path: The current path.
:type from_path: str
:param to_path: The new path.
:type to_path: str
"""
kwargs.setdefault("comment", "move %s to %s" % (from_path, to_path))
@ -228,11 +281,20 @@ def move(from_path, to_path, **kwargs):
def perms(path, group=None, mode=None, owner=None, recursive=False, **kwargs):
"""Set permissions on a file or directory.
- path (str): The path to be changed.
- group (str): The name of the group to be applied.
- mode (int | str): The access permissions of the file or directory.
- owner (str): The name of the user to be applied.
- recursive: Create all directories along the path.
:param path: The path to be changed.
:type path: str
:param group: The name of the group to be applied.
:type group: str
:param mode: The access permissions of the file or directory.
:type mode: int | str
:param owner: The name of the user to be applied.
:type owner: str
:param recursive: Update all files and directories along the path.
:type recursive: bool
"""
comment = kwargs.pop("comment", "set permissions on %s" % path)
@ -292,13 +354,26 @@ def perms(path, group=None, mode=None, owner=None, recursive=False, **kwargs):
def prompt(name, back_title="Input", choices=None, default=None, dialog=False, help_text=None, label=None, **kwargs):
"""Prompt the user for input.
- name (str): The programmatic name of the input.
- back_title (str): The back title used with the dialog command.
- choices (str | list): A list of valid choices.
- default: The default value.
- dialog (bool): Use a dialog command for the prompt.
- help_text (str): The text to display with the dialog command.
- label (str): The label for the input.
:param name: The programmatic name of the input.
:type name: str
:param back_title: The back title used with the dialog command.
:type back_title: str
:param choices: A list of valid choices.
:type choices: list | str
:param default: The default value.
:type default: str
:param dialog: Use a dialog command for the prompt.
:type dialog: bool
:param help_text: The text to display with the dialog command.
:type help_text: str
:param label: The label for the input.
:type label: str
"""
return Prompt(
@ -316,9 +391,14 @@ def prompt(name, back_title="Input", choices=None, default=None, dialog=False, h
def remove(path, force=False, recursive=False, **kwargs):
"""Remove a file or directory.
- path (str): The path to be removed.
- force (bool): Force the removal.
- recursive (bool): Remove all directories along the path.
:param path: The path to be removed.
:type path: str
:param force: Force the removal.
:type force: bool
:param recursive: Remove all directories along the path.
:type recursive: bool
"""
kwargs.setdefault("comment", "remove %s" % path)
@ -336,14 +416,23 @@ def remove(path, force=False, recursive=False, **kwargs):
return Command(" ".join(statement), **kwargs)
def replace(path, backup=".b", delimiter="/", find=None, replace=None, **kwargs):
def replace(path, backup=".b", delimiter="/", find=None, sub=None, **kwargs):
"""Find and replace text in a file.
- path (str): The path to the file to be edited.
- backup (str): The backup file extension to use.
- delimiter (str): The pattern delimiter.
- find (str): The old text. Required.
- replace (str): The new text. Required.
:param path: The path to the file to be edited.
:type path: str
:param backup: The backup file extension to use.
:type backup: str
:param delimiter: The pattern delimiter.
:type delimiter: str
:param find: The old text. Required.
:param find: str
:param sub: The new text. Required.
:type sub: str
"""
@ -354,7 +443,7 @@ def replace(path, backup=".b", delimiter="/", find=None, replace=None, **kwargs)
'delimiter': delimiter,
'path': path,
'pattern': find,
'replace': replace,
'replace': sub,
}
template = "sed -i %(backup)s 's%(delimiter)s%(pattern)s%(delimiter)s%(replace)s%(delimiter)sg' %(path)s"
@ -365,7 +454,12 @@ def replace(path, backup=".b", delimiter="/", find=None, replace=None, **kwargs)
def run(statement, **kwargs):
"""Run any command."""
"""Run any command.
:param statement: The statement to be executed.
:type statement: str
"""
kwargs.setdefault("comment", "run statement")
return Command(statement, **kwargs)
@ -375,16 +469,35 @@ def rsync(source, target, delete=False, exclude=None, host=None, key_file=None,
recursive=True, user=None, **kwargs):
"""Synchronize a directory structure.
- source (str): The source directory.
- target (str): The target directory.
- delete (bool): Indicates target files that exist in source but not in target should be removed.
- exclude (str): The path to an exclude file.
- host (str): The host name or IP address. This causes the command to run over SSH.
- key_file (str): The privacy SSH key (path) for remote connections. User expansion is automatically applied.
- links (bool): Include symlinks in the sync.
- port (int): The SSH port to use for remote connections.
- recursive (bool): Indicates source contents should be recursively synchronized.
- user (str): The user name to use for remote connections.
:param source: The source directory.
:type source: str
:param target: The target directory.
:type target: str
:param delete: Indicates target files that exist in source but not in target should be removed.
:type delete: bool
:param exclude: The path to an exclude file.
:type exclude: str
:param host: The host name or IP address.
:type host: str
:param key_file: The privacy SSH key (path) for remote connections. User expansion is automatically applied.
:type key_file: str
:param links: Include symlinks in the sync.
:type links: bool
:param port: The SSH port to use for remote connections.
:type port: int
:param recursive: Indicates source contents should be recursively synchronized.
:type recursive: bool
:param user: The username to use for remote connections.
:type user: str
"""
# - guess: When ``True``, the ``host``, ``key_file``, and ``user`` will be guessed based on the base name of
@ -455,12 +568,23 @@ def rsync(source, target, delete=False, exclude=None, host=None, key_file=None,
def scopy(from_path, to_path, host=None, key_file=None, port=22, user=None, **kwargs):
"""Copy a file or directory to a remote server.
- from_path (str): The source directory.
- to_path (str): The target directory.
- host (str): The host name or IP address. Required.
- key_file (str): The privacy SSH key (path) for remote connections. User expansion is automatically applied.
- port (int): The SSH port to use for remote connections.
- user (str): The user name to use for remote connections.
:param from_path: The source of the copy.
:type from_path: str
:param to_path: The remote target of the copy.
:type to_path: str
:param host: The host name or IP address. Required.
:type host: str
:param key_file: The privacy SSH key (path) for remote connections. User expansion is automatically applied.
:type key_file: str
:param port: The SSH port to use for remote connections.
:type port: int
:param user: The username to use for remote connections.
:type user: str
"""
kwargs.setdefault("comment", "copy %s to remote %s" % (from_path, to_path))
@ -491,16 +615,23 @@ def scopy(from_path, to_path, host=None, key_file=None, port=22, user=None, **kw
def sync(source, target, delete=False, exclude=None, links=True, recursive=True, **kwargs):
"""Synchronize a local directory structure.
- source (str): The source directory.
- target (str): The target directory.
- delete (bool): Indicates target files that exist in source but not in target should be removed.
- exclude (str): The path to an exclude file.
- host (str): The host name or IP address. This causes the command to run over SSH.
- key_file (str): The privacy SSH key (path) for remote connections. User expansion is automatically applied.
- links (bool): Include symlinks in the sync.
- port (int): The SSH port to use for remote connections.
- recursive (bool): Indicates source contents should be recursively synchronized.
- user (str): The user name to use for remote connections.
:param source: The source directory.
:type source: str
:param target: The target directory.
:type target: str
:param delete: Indicates target files that exist in source but not in target should be removed.
:type delete: bool
:param exclude: The path to an exclude file.
:type exclude: str
:param links: Include symlinks in the sync.
:type links: bool
:param recursive: Indicates source contents should be recursively synchronized.
:type recursive: bool
"""
# - guess: When ``True``, the ``host``, ``key_file``, and ``user`` will be guessed based on the base name of
@ -552,7 +683,8 @@ def sync(source, target, delete=False, exclude=None, links=True, recursive=True,
def touch(path, **kwargs):
"""Touch a file or directory.
- path (str): The file or directory to touch.
:param path: The file or directory to touch.
:type path: str
"""
kwargs.setdefault("comment", "touch %s" % path)
@ -563,7 +695,8 @@ def touch(path, **kwargs):
def wait(seconds, **kwargs):
"""Pause execution for a number of seconds.
- seconds (int): The number of seconds to wait.
:param seconds: The number of seconds to wait.
:type seconds: int
"""
kwargs.setdefault("comment", "pause for %s seconds" % seconds)
@ -574,8 +707,11 @@ def wait(seconds, **kwargs):
def write(path, content=None, **kwargs):
"""Write to a file.
- path (str): The file to be written.
- content (str): The content to be written. Note: If omitted, this command is equivalent to ``touch``.
:param path: The file to be written.
:type path: str
:param content: The content to be written. Note: If omitted, this command is equivalent to ``touch``.
:type content: str
"""
_content = content or ""

@ -4,11 +4,20 @@ from .base import Command
def python_pip(name, op="install", upgrade=False, venv=None, version=3, **kwargs):
"""Use pip to install or uninstall a Python package.
- name (str): The name of the package.
- op (str): The operation to perform; install, uninstall
- upgrade (bool): Upgrade an installed package.
- venv (str): The name of the virtual environment to load.
- version (int): The Python version to use, e.g. ``2`` or ``3``.
:param name: The name of the package.
:type name: str
:param op: The operation to perform; ``install``, ``remove``
:type op: str
:param upgrade: Upgrade an installed package.
:type upgrade: bool
:param venv: The name of the virtual environment to load.
:type venv: str
:param version: The Python version to use, e.g. ``2`` or ``3``.
:type version: int
"""
manager = "pip"
@ -57,7 +66,8 @@ def python_pip_file(path, venv=None, version=3, **kwargs):
def python_virtualenv(name, **kwargs):
"""Create a Python virtual environment.
- name (str): The name of the environment to create.
:param name: The name of the environment to create.
:type name: str
"""
kwargs.setdefault("comment", "create %s virtual environment" % name)

@ -44,7 +44,8 @@ __all__ = (
def apache(op, **kwargs):
"""Execute an Apache-related command.
- op (str): The operation to perform; reload, restart, start, stop, test.
:param op: The operation to perform; ``reload``, ``restart``, ``start``, ``stop``, ``test``.
:type op: str
"""
if op == "reload":
@ -64,7 +65,8 @@ def apache(op, **kwargs):
def apache_disable_module(module, **kwargs):
"""Disable an Apache module.
- name (str): The module name.
:param module: The name of the module.
:type module: str
"""
kwargs.setdefault("comment", "disable %s apache module" % module)
@ -75,7 +77,8 @@ def apache_disable_module(module, **kwargs):
def apache_disable_site(site, **kwargs):
"""Disable an Apache site.
- name (str): The domain name.
:param site: The site/domain name.
:type site: str
"""
kwargs.setdefault("comment", "disable %s apache site" % site)
@ -86,7 +89,8 @@ def apache_disable_site(site, **kwargs):
def apache_enable_module(module, **kwargs):
"""Enable an Apache module.
- name (str): The module name.
:param module: The name of the module.
:type module: str
"""
kwargs.setdefault("comment", "enable %s apache module" % module)
@ -97,6 +101,8 @@ def apache_enable_module(module, **kwargs):
def apache_enable_site(site, **kwargs):
"""Enable an Apache site.
:param site: The site/domain name.
:type site: str
"""
kwargs.setdefault("comment", "enable %s apache module" % site)
@ -105,6 +111,7 @@ def apache_enable_site(site, **kwargs):
def apache_reload(**kwargs):
"""Reload the apache service."""
kwargs.setdefault("comment", "reload apache")
kwargs.setdefault("register", "apache_reloaded")
@ -112,6 +119,7 @@ def apache_reload(**kwargs):
def apache_restart(**kwargs):
"""Restart the apache service."""
kwargs.setdefault("comment", "restart apache")
kwargs.setdefault("register", "apache_restarted")
@ -119,6 +127,7 @@ def apache_restart(**kwargs):
def apache_start(**kwargs):
"""Start the apache service."""
kwargs.setdefault("comment", "start apache")
kwargs.setdefault("register", "apache_started")
@ -126,12 +135,14 @@ def apache_start(**kwargs):
def apache_stop(**kwargs):
"""Stop the apache service."""
kwargs.setdefault("comment", "stop apache")
return Command("service apache2 stop", **kwargs)
def apache_test(**kwargs):
"""Run a configuration test on apache."""
kwargs.setdefault("comment", "check apache configuration")
kwargs.setdefault("register", "apache_checks_out")
@ -141,7 +152,8 @@ def apache_test(**kwargs):
def service_reload(service, **kwargs):
"""Reload a service.
- name (str): The service name.
:param service: The service name.
:type service: str
"""
kwargs.setdefault("comment", "reload %s service" % service)
@ -153,7 +165,8 @@ def service_reload(service, **kwargs):
def service_restart(service, **kwargs):
"""Restart a service.
- name (str): The service name.
:param service: The service name.
:type service: str
"""
kwargs.setdefault("comment", "restart %s service" % service)
@ -165,7 +178,8 @@ def service_restart(service, **kwargs):
def service_start(service, **kwargs):
"""Start a service.
- name (str): The service name.
:param service: The service name.
:type service: str
"""
kwargs.setdefault("comment", "start %s service" % service)
@ -177,7 +191,8 @@ def service_start(service, **kwargs):
def service_stop(service, **kwargs):
"""Stop a service.
- name (str): The service name.
:param service: The service name.
:type service: str
"""
kwargs.setdefault("comment", "stop %s service" % service)
@ -189,7 +204,8 @@ def service_stop(service, **kwargs):
def system(op, **kwargs):
"""Perform a system operation.
- op (str): The operation to perform; reboot, update, upgrade.
:param op: The operation to perform; ``reboot``, ``update``, ``upgrade``.
:type op: str
"""
if op == "reboot":
@ -205,7 +221,8 @@ def system(op, **kwargs):
def system_install(package, **kwargs):
"""Install a system-level package.
- name (str): The name of the package to install.
:param package: The name of the package to install.
:type package: str
"""
kwargs.setdefault("comment", "install system package %s" % package)
@ -214,6 +231,7 @@ def system_install(package, **kwargs):
def system_reboot(**kwargs):
"""Reboot the system."""
kwargs.setdefault("comment", "reboot the system")
return Command("reboot", **kwargs)
@ -222,7 +240,8 @@ def system_reboot(**kwargs):
def system_uninstall(package, **kwargs):
"""Uninstall a system-level package.
- name (str): The name of the package to uninstall.
:param package: The name of the package to remove.
:type package: str
"""
kwargs.setdefault("comment", "remove system package %s" % package)
@ -231,12 +250,14 @@ def system_uninstall(package, **kwargs):
def system_update(**kwargs):
"""Update the system's package info."""
kwargs.setdefault("comment", "update system package info")
return Command("apt update -y", **kwargs)
def system_upgrade(**kwargs):
"""updated the system."""
kwargs.setdefault("comment", "upgrade the system")
return Command("apt upgrade -y", **kwargs)
@ -245,11 +266,20 @@ def system_upgrade(**kwargs):
def user(name, groups=None, home=None, op="add", password=None, **kwargs):
"""Create or remove a user.
- name (str): The user name.
- groups (str | list): A list of groups to which the user should belong.
- home (str): The path to the user's home directory.
- op (str); The operation to perform; ``add`` or ``remove``.
- password (str): The user's password. (NOT IMPLEMENTED)
:param name: The username.
:type name: str
:param groups: A list of groups to which the user should belong.
:type groups: list | str
:param home: The path to the user's home directory.
:type home: str
:param op: The operation to perform; ``add`` or ``remove``.
:type op:
:param password: The user's password. (NOT IMPLEMENTED)
:type password: str
"""
if op == "add":

@ -125,8 +125,7 @@ class BaseLoader(File):
:param path: The path to the command file.
:type path: str
:param context: Global context that may be used when to parse the command file, snippets, and templates. This is
converted to a ``dict`` when passed to a Snippet or Template.
:param context: Global context that may be used to parse the command file and templates.
:type context: scripttease.lib.contexts.Context
:param locations: A list of paths where templates and other external files may be found. The ``templates/``

@ -22,7 +22,7 @@ setup(
url='https://develmaycare.com/products/python/scripttease/',
download_url='https://gittraction.com/diff6/python-scripttease',
project_urls={
'Documentation': "https://docs.develmaycare.com/en/python-scripttease/latest/",
'Documentation': "https://docs.diff6.com/en/python-scripttease/latest/",
'Source': "https://gittraction.com/diff6/python-scripttease",
'Tracker': "https://gittraction.com/diff6/python-scripttease/issues"
},

@ -23,6 +23,23 @@ def test_pgsql_exists():
assert "testing_exists" in s
def test_pgsql_grant():
with pytest.raises(InvalidInput):
pgsql_grant("bob")
with pytest.raises(InvalidInput):
pgsql_grant("bob", database="testing")
c = pgsql_grant("bob", database="testing", schema="public")
s = c.get_statement()
assert '--dbname="testing"' in s
assert "GRANT ALL PRIVILEGES ON SCHEMA public TO bob" in s
c = pgsql_grant("bob", database="testing", table="testing")
s = c.get_statement()
assert "GRANT ALL PRIVILEGES ON TABLE testing TO bob" in s
def test_pgsql_drop():
c = pgsql_drop("testing")
s = c.get_statement()

@ -208,7 +208,7 @@ def test_scopy():
def test_sed():
c = replace("/path/to/file.txt", find="testing", replace="123")
c = replace("/path/to/file.txt", find="testing", sub="123")
s = c.get_statement()
assert "sed -i .b" in s
assert "s/testing/123/g" in s

Loading…
Cancel
Save