Updated docs.

development
Shawn Davis 4 years ago
parent 23563b48b4
commit a710e228ab
  1. 2
      .status
  2. 1
      Makefile
  3. 3
      docs/Makefile
  4. 111
      docs/generate_command_signatures.py
  5. 440
      docs/source/_command-examples.rst
  6. 4
      docs/source/_data/cloc.csv
  7. 1019
      docs/source/_includes/overlays.rst
  8. 17
      docs/source/_includes/project-dependencies.rst
  9. 14
      docs/source/_includes/project-tests.rst
  10. 39
      docs/source/commands.rst
  11. 27
      docs/source/contact.rst
  12. 47
      docs/source/getting-started.rst
  13. 110
      docs/source/how-to.rst
  14. 20
      docs/source/index.rst
  15. 21
      docs/source/introduction.rst
  16. 155
      docs/source/project.rst
  17. 0
      docs/source/reference.rst
  18. 40
      docs/source/topics-configuration.rst
  19. 22
      docs/source/topics-overlays.rst
  20. 11
      docs/source/topics.rst
  21. 5
      packages.ini
  22. 4
      requirements.pip
  23. 28
      scripttease/library/overlays/common.py
  24. 78
      scripttease/library/overlays/django.py
  25. 173
      scripttease/library/overlays/pgsql.py
  26. 318
      scripttease/library/overlays/posix.py
  27. 75
      scripttease/library/overlays/ubuntu.py
  28. 4
      scripttease/parsers/utils.py
  29. 4
      setup.py
  30. 5
      tests/test_library_overlays_posix.py

@ -1 +1 @@
active
active

@ -19,6 +19,7 @@ help:
#> docs - Generate documentation.
docs: lines
cd docs && make dirhtml;
cd docs && make html;
cd docs && make coverage;
open docs/build/coverage/python.txt;

@ -54,7 +54,8 @@ clean:
.PHONY: html
html:
#./generate_command_signatures.py > source/_command-examples.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,12 +1,22 @@
#! /usr/bin/env python
# Imports
from collections import OrderedDict
import inspect
import sys
# Set path before importing overlays.
sys.path.append("../")
from script_tease.mappings import MAPPING
# Import overlays
from scripttease.library.overlays.common import COMMON_MAPPINGS
from scripttease.library.overlays.django import DJANGO_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
@ -22,44 +32,89 @@ def get_signature(fn):
return args, kwargs
keys = list(MAPPING.keys())
keys.sort()
def print_description(text):
print(text)
print("")
for key in keys:
cls = MAPPING[key]
print(key)
print("." * len(key))
def print_heading(title):
print(title)
print("=" * len(title))
print("")
extra = cls.get_docs()
if extra is not None:
print(extra)
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("")
# if cls.__init__.__doc__:
# print(cls.__init__.__doc__)
# print("")
print(".. code-block:: ini")
print("")
print(" [run %s command]" % key)
print(".. code-block:: cfg")
print("")
args, kwargs = get_signature(func)
if cls.__doc__:
print(" [%s]" % cls.__doc__.strip().replace(".", "").lower())
else:
print(" [run a %s command]" % cls.__name__.lower())
line = list()
for a in args:
if a != "kwargs":
line.append(a)
args, kwargs = get_signature(cls.__init__)
print(" %s: %s" % (key, " ".join(line)))
line = list()
for a in args:
if a not in ("self", "kwargs"):
line.append(a)
for option, value in kwargs.items():
if value is True:
_value = "yes"
elif value is False:
_value = "no"
else:
_value = value
print(" %s = %s" % (key, " ".join(line)))
print(" %s: %s" % (option, value))
for option, value in kwargs.items():
print(" %s = %s" % (option, value))
print("")
print("")
# Overlay output.
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("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)
exclude_from_ubuntu = COMMON_MAPPINGS.copy()
exclude_from_ubuntu.update(DJANGO_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, Postgres, and POSIX.")
print_mapping(UBUNTU_MAPPINGS, excludes=exclude_from_ubuntu)

@ -1,440 +0,0 @@
apache.disable_module
.....................
.. code-block:: ini
[disable an apache module]
apache.disable_module = module_name
apache.disable_site
...................
.. code-block:: ini
[disable a virtual host]
apache.disable_site = domain_name
apache.enable_module
....................
.. code-block:: ini
[enable an apache module]
apache.enable_module = module_name
apache.enable_site
..................
.. code-block:: ini
[enable a virtual host]
apache.enable_site = domain_name
apache.test
...........
.. code-block:: ini
[run an apache config test]
apache: test
append
......
.. code-block:: ini
[append to a file]
append = path
content = None
archive
.......
.. code-block:: ini
[create an archive file]
archive = from_path
absolute = False
exclude = None
file_name = archive.tgz
strip = 0
to_path = .
view = False
certbot
.......
Alias: ssl
.. code-block:: ini
[get new ssl certificate from let's encrypt]
certbot = domain_name
email = None
webroot = None
copy
....
.. code-block:: ini
[copy a file or directory]
copy = from_path to_path
overwrite = False
recursive = False
django
......
.. code-block:: ini
[run a django management command]
django = name
django.dumpdata
...............
.. code-block:: ini
[export django fixtures]
django.dumpdata = app_name
file_name = initial
indent = 4
natural_foreign = False
natural_primary = False
path = None
django.loaddata
...............
.. code-block:: ini
[load django fixtures]
django.loaddata = app_name
file_name = initial
path = None
extract
.......
.. code-block:: ini
[extract an archive]
extract = from_path
absolute = False
exclude = None
file_name = archive.tgz
strip = 0
to_path = None
view = False
install
.......
.. code-block:: ini
[install a package using apt-get]
apt = package
remove = False
makedir
.......
.. code-block:: ini
[create a directory]
makedir = path
mode = None
recursive = True
message
.......
.. code-block:: ini
[run a message command]
message = output
back_title = Message
dialog = False
height = 15
width = 100
mkdir
.....
.. code-block:: ini
[create a directory]
mkdir = path
mode = None
recursive = True
move
....
.. code-block:: ini
[move a file or directory]
move = from_path to_path
perms
.....
.. code-block:: ini
[set permissions on a file or directory]
perms = path
group = None
mode = None
owner = None
recursive = False
pg.createdb
...........
.. code-block:: ini
[create a postgresql database]
pg.createdb = name
admin_pass = None
admin_user = postgres
host = localhost
owner = None
port = 5432
template = None
pg.createuser
.............
.. code-block:: ini
[create a postgresql user]
pg.createuser = name
admin_pass = None
admin_user = postgres
host = localhost
password = None
port = 5432
pg.db
.....
.. code-block:: ini
[create a postgresql database]
pg.db = name
admin_pass = None
admin_user = postgres
host = localhost
owner = None
port = 5432
template = None
pg.dropdb
.........
.. code-block:: ini
[remove a postgresql database]
pg.dropdb = name
admin_pass = None
admin_user = postgres
host = localhost
port = 5432
pg.dropuser
...........
.. code-block:: ini
[remove a postgres user]
pg.dropuser = name
admin_pass = None
admin_user = postgres
host = localhost
port = 5432
pg.dump
.......
.. code-block:: ini
[export a postgres database]
pg.dump = name
admin_pass = None
admin_user = postgres
file_name = None
host = localhost
port = 5432
pg.exists
.........
.. code-block:: ini
[determine if a postgres database exists]
pg.exists = name
admin_pass = None
admin_user = postgres
host = localhost
port = 5432
pip
...
.. code-block:: ini
[install a python package using pip]
pip = package
remove = False
upgrade = False
psql
....
.. code-block:: ini
[execute a psql command]
psql = sql
database = template1
host = localhost
password = None
port = 5432
user = postgres
reload
......
.. code-block:: ini
[reload a service]
reload = service
remove
......
.. code-block:: ini
[remove a file or directory]
remove = path
force = False
recursive = False
restart
.......
.. code-block:: ini
[restart a service]
restart = service
rsync
.....
.. code-block:: ini
[synchronize files from a local to remote directory]
rsync = source target
delete = False
guess = False
host = None
key_file = None
links = True
port = 22
recursive = True
user = None
run
...
.. code-block:: ini
[a command to be executed]
run = statement
comment = None
condition = None
cd = None
environments = None
function = None
prefix = None
register = None
shell = None
stop = False
sudo = None
tags = None
scopy
.....
.. code-block:: ini
[copy a file from the local (machine) to the remote host]
scp = from_path to_path
host = None
key_file = None
port = 22
user = None
sed
...
.. code-block:: ini
[replace text in a file]
sed = path
backup = .b
change = None
delimiter = /
find = None
start
.....
.. code-block:: ini
[start a service]
start = service
stop
....
.. code-block:: ini
[stop a service]
stop = service
symlink
.......
.. code-block:: ini
[create a symlink]
symlink = source
force = False
target = None
touch
.....
.. code-block:: ini
[touch a file or directory]
touch = path
virtualenv
..........
.. code-block:: ini
[create a python virtual environment]
virtualenv = name
write
.....
.. code-block:: ini
[write to a file]
write = path
content = None
overwrite = False

@ -1,3 +1,3 @@
files,language,blank,comment,code
17,Python,705,569,1156
17,SUM,705,569,1156
18,Python,774,700,1285
18,SUM,774,700,1285

1 files language blank comment code
2 17 18 Python 705 774 569 700 1156 1285
3 17 18 SUM 705 774 569 700 1156 1285

File diff suppressed because it is too large Load Diff

@ -0,0 +1,17 @@
Requirements
------------
superpython
-----------
**URLs**
- `Source Code <https://github.com/develmaycare/superpython>`_
**Install**
.. code-block:: bash
pip install git+https://github.com/develmaycare/superpython;

@ -1,19 +1,15 @@
*****
Tests
*****
Coverage Requirements
=====================
---------------------
100% coverage is required for the ``master`` branch.
See `current coverage report <coverage/index.html>`_.
.. csv-table:: Lines of Code
:file: _data/cloc.csv
:file: ../_data/cloc.csv
Set Up for Testing
==================
------------------
Install requirements:
@ -22,7 +18,7 @@ Install requirements:
pip install tests/requirements.pip
Running Tests
=============
.............
.. tip::
You may use the ``tests`` target of the ``Makefile`` to run tests with coverage:
@ -48,7 +44,7 @@ To allow output from print statements within a test method, add the ``-s`` switc
python -m pytest -s tests/units/path/to/test.py
Reference
=========
---------
- `coverage <https://coverage.readthedocs.io/en/v4.5.x/>`_
- `pytest <https://pytest.org>`_

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

@ -0,0 +1,27 @@
.. _contact:
*******
Contact
*******
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
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
Support
=======
Support inquiries and other questions may be answered on StackOverflow `using the python-scripttease tag`_.
.. _using the python-scripttease tag: https://stackoverflow.com/questions/tagged/python-scripttease

@ -0,0 +1,47 @@
.. _getting-started:
***************
Getting Started
***************
System Requirements
===================
TODO
Install
=======
TODO
Configuration
=============
TODO
Examples
========
TODO
Next Steps
==========
TODO
Resources
=========
TODO
FAQs
====
**Question?**
Answer.
Have a question? `Just ask`_!
.. _Just ask: https://{{ project_domain }}/contact

@ -0,0 +1,110 @@
.. _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.
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)
Export Commands as a Script
===========================
.. code-block:: python
config = Config("commands.ini")
if not config.load():
print("Bummer!")
exit()
script = config.as_script()
print(script)

@ -1,13 +1,23 @@
Python Script Tease
===================
Script Tease
============
.. raw:: html
<p>
<a href="https://develmaycare.com/products/python/script-tease/">Project Home Page</a>
</p>
.. toctree::
:maxdepth: 2
Introduction <introduction>
Configuration <configuration>
Developer Reference <developer>
Tests <tests>
Getting Started <getting-started>
How To <how-to>
Commands <commands>
Topics <topics>
Reference <reference>
Project <project>
Contact <contact>
Indices and tables
==================

@ -4,6 +4,9 @@
Introduction
************
Overview
========
Script Tease is a library and command line tool for generating commands programmatically or using configuration files.
Concepts
@ -14,9 +17,8 @@ Generating Commands
Script Tease may be used in two (2) ways:
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:`configuration`.
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`.
Overlays
--------
@ -25,3 +27,16 @@ An *overlay* is a set of command meta functions that define the capabilities of
.. note::
At present, the only fully defined overlay is for Ubuntu.
See :ref:`topics-overlays`.
Terms and Definitions
=====================
term
Definition.
License
=======
Script Tease is released under the BSD 3 clause license.

@ -0,0 +1,155 @@
*******
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.
Translations
------------
TODO: Translations help make Script Tease available to more people around the world.
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=ScriptTease
.. note::
We reserve the right to proof and approve or decline all content posted on our web site.
Development
===========
TODO
Setting Up For Development
--------------------------
TODO
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/
Tips for Successful Development
-------------------------------
TODO
Getting Help
------------
TODO
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
-------
Prior to the 1.0 release, new features may be released at any time. After the 1.0 release, new features 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,4 +1,4 @@
.. _configuration:
.. _topics-configuration:
*************
Configuration
@ -40,6 +40,8 @@ Notes regarding this format:
- ``yes`` and ``no`` are interpreted as boolean values.
- List values, where required, are separated by commas.
.. _topics-configuration-common-parameters:
Common Parameters
-----------------
@ -76,13 +78,6 @@ Example of an "itemized" command:
.. note::
Command itemization may vary with the command type.
Available Commands
------------------
The following commands instantiate command instances. Each example is shown with the defaults.
.. include:: _command-examples.rst
Pre-Parsing Command Files as Templates
======================================
@ -121,34 +116,7 @@ Then with a config instance:
Using the Tease Command
=======================
The ``tease`` command may be used to parse a configuration file, providing additional utilities for working with
commands.
.. code-block:: text
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.
The ``tease`` command may be used to parse a configuration file, providing additional utilities for working with commands. See :ref:`commands`.
The ``path`` argument defaults to ``commands.ini``.

@ -0,0 +1,22 @@
.. _topics-overlays:
*******
Overlay
*******
An overlay is a collection of functions that provide an interface to command creation.
There are currently three (3) general and re-usable overlays:
- common
- django
- pgsql
And two (2) overlays that are specific to operating systems:
- posix
- 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

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

@ -0,0 +1,5 @@
; SuperPython already has many of the required dependencies, including jinja2 and pygments.
[superpython]
comment = Used throughout for its various components and utilities.
scm = https://github.com/develmaycare/superpython
source = https://github.com/develmaycare/superpython

@ -1,3 +1 @@
coverage
django
sphinx
superpython

@ -8,12 +8,21 @@ __all__ = (
"COMMON_MAPPINGS",
"python_pip",
"python_virtualenv",
"run",
)
# Functions
def python_pip(name, op="install", upgrade=False, venv=None, **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.
"""
if upgrade:
statement = "pip install --upgrade -y %s" % name
else:
@ -27,14 +36,31 @@ def python_pip(name, op="install", upgrade=False, venv=None, **kwargs):
return Command(statement, **kwargs)
def python_virtualenv(name="python", **kwargs):
def python_virtualenv(name, **kwargs):
"""Create a Python virtual environment.
- name (str): The name of the environment to create.
"""
kwargs.setdefault("comment", "create %s virtual environment" % name)
return Command("virtualenv %s" % name, **kwargs)
def run(statement, **kwargs):
"""Run any statement.
- statement (str): The statement to be executed.
"""
kwargs.setdefault("comment", "run statement")
return Command(statement, **kwargs)
# Mappings
COMMON_MAPPINGS = {
'pip': python_pip,
'run': run,
'virtualenv': python_virtualenv,
}

@ -19,6 +19,19 @@ __all__ = (
def _django(name, *args, venv=None, **kwargs):
"""Process a django-based command.
:param name: The name of the management command.
:type name: str
:param venv: The virtual environment to use.
:type venv: str
args and kwargs are used to instantiate the command instance.
This exists because we need ``django()`` to serve as an interface for any management command.
"""
if venv is not None:
kwargs['prefix'] = "source %s/bin/activate" % venv
@ -59,6 +72,14 @@ def _django(name, *args, venv=None, **kwargs):
def django(name, *args, venv=None, **kwargs):
"""Run any Django management command.
- name (str): The name of the management command.
- venv (str): The of the virtual environment to use.
args are passed as positional arguments, while kwargs are given as switches.
"""
if name == "check":
return django_check(venv=venv, **kwargs)
elif name in ("collectstatic", "static"):
@ -70,6 +91,11 @@ def django(name, *args, venv=None, **kwargs):
def django_check(venv=None, **kwargs):
"""Run the Django check command.
- venv (str): The of the virtual environment to use.
"""
kwargs.setdefault("comment", "run django checks")
kwargs.setdefault("register", "django_checks_out")
@ -77,6 +103,11 @@ def django_check(venv=None, **kwargs):
def django_collect_static(venv=None, **kwargs):
"""Collect static files.
- venv (str): The of the virtual environment to use.
"""
kwargs.setdefault("comment", "collect static files")
return _django("collectstatic", venv=venv, **kwargs)
@ -84,25 +115,16 @@ def django_collect_static(venv=None, **kwargs):
def django_dumpdata(app_name, base_path="local", file_name="initial", indent=4, natural_foreign=False,
natural_primary=False, path=None, venv=None, **kwargs):
"""Initialize the command.
:param app_name: The name (app label) of the app. ``app_label.ModelName`` may also be given.
:type app_name: str
:param file_name: The file name to which the data will be dumped.
:type file_name: str
:param indent: Indentation of the exported fixtures.
:type indent: int
:param natural_foreign: Use the natural foreign parameter.
:type natural_foreign: bool
"""Dump data from the database.
:param natural_primary: Use the natural primary parameter.
:type natural_primary: bool
:param path: The path to the data file.
:type path: str
- app_name (str): The name (app label) of the app. ``app_label.ModelName`` may also be given.
- base_path (str): The path under which apps are located in source.
- file_name (str): The file name to which the data will be dumped.
- indent (int): Indentation of the exported fixtures.
- natural_foreign (bool): Use the natural foreign parameter.
- natural_primary (bool): Use the natural primary parameter.
- path (str): The path to the data file.
- venv (str): The of the virtual environment to use.
"""
kwargs.setdefault("comment", "export fixtures for %s" % app_name)
@ -125,16 +147,13 @@ def django_dumpdata(app_name, base_path="local", file_name="initial", indent=4,
def django_loaddata(app_name, base_path="local", file_name="initial", path=None, venv=None, **kwargs):
"""Initialize the command.
:param app_name: The name (app label) of the app.
:type app_name: str
:param file_name: The file name to which the data will be dumped.
:type file_name: str
"""Load data into the database.
:param path: The path to the data file.
:type path: str
- app_name (str): The name (app label) of the app. ``app_label.ModelName`` may also be given.
- base_path (str): The path under which apps are located in source.
- file_name (str): The file name to which the data will be dumped.
- path (str): The path to the data file.
- venv (str): The of the virtual environment to use.
"""
kwargs.setdefault("comment", "load fixtures for %s" % app_name)
@ -147,6 +166,11 @@ def django_loaddata(app_name, base_path="local", file_name="initial", path=None,
def django_migrate(venv=None, **kwargs):
"""Apply database migrations.
- venv (str): The of the virtual environment to use.
"""
kwargs.setdefault("comment", "run django database migrations")
return _django("migrate", venv=venv, **kwargs)

@ -19,6 +19,25 @@ __all__ = (
def _get_pgsql_command(name, host="localhost", password=None, port=5432, user="postgres"):
"""Get a postgres-related command using commonly required parameters.
:param name: The name of the command.
:type name: 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 user name that will be used to execute the command.
:rtype: list[str]
"""
a = list()
if password:
@ -37,26 +56,13 @@ def pg_create_database(name, admin_pass=None, admin_user="postgres", host="local
template=None, **kwargs):
"""Create a PostgreSQL database.
:param name: The database name.
:type name: str
:param admin_pass: The password for the user with sufficient access privileges to execute the command.
:type admin_pass: str
:param admin_user: The name of the user with sufficient access privileges to execute the command.
:type admin_user: str
:param host: The database host name or IP address.
:type host: str
:param owner: The owner (user/role name) of the new database.
:type owner: str
:param port: The port number of the Postgres service running on the host.
:type port: int
:param template: The database template name to use, if any.
:type template: str
- name (str): The database name.
- admin_pass (str): The password for the user with sufficient access privileges to execute the command.
- admin_user (str): The name of the user with sufficient access privileges to execute the command.
- host (str): The database host name or IP address.
- owner (str): The owner (user/role name) of the new database.
- port (int): The port number of the Postgres service running on the host.
- template (str): The database template name to use, if any.
"""
_owner = owner or admin_user
@ -79,23 +85,11 @@ def pg_create_database(name, admin_pass=None, admin_user="postgres", host="local
def pg_create_user(name, admin_pass=None, admin_user="postgres", host="localhost", password=None, port=5432, **kwargs):
"""Create a PostgreSQL user.
:param name: The user name.
:type name: str
:param admin_pass: The password for the user with sufficient access privileges to execute the command.
:type admin_pass: str
:param admin_user: The name of the user with sufficient access privileges to execute the command.
:type admin_user: str
:param host: The database host name or IP address.
:type host: str
:param password: The password for the new user.
:type password: str
:param port: The port number of the Postgres service running on the host.
:type port: int
- name (str): The user name.
- admin_pass (str): The password for the user with sufficient access privileges to execute the command.
- admin_user (str): The name of the user with sufficient access privileges to execute the command.
- host (str): The database host name or IP address.
- port (int): The port number of the Postgres service running on the host.
"""
# Postgres commands always run without sudo because the -U may be provided.
@ -116,20 +110,12 @@ def pg_create_user(name, admin_pass=None, admin_user="postgres", host="localhost
def pg_database_exists(name, admin_pass=None, admin_user="postgres", host="localhost", port=5432, **kwargs):
"""Determine if a Postgres database exists.
:param name: The database name.
:type name: str
:param admin_pass: The password for the user with sufficient access privileges to execute the command.
:type admin_pass: str
:param admin_user: The name of the user with sufficient access privileges to execute the command.
:type admin_user: str
:param host: The database host name or IP address.
:type host: str
:param port: The port number of the Postgres service running on the host.
:type port: int
- name (str): The database name.
- admin_pass (str): The password for the user with sufficient access privileges to execute the command.
- admin_user (str): The name of the user with sufficient access privileges to execute the command.
- host (str): The database host name or IP address.
- owner (str): The owner (user/role name) of the new database.
- port (int): The port number of the Postgres service running on the host.
"""
# Postgres commands always run without sudo because the -U may be provided. However, sudo may be required for
@ -147,20 +133,11 @@ def pg_database_exists(name, admin_pass=None, admin_user="postgres", host="local
def pg_drop_database(name, admin_pass=None, admin_user="postgres", host="localhost", port=5432, **kwargs):
"""Remove a PostgreSQL database.
:param name: The database name.
:type name: str
:param admin_pass: The password for the user with sufficient access privileges to execute the command.
:type admin_pass: str
:param admin_user: The name of the user with sufficient access privileges to execute the command.
:type admin_user: str
:param host: The database host name or IP address.
:type host: str
:param port: The port number of the Postgres service running on the host.
:type port: int
- name (str): The database name.
- admin_pass (str): The password for the user with sufficient access privileges to execute the command.
- admin_user (str): The name of the user with sufficient access privileges to execute the command.
- host (str): The database host name or IP address.
- port (int): The port number of the Postgres service running on the host.
"""
# Postgres commands always run without sudo because the -U may be provided.
@ -176,20 +153,11 @@ def pg_drop_database(name, admin_pass=None, admin_user="postgres", host="localho
def pg_drop_user(name, admin_pass=None, admin_user="postgres", host="localhost", port=5432, **kwargs):
"""Remove a Postgres user.
:param name: The user name.
:type name: str
:param admin_pass: The password for the user with sufficient access privileges to execute the command.
:type admin_pass: str
:param admin_user: The name of the user with sufficient access privileges to execute the command.
:type admin_user: str
:param host: The database host name or IP address.
:type host: str
:param port: The port number of the Postgres service running on the host.
:type port: int
- name (str): The user name.
- admin_pass (str): The password for the user with sufficient access privileges to execute the command.
- admin_user (str): The name of the user with sufficient access privileges to execute the command.
- host (str): The database host name or IP address.
- port (int): The port number of the Postgres service running on the host.
"""
# Postgres commands always run without sudo because the -U may be provided.
@ -206,24 +174,12 @@ def pg_dump_database(name, admin_pass=None, admin_user="postgres", file_name=Non
**kwargs):
"""Export a Postgres database.
:param name: The database name.
:type name: str
:param admin_pass: The password for the user with sufficient access privileges to execute the command.
:type admin_pass: str
:param admin_user: The name of the user with sufficient access privileges to execute the command.
:type admin_user: str
:param host: The database host name or IP address.
:type host: str
:param file_name: The name (including the path, if desired) of the export file. Defaults to the
``database_name`` plus ".sql"
:type file_name: str
:param port: The port number of the Postgres service running on the host.
:type port: int
- name (str): The database name.
- admin_pass (str): The password for the user with sufficient access privileges to execute the command.
- admin_user (str): The name of the user with sufficient access privileges to execute the command.
- file_name (str): The name/path of the export file. Defaults the database name plus ``.sql``.
- host (str): The database host name or IP address.
- port (int): The port number of the Postgres service running on the host.
"""
_file_name = file_name or "%s.sql" % name
@ -243,23 +199,12 @@ def pg_dump_database(name, admin_pass=None, admin_user="postgres", file_name=Non
def psql(sql, database="template1", host="localhost", password=None, port=5432, user="postgres", **kwargs):
"""Execute a psql command.
:param sql: The SQL to be executed.
:type sql: str
:param database: The database name.
:type database: str
:param password: The password for the user with sufficient access privileges to execute the command.
:type password: str
:param host: The database host name or IP address.
:type host: str
:param port: The port number of the Postgres service running on the host.
:type port: int
:param user: The name of the user with sufficient access privileges to execute the command.
:type user: str
- sql (str): The SQL to be executed.
- database (str): The database name.
- admin_pass (str): The password for the user with sufficient access privileges to execute the command.
- admin_user (str): The name of the user with sufficient access privileges to execute the command.
- host (str): The database host name or IP address.
- port (int): The port number of the Postgres service running on the host.
"""
# Postgres commands always run without sudo because the -U may be provided.

@ -20,7 +20,6 @@ __all__ = (
"remove",
"rename",
"rsync",
"run",
"scopy",
"sed",
"symlink",
@ -35,34 +34,12 @@ def archive(from_path, absolute=False, exclude=None, file_name="archive.tgz", st
**kwargs):
"""Create a file archive.
:param from_path: The path that should be archived.
:type from_path: str
:param absolute: By default, the leading slash is stripped from each path. Set to ``True`` to preserve the
absolute path.
:type absolute: bool
:param bzip2: Compress using bzip2.
:type bzip2: bool
:param exclude: A pattern to be excluded from the archive.
:type exclude: str
:param format: The command to use for the operation.
:type format: str
:param gzip: Compress using gzip.
:type gzip: bool
:param strip: Remove the specified number of leading elements from the path. Paths with fewer elements will be
silently skipped.
: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
- 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.
"""
tokens = ["tar"]
@ -93,14 +70,9 @@ 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.
: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
- 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.
"""
_email = email or os.environ.get("SCRIPTTEASE_CERTBOT_EMAIL", None)
@ -122,25 +94,12 @@ def certbot(domain_name, email=None, webroot=None, **kwargs):
def extract(from_path, absolute=False, exclude=None, strip=None, to_path=None, view=False, **kwargs):
"""Extract a file archive.
:param from_path: The path to the archive file.
:type from_path: str
:param absolute: By default, the leading slash is stripped from each path. Set to ``True`` to preserve the
absolute path.
:type absolute: bool
:param exclude: A pattern to be excluded from the archive.
:type exclude: str
:param strip: Remove the specified number of leading elements from the path. Paths with fewer elements will be
silently skipped.
:type strip: int
:param to_path: Where the archive should be extracted.
:type to_path: str
:param view: View the output of the command as it happens.
:type view: bool
- 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.
"""
_to_path = to_path or "./"
@ -172,11 +131,8 @@ def extract(from_path, absolute=False, exclude=None, strip=None, to_path=None, v
def file_append(path, content=None, **kwargs):
"""Append content to a file.
:param path: The path to the file.
:type path: str
:param content: The content to be appended.
:type content: str
- path (str): The path to the file.
- content (str): The content to be appended.
"""
kwargs.setdefault("comment", "append to %s" % path)
@ -187,19 +143,12 @@ def file_append(path, content=None, **kwargs):
def file_copy(from_path, to_path, overwrite=False, recursive=False, **kwargs):
"""Initialize the command.
: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
"""Copy a file or directory.
:param recursive: Copy sub-directories.
:type recursive: bool
- 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.
"""
kwargs.setdefault("comment", "copy %s to %s" % (from_path, to_path))
@ -220,13 +169,10 @@ def file_copy(from_path, to_path, overwrite=False, recursive=False, **kwargs):
def file_write(path, content=None, **kwargs):
"""Initialize the command.
"""Write to a file.
: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 :py:class:`Touch`.
:type content: str
- path (str): The file to be written.
- content (str): The content to be written. Note: If omitted, this command is equivalent to ``touch``.
"""
_content = content or ""
@ -246,16 +192,11 @@ def file_write(path, content=None, **kwargs):
def mkdir(path, mode=None, recursive=True, **kwargs):
"""Initialize the command.
:param path: The path to be created.
:type path: str
:param mode: The access permissions of the new directory.
:type mode: int | str
"""Create a directory.
:param recursive: Create all directories along the path.
:type recursive: bool
- 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)
@ -273,6 +214,12 @@ def mkdir(path, mode=None, recursive=True, **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.
"""
kwargs.setdefault("comment", "move %s to %s" % (from_path, to_path))
statement = "mv %s %s" % (from_path, to_path)
@ -280,22 +227,13 @@ def move(from_path, to_path, **kwargs):
def perms(path, group=None, mode=None, owner=None, recursive=False, **kwargs):
"""Initialize the command.
: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
"""Set permissions on a file or directory.
:param owner: The name of the user to be applied.
:type owner: str
:param recursive: Create all directories along the path.
:type recursive: bool
- 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.
"""
commands = list()
@ -345,16 +283,11 @@ def perms(path, group=None, mode=None, owner=None, recursive=False, **kwargs):
def remove(path, force=False, recursive=False, **kwargs):
"""Initialize the command.
:param path: The path to be removed.
:type path: str
:param force: Force the removal.
:type force: bool
"""Remove a file or directory.
:param recursive: Remove all directories along the path.
:type recursive: bool
- path (str): The path to be removed.
- force (bool): Force the removal.
- recursive (bool): Remove all directories along the path.
"""
kwargs.setdefault("comment", "remove %s" % path)
@ -373,13 +306,10 @@ def remove(path, force=False, recursive=False, **kwargs):
def rename(from_name, to_name, **kwargs):
"""Rename of a file or directory.
"""Rename a file or directory.
:param from_name: The name (or path) of the existing file.
:type from_name: str
:param to_name: The name (or path) of the new file.
:type to_name: str
- from_name (str): The name (or path) of the existing file.
- to_name (str): The name (or path) of the new file.
"""
kwargs.setdefault("comment", "rename %s" % from_name)
@ -388,41 +318,21 @@ def rename(from_name, to_name, **kwargs):
def rsync(source, target, delete=False, exclude=None, host=None, key_file=None, links=True, port=22,
recursive=True, user=None, **kwargs):
"""Initialize the command.
: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. This causes the command to run over SSH and may require a
``key_file``, ``port``, and ``user``.
:type host: str
:param key_file: The path to the private SSH key to use for remove 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 user name to use for remote connections.
"""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 guess: When ``True``, the ``host``, ``key_file``, and ``user`` will be guessed based on the base name of
# - guess: When ``True``, the ``host``, ``key_file``, and ``user`` will be guessed based on the base name of
# the source path.
# :type guess: bool
# if guess:
@ -434,7 +344,7 @@ def rsync(source, target, delete=False, exclude=None, host=None, key_file=None,
# key_file = key_file
# user = user
kwargs.setdefault("comment", "copy %s to remote %s" % (source, target))
kwargs.setdefault("comment", "sync %s with %s" % (source, target))
# rsync -e "ssh -i $(SSH_KEY) -p $(SSH_PORT)" -P -rvzc --delete
# $(OUTPUTH_PATH) $(SSH_USER)@$(SSH_HOST):$(UPLOAD_PATH) --cvs-exclude;
@ -478,75 +388,50 @@ def rsync(source, target, delete=False, exclude=None, host=None, key_file=None,
return Command(statement, **kwargs)
def run(statement, **kwargs):
"""Run any statement."""
kwargs.setdefault("comment", "run statement")
return Command(statement, **kwargs)
def scopy(from_path, to_path, host=None, key_file=None, port=22, user=None, **kwargs):
"""Initialize the command.
"""Copy a file or directory to a remote server.
:param from_path: The source directory.
:type from_path: str
- 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 to_path: The target directory.
:type to_path: str
:param host: The host name or IP address. Required.
:type host: str
:param key_file: The path to the private SSH key to use for remove 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 user name to use for remote connections.
"""
kwargs.setdefault("comment", "copy %s to remote %s" % (from_path, to_path))
"""
kwargs.setdefault("comment", "copy %s to remote %s" % (from_path, to_path))
# TODO: What to do to force local versus remote commands?
# kwargs['local'] = True
# TODO: What to do to force local versus remote commands?
# kwargs['local'] = True
kwargs['sudo'] = False
kwargs['sudo'] = False
statement = ["scp"]
statement = ["scp"]
if key_file is not None:
statement.append("-i %s" % key_file)
if key_file is not None:
statement.append("-i %s" % key_file)
statement.append("-P %s" % port)
statement.append(from_path)
statement.append("-P %s" % port)
statement.append(from_path)
if host is not None and user is not None:
statement.append("%s@%s:%s" % (user, host, to_path))
elif host is not None:
statement.append("%s:%s" % (host, to_path))
else:
raise ValueError("Host is a required keyword argument.")
if host is not None and user is not None:
statement.append("%s@%s:%s" % (user, host, to_path))
elif host is not None:
statement.append("%s:%s" % (host, to_path))
else:
raise ValueError("Host is a required keyword argument.")
return Command(" ".join(statement), **kwargs)
return Command(" ".join(statement), **kwargs)
def sed(path, backup=".b", delimiter="/", find=None, replace=None, **kwargs):
"""Find and replace text in a file.
: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.
:param find: The old text. Required.
:type find: str
:param replace: The new text. Required.
:type replace: str
- 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.
"""
@ -568,16 +453,11 @@ def sed(path, backup=".b", delimiter="/", find=None, replace=None, **kwargs):
def symlink(source, force=False, target=None, **kwargs):
"""Initialize the command.
:param source: The source of the link.
:type source: str
:param force: Force the creation of the link.
:type force: bool
"""Create a symlink.
:param target: The name or path of the target. Defaults to the base name of the source path.
:type target: str
- 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.
"""
_target = target or os.path.basename(source)
@ -596,10 +476,9 @@ def symlink(source, force=False, target=None, **kwargs):
def touch(path, **kwargs):
"""Initialize the command.
"""Touch a file or directory.
:param path: The file or directory to touch.
:type path: str
- path (str): The file or directory to touch.
"""
kwargs.setdefault("comment", "touch %s" % path)
@ -643,14 +522,13 @@ POSIX_MAPPINGS = {
'copy': file_copy,
'extract': extract,
'func': Function,
'function': Function,
# 'function': Function,
'mkdir': mkdir,
'move': move,
'perms': perms,
'remove': remove,
'rename': rename,
'rsync': rsync,
'run': run,
'scopy': scopy,
'sed': sed,
'ssl': certbot,

@ -37,10 +37,23 @@ __all__ = (
def command_exists(name):
"""Indicates whether a given command exists in this overaly.
:param name: The name of the command.
:type name: str
:rtype: bool
"""
return name in MAPPINGS
def apache(op, **kwargs):
"""Execute an Apache-related command.
- op (str): The operation to perform; reload, restart, start, stop, test.
"""
if op == "reload":
return apache_reload(**kwargs)
elif op == "restart":
@ -56,24 +69,43 @@ def apache(op, **kwargs):
def apache_disable_module(name, **kwargs):
"""Disable an Apache module.
- name (str): The module name.
"""
kwargs.setdefault("comment", "disable %s apache module" % name)
return Command("a2dismod %s" % name, **kwargs)
def apache_disable_site(name, **kwargs):
"""Disable an Apache site.
- name (str): The domain name.
"""
kwargs.setdefault("comment", "disable %s apache site" % name)
return Command("a2dissite %s" % name, **kwargs)
def apache_enable_module(name, **kwargs):
"""Enable an Apache module.
- name (str): The module name.
"""
kwargs.setdefault("comment", "enable %s apache module" % name)
return Command("a2enmod %s" % name, **kwargs)
def apache_enable_site(name, **kwargs):
"""Enable an Apache site.
"""
kwargs.setdefault("comment", "enable %s apache module" % name)
return Command("a2ensite %s" % name, **kwargs)
@ -114,6 +146,11 @@ def apache_test(**kwargs):
def service_reload(name, **kwargs):
"""Reload a service.
- name (str): The service name.
"""
kwargs.setdefault("comment", "reload %s service" % name)
kwargs.setdefault("register", "%s_reloaded" % name)
@ -121,6 +158,11 @@ def service_reload(name, **kwargs):
def service_restart(name, **kwargs):
"""Restart a service.
- name (str): The service name.
"""
kwargs.setdefault("comment", "restart %s service" % name)
kwargs.setdefault("register", "%s_restarted" % name)
@ -128,6 +170,11 @@ def service_restart(name, **kwargs):
def service_start(name, **kwargs):
"""Start a service.
- name (str): The service name.
"""
kwargs.setdefault("comment", "start %s service" % name)
kwargs.setdefault("register", "%s_started" % name)
@ -135,6 +182,11 @@ def service_start(name, **kwargs):
def service_stop(name, **kwargs):
"""Stop a service.
- name (str): The service name.
"""
kwargs.setdefault("comment", "stop %s service" % name)
kwargs.setdefault("register", "%s_stopped" % name)
@ -142,6 +194,11 @@ def service_stop(name, **kwargs):
def system(op, **kwargs):
"""Perform a system operation.
- op (str): The operation to perform; reboot, update, upgrade.
"""
if op == "reboot":
return system_reboot(**kwargs)
elif op == "update":
@ -153,6 +210,11 @@ def system(op, **kwargs):
def system_install(name, **kwargs):
"""Install a system-level package.
- name (str): The name of the package to install.
"""
kwargs.setdefault("comment", "install system package %s" % name)
return Command("apt-get install -y %s" % name, **kwargs)
@ -165,6 +227,11 @@ def system_reboot(**kwargs):
def system_uninstall(name, **kwargs):
"""Uninstall a system-level package.
- name (str): The name of the package to uninstall.
"""
kwargs.setdefault("comment", "remove system package %s" % name)
return Command("apt-get uninstall -y %s" % name, **kwargs)
@ -183,6 +250,14 @@ def system_upgrade(**kwargs):
def template(source, target, backup=True, parser=None, **kwargs):
"""Create a file from a template.
- source (str): The path to the template file.
- target (str): The path to where the new file should be created.
- backup (bool): Indicates whether a backup should be made if the target file already exists.
- parser (str): The parser to use ``jinja`` (the default) or ``simple``.
"""
return Template(source, target, backup=backup, parser=parser, **kwargs)

@ -27,8 +27,8 @@ def filter_commands(commands, environments=None, tags=None):
:type environments: list[str]
:param tags: Tag names to be matched.
:type tags
:return:
:type tags: list[str]
"""
filtered = list()
for command in commands:

@ -22,9 +22,8 @@ setup(
url='https://github.com/develmaycare/python-scripttease',
packages=find_packages(exclude=["tests"]),
include_package_data=True,
# superpython provides jinja2 and pygments
install_requires=[
"jinja2",
"pygments",
"superpython",
],
dependency_links=[
@ -43,6 +42,7 @@ setup(
zip_safe=False,
tests_require=[
"coverage",
"pytest",
],
test_suite='runtests.runtests',
entry_points={

@ -107,6 +107,11 @@ def test_remove():
assert "/path/to/dir" in s
def test_rename():
c = rename("/path/to/file.txt", "/path/to/renamed.txt")
assert "mv /path/to/file.txt /path/to/renamed.txt" in c.get_statement()
def test_rsync():
c = rsync(
"/path/to/local/",

Loading…
Cancel
Save