parent
7a64eeff09
commit
6de3956dc6
29 changed files with 910 additions and 1217 deletions
@ -1,9 +1,15 @@ |
|||||||
[project] |
[project] |
||||||
category = developer |
category = developer |
||||||
description = A collection of classes and commands for automated command line scripting using Pythonn. |
description = A collection of classes and commands for automated command line scripting using Python. |
||||||
|
icon = fas fa-scroll |
||||||
title = Python Script Tease |
title = Python Script Tease |
||||||
type = cli |
type = cli |
||||||
|
|
||||||
[business] |
[business] |
||||||
code = PTL |
code = PTL |
||||||
name = Pleasant Tents, LLC |
name = Pleasant Tents, LLC |
||||||
|
|
||||||
|
[license] |
||||||
|
code = bsd3 |
||||||
|
name = 3-Clause BSD |
||||||
|
url = https://opensource.org/licenses/BSD-3-Clause |
||||||
|
@ -1,382 +0,0 @@ |
|||||||
#! /usr/bin/env python |
|
||||||
|
|
||||||
from argparse import ArgumentParser, RawDescriptionHelpFormatter |
|
||||||
from commonkit import highlight_code, indent, smart_cast, write_file |
|
||||||
from commonkit.logging import LoggingHelper |
|
||||||
from commonkit.shell import EXIT |
|
||||||
from markdown import markdown |
|
||||||
import os |
|
||||||
import sys |
|
||||||
|
|
||||||
sys.path.insert(0, "../") |
|
||||||
|
|
||||||
from scripttease.constants import LOGGER_NAME |
|
||||||
from scripttease.lib.contexts import Context |
|
||||||
from scripttease.lib.loaders import load_variables, INILoader, YMLLoader |
|
||||||
from scripttease.variables import PATH_TO_SCRIPT_TEASE |
|
||||||
from scripttease.version import DATE as VERSION_DATE, VERSION |
|
||||||
|
|
||||||
DEBUG = 10 |
|
||||||
|
|
||||||
logging = LoggingHelper(colorize=True, name=LOGGER_NAME) |
|
||||||
log = logging.setup() |
|
||||||
|
|
||||||
|
|
||||||
def execute(): |
|
||||||
"""Process script configurations.""" |
|
||||||
|
|
||||||
__author__ = "Shawn Davis <shawn@develmaycare.com>" |
|
||||||
__date__ = VERSION_DATE |
|
||||||
__help__ = """NOTES |
|
||||||
|
|
||||||
This command is used to parse configuration files and output the commands. |
|
||||||
|
|
||||||
""" |
|
||||||
__version__ = VERSION + "+new" |
|
||||||
|
|
||||||
# Main argument parser from which sub-commands are created. |
|
||||||
parser = ArgumentParser(description=__doc__, epilog=__help__, formatter_class=RawDescriptionHelpFormatter) |
|
||||||
|
|
||||||
parser.add_argument( |
|
||||||
"path", |
|
||||||
default="steps.ini", |
|
||||||
nargs="?", |
|
||||||
help="The path to the configuration file." |
|
||||||
) |
|
||||||
|
|
||||||
parser.add_argument( |
|
||||||
"-c", |
|
||||||
"--color", |
|
||||||
action="store_true", |
|
||||||
dest="color_enabled", |
|
||||||
help="Enable code highlighting for terminal output." |
|
||||||
) |
|
||||||
|
|
||||||
parser.add_argument( |
|
||||||
"-C=", |
|
||||||
"--context=", |
|
||||||
action="append", |
|
||||||
dest="variables", |
|
||||||
help="Context variables for use in pre-parsing the config and templates. In the form of: name:value" |
|
||||||
) |
|
||||||
|
|
||||||
parser.add_argument( |
|
||||||
"-d=", |
|
||||||
"--docs=", |
|
||||||
choices=["html", "markdown", "plain", "rst"], |
|
||||||
# default="markdown", |
|
||||||
dest="docs", |
|
||||||
help="Output documentation instead of code." |
|
||||||
) |
|
||||||
|
|
||||||
parser.add_argument( |
|
||||||
"-D", |
|
||||||
"--debug", |
|
||||||
action="store_true", |
|
||||||
dest="debug_enabled", |
|
||||||
help="Enable debug output." |
|
||||||
) |
|
||||||
|
|
||||||
parser.add_argument( |
|
||||||
"-f=", |
|
||||||
"--filter=", |
|
||||||
action="append", |
|
||||||
dest="filters", |
|
||||||
help="Filter the commands in the form of: attribute:value" |
|
||||||
) |
|
||||||
|
|
||||||
parser.add_argument( |
|
||||||
"-i=", |
|
||||||
"--inventory=", |
|
||||||
dest="inventory", |
|
||||||
help="Copy an inventory item to a local directory." |
|
||||||
) |
|
||||||
|
|
||||||
parser.add_argument( |
|
||||||
"-o=", |
|
||||||
"--option=", |
|
||||||
action="append", |
|
||||||
dest="options", |
|
||||||
help="Common command options in the form of: name:value" |
|
||||||
) |
|
||||||
|
|
||||||
parser.add_argument( |
|
||||||
"-P=", |
|
||||||
"--profile=", |
|
||||||
choices=["centos", "ubuntu"], |
|
||||||
default="ubuntu", |
|
||||||
dest="profile", |
|
||||||
help="The OS profile to use." |
|
||||||
) |
|
||||||
|
|
||||||
parser.add_argument( |
|
||||||
"-T=", |
|
||||||
"--template-path=", |
|
||||||
action="append", |
|
||||||
dest="template_locations", |
|
||||||
help="The location of template files that may be used with the template command." |
|
||||||
) |
|
||||||
|
|
||||||
parser.add_argument( |
|
||||||
"-w=", |
|
||||||
"--write=", |
|
||||||
dest="output_file", |
|
||||||
help="Write the output to disk." |
|
||||||
) |
|
||||||
|
|
||||||
parser.add_argument( |
|
||||||
"-V=", |
|
||||||
"--variables-file=", |
|
||||||
dest="variables_file", |
|
||||||
help="Load variables from a file." |
|
||||||
) |
|
||||||
|
|
||||||
# Access to the version number requires special consideration, especially |
|
||||||
# when using sub parsers. The Python 3.3 behavior is different. See this |
|
||||||
# answer: http://stackoverflow.com/questions/8521612/argparse-optional-subparser-for-version |
|
||||||
parser.add_argument( |
|
||||||
"-v", |
|
||||||
action="version", |
|
||||||
help="Show version number and exit.", |
|
||||||
version=__version__ |
|
||||||
) |
|
||||||
|
|
||||||
parser.add_argument( |
|
||||||
"--version", |
|
||||||
action="version", |
|
||||||
help="Show verbose version information and exit.", |
|
||||||
version="%(prog)s" + " %s %s by %s" % (__version__, __date__, __author__) |
|
||||||
) |
|
||||||
|
|
||||||
# Parse arguments. |
|
||||||
args = parser.parse_args() |
|
||||||
|
|
||||||
if args.debug_enabled: |
|
||||||
log.setLevel(DEBUG) |
|
||||||
|
|
||||||
log.debug("Namespace: %s" % args) |
|
||||||
|
|
||||||
# Create the global context. |
|
||||||
context = Context() |
|
||||||
|
|
||||||
if args.variables_file: |
|
||||||
variables = load_variables(args.variables_file) |
|
||||||
for v in variables: |
|
||||||
context.variables[v.name] = v |
|
||||||
|
|
||||||
if args.variables: |
|
||||||
for token in args.variables: |
|
||||||
try: |
|
||||||
key, value = token.split(":") |
|
||||||
context.add(key, smart_cast(value)) |
|
||||||
except ValueError: |
|
||||||
context.add(token, True) |
|
||||||
|
|
||||||
# Capture filters. |
|
||||||
if args.filters: |
|
||||||
filters = dict() |
|
||||||
for token in args.filters: |
|
||||||
key, value = token.split(":") |
|
||||||
if key not in filters: |
|
||||||
filters[key] = list() |
|
||||||
|
|
||||||
filters[key].append(value) |
|
||||||
|
|
||||||
# Handle global command options. |
|
||||||
options = dict() |
|
||||||
if args.options: |
|
||||||
for token in args.options: |
|
||||||
try: |
|
||||||
key, value = token.split(":") |
|
||||||
options[key] = smart_cast(value) |
|
||||||
except ValueError: |
|
||||||
options[token] = True |
|
||||||
|
|
||||||
# The path may have been given as a file name (steps.ini), path, or an inventory name. |
|
||||||
input_locations = [ |
|
||||||
args.path, |
|
||||||
os.path.join(PATH_TO_SCRIPT_TEASE, "data", "inventory", args.path, "steps.ini"), |
|
||||||
os.path.join(PATH_TO_SCRIPT_TEASE, "data", "inventory", args.path, "steps.yml"), |
|
||||||
] |
|
||||||
path = None |
|
||||||
for location in input_locations: |
|
||||||
if os.path.exists(location): |
|
||||||
path = location |
|
||||||
break |
|
||||||
|
|
||||||
if path is None: |
|
||||||
log.warning("Path does not exist: %s" % args.path) |
|
||||||
exit(EXIT.INPUT) |
|
||||||
|
|
||||||
# Load the commands. |
|
||||||
if path.endswith(".ini"): |
|
||||||
loader = INILoader( |
|
||||||
path, |
|
||||||
context=context, |
|
||||||
locations=args.template_locations, |
|
||||||
profile=args.profile, |
|
||||||
**options |
|
||||||
) |
|
||||||
elif path.endswith(".yml"): |
|
||||||
loader = YMLLoader( |
|
||||||
path, |
|
||||||
context=context, |
|
||||||
locations=args.template_locations, |
|
||||||
profile=args.profile, |
|
||||||
**options |
|
||||||
) |
|
||||||
else: |
|
||||||
log.error("Unsupported file format: %s" % path) |
|
||||||
exit(EXIT.ERROR) |
|
||||||
|
|
||||||
# noinspection PyUnboundLocalVariable |
|
||||||
if not loader.load(): |
|
||||||
log.error("Failed to load the input file: %s" % path) |
|
||||||
exit(EXIT.ERROR) |
|
||||||
|
|
||||||
# Validate snippets before continuing. |
|
||||||
valid = list() |
|
||||||
for snippet in loader.get_snippets(): |
|
||||||
if snippet.is_valid: |
|
||||||
valid.append(True) |
|
||||||
else: |
|
||||||
log.error("Invalid snippet: %s" % snippet.name) |
|
||||||
valid.append(False) |
|
||||||
|
|
||||||
if not all(valid): |
|
||||||
exit(EXIT.ERROR) |
|
||||||
|
|
||||||
# Generate output. |
|
||||||
if args.docs: |
|
||||||
output = list() |
|
||||||
for snippet in loader.get_snippets(): |
|
||||||
|
|
||||||
# Will this every happen? |
|
||||||
# if snippet is None: |
|
||||||
# continue |
|
||||||
|
|
||||||
if snippet.name == "explain": |
|
||||||
if snippet.header: |
|
||||||
if args.docs == "plain": |
|
||||||
output.append("***** %s *****" % snippet.name.title()) |
|
||||||
elif args.docs == "rst": |
|
||||||
output.append(snippet.name.title()) |
|
||||||
output.append("=" * len(snippet.name)) |
|
||||||
else: |
|
||||||
output.append("## %s" % snippet.name.title()) |
|
||||||
|
|
||||||
output.append("") |
|
||||||
|
|
||||||
output.append(snippet.content) |
|
||||||
output.append("") |
|
||||||
elif snippet.name == "screenshot": |
|
||||||
if args.docs == "html": |
|
||||||
b = list() |
|
||||||
b.append('<img src="%s"' % snippet.args[0]) |
|
||||||
b.append('alt="%s"' % snippet.caption or snippet.comment) |
|
||||||
|
|
||||||
if snippet.classes: |
|
||||||
b.append('class="%s"' % snippet.classes) |
|
||||||
|
|
||||||
if snippet.height: |
|
||||||
b.append('height="%s"' % snippet.height) |
|
||||||
|
|
||||||
if snippet.width: |
|
||||||
b.append('width="%s"' % snippet) |
|
||||||
|
|
||||||
output.append(" ".join(b) + ">") |
|
||||||
output.append("") |
|
||||||
elif args.docs == "plain": |
|
||||||
output.append(snippet.args[0]) |
|
||||||
output.append("") |
|
||||||
elif args.docs == "rst": |
|
||||||
output.append(".. figure:: %s" % snippet.args[0]) |
|
||||||
|
|
||||||
if snippet.caption: |
|
||||||
output.append(indent(":alt: %s" % snippet.caption or snippet.comment)) |
|
||||||
|
|
||||||
if snippet.height: |
|
||||||
output.append(indent(":height: %s" % snippet.height)) |
|
||||||
|
|
||||||
if snippet.width: |
|
||||||
output.append(indent(":width: %s" % snippet.width)) |
|
||||||
|
|
||||||
output.append("") |
|
||||||
else: |
|
||||||
output.append("![%s](%s)" % (snippet.caption or snippet.comment, snippet.args[0])) |
|
||||||
output.append("") |
|
||||||
elif snippet.name == "template": |
|
||||||
if args.docs == "plain": |
|
||||||
output.append("+++") |
|
||||||
output.append(snippet.get_content()) |
|
||||||
output.append("+++") |
|
||||||
elif args.docs == "rst": |
|
||||||
output.append(".. code-block:: %s" % snippet.get_target_language()) |
|
||||||
output.append("") |
|
||||||
output.append(indent(snippet.get_content())) |
|
||||||
output.append("") |
|
||||||
else: |
|
||||||
output.append("```%s" % snippet.get_target_language()) |
|
||||||
output.append(snippet.get_content()) |
|
||||||
output.append("```") |
|
||||||
output.append("") |
|
||||||
else: |
|
||||||
statement = snippet.get_statement(include_comment=False, include_register=False, include_stop=False) |
|
||||||
if statement is not None: |
|
||||||
line = snippet.comment.replace("#", "") |
|
||||||
output.append("%s:" % line.capitalize()) |
|
||||||
output.append("") |
|
||||||
if args.docs == "plain": |
|
||||||
output.append("---") |
|
||||||
output.append(statement) |
|
||||||
output.append("---") |
|
||||||
output.append("") |
|
||||||
elif args.docs == "rst": |
|
||||||
output.append(".. code-block:: bash") |
|
||||||
output.append("") |
|
||||||
output.append(indent(statement)) |
|
||||||
output.append("") |
|
||||||
else: |
|
||||||
output.append("```bash") |
|
||||||
output.append(statement) |
|
||||||
output.append("```") |
|
||||||
output.append("") |
|
||||||
|
|
||||||
if args.docs == "html": |
|
||||||
_output = markdown("\n".join(output), extensions=['fenced_code']) |
|
||||||
else: |
|
||||||
_output = "\n".join(output) |
|
||||||
|
|
||||||
print(_output) |
|
||||||
|
|
||||||
if args.output_file: |
|
||||||
write_file(args.output_file, _output) |
|
||||||
else: |
|
||||||
commands = list() |
|
||||||
for snippet in loader.get_snippets(): |
|
||||||
# Explanations and screenshots don't produce usable statements but may be added as comments. |
|
||||||
if snippet.name in ("explain", "screenshot"): |
|
||||||
# commands.append("# %s" % snippet.content) |
|
||||||
# commands.append("") |
|
||||||
continue |
|
||||||
|
|
||||||
statement = snippet.get_statement() |
|
||||||
if statement is not None: |
|
||||||
commands.append(statement) |
|
||||||
commands.append("") |
|
||||||
|
|
||||||
if args.color_enabled: |
|
||||||
print(highlight_code("\n".join(commands), language="bash")) |
|
||||||
else: |
|
||||||
print("\n".join(commands)) |
|
||||||
|
|
||||||
if args.output_file: |
|
||||||
write_file(args.output_file, "\n".join(commands)) |
|
||||||
|
|
||||||
exit(EXIT.OK) |
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__': |
|
||||||
execute() |
|
||||||
|
|
||||||
|
|
@ -1,126 +1,374 @@ |
|||||||
# Imports |
import logging |
||||||
|
|
||||||
from commonkit import smart_cast |
from commonkit import smart_cast |
||||||
from configparser import ConfigParser |
from commonkit.shell import EXIT |
||||||
import logging |
import logging |
||||||
import os |
import os |
||||||
from ..constants import LOGGER_NAME |
from ..lib.loaders import load_variables, INILoader, YMLLoader |
||||||
|
from ..variables import LOGGER_NAME, PATH_TO_SCRIPT_TEASE |
||||||
|
|
||||||
log = logging.getLogger(LOGGER_NAME) |
log = logging.getLogger(LOGGER_NAME) |
||||||
|
|
||||||
# Exports |
|
||||||
|
|
||||||
__all__ = ( |
|
||||||
"context_from_cli", |
|
||||||
"filters_from_cli", |
|
||||||
"options_from_cli", |
|
||||||
"variables_from_file", |
|
||||||
) |
|
||||||
|
|
||||||
# Functions |
|
||||||
|
|
||||||
|
|
||||||
def context_from_cli(variables): |
|
||||||
"""Takes a list of variables given in the form of ``name:value`` and converts them to a dictionary. |
|
||||||
|
|
||||||
:param variables: A list of strings of ``name:value`` pairs. |
|
||||||
:type variables: list[str] |
|
||||||
|
|
||||||
:rtype: dict |
|
||||||
|
|
||||||
The ``value`` of the pair passes through "smart casting" to convert it to the appropriate Python data type. |
|
||||||
|
|
||||||
""" |
|
||||||
context = dict() |
|
||||||
for i in variables: |
|
||||||
key, value = i.split(":") |
|
||||||
context[key] = smart_cast(value) |
|
||||||
|
|
||||||
return context |
|
||||||
|
|
||||||
|
|
||||||
def filters_from_cli(filters): |
|
||||||
"""Takes a list of filters given in the form of ``name:value`` and converts them to a dictionary. |
|
||||||
|
|
||||||
:param filters: A list of strings of ``attribute:value`` pairs. |
|
||||||
:type filters: list[str] |
|
||||||
|
|
||||||
:rtype: dict |
|
||||||
|
|
||||||
""" |
|
||||||
_filters = dict() |
|
||||||
for i in filters: |
|
||||||
key, value = i.split(":") |
|
||||||
if key not in filters: |
|
||||||
_filters[key] = list() |
|
||||||
|
|
||||||
_filters[key].append(value) |
|
||||||
|
|
||||||
return _filters |
|
||||||
|
|
||||||
|
|
||||||
def options_from_cli(options): |
def options_from_cli(options): |
||||||
"""Takes a list of variables given in the form of ``name:value`` and converts them to a dictionary. |
|
||||||
|
|
||||||
:param options: A list of strings of ``name:value`` pairs. |
|
||||||
:type options: list[str] |
|
||||||
|
|
||||||
:rtype: dict |
|
||||||
|
|
||||||
The ``value`` of the pair passes through "smart casting" to convert it to the appropriate Python data type. |
|
||||||
|
|
||||||
""" |
|
||||||
_options = dict() |
_options = dict() |
||||||
for i in options: |
for token in options: |
||||||
key, value = i.split(":") |
try: |
||||||
|
key, value = token.split(":") |
||||||
_options[key] = smart_cast(value) |
_options[key] = smart_cast(value) |
||||||
|
except ValueError: |
||||||
|
_options[token] = True |
||||||
|
|
||||||
return _options |
return _options |
||||||
|
|
||||||
|
|
||||||
def variables_from_file(path): |
def loader(path, context=None, options=None, template_locations=None): |
||||||
"""Loads variables from a given INI file. |
# The path may have been given as a file name (steps.ini), path, or an inventory name. |
||||||
|
input_locations = [ |
||||||
:param path: The path to the INI file. |
path, |
||||||
:type path: str |
os.path.join(PATH_TO_SCRIPT_TEASE, "data", "inventory", path, "steps.ini"), |
||||||
|
os.path.join(PATH_TO_SCRIPT_TEASE, "data", "inventory", path, "steps.yml"), |
||||||
:rtype: dict | None |
] |
||||||
|
path = None |
||||||
The resulting dictionary flattens the sections and values. For example: |
for location in input_locations: |
||||||
|
if os.path.exists(location): |
||||||
|
path = location |
||||||
|
break |
||||||
|
|
||||||
|
if path is None: |
||||||
|
log.warning("Path does not exist: %s" % path) |
||||||
|
return None |
||||||
|
|
||||||
.. code-block:: ini |
# Initialize the loader. |
||||||
|
if path.endswith(".ini"): |
||||||
|
_loader = INILoader( |
||||||
|
path, |
||||||
|
context=context, |
||||||
|
locations=template_locations, |
||||||
|
**options |
||||||
|
) |
||||||
|
elif path.endswith(".yml"): |
||||||
|
_loader = YMLLoader( |
||||||
|
path, |
||||||
|
context=context, |
||||||
|
locations=template_locations, |
||||||
|
**options |
||||||
|
) |
||||||
|
else: |
||||||
|
log.error("Unsupported file format: %s" % path) |
||||||
|
return None |
||||||
|
|
||||||
[copyright] |
# Load the commands. |
||||||
name = ACME, Inc. |
if not _loader.load(): |
||||||
year = 2020 |
log.error("Failed to load the input file: %s" % path) |
||||||
|
return None |
||||||
|
|
||||||
[domain] |
return _loader |
||||||
name = example.com |
|
||||||
tld = example_com |
|
||||||
|
|
||||||
The dictionary would contain: |
|
||||||
|
|
||||||
.. code-block:: python |
def subcommands(subparsers): |
||||||
|
"""Initialize sub-commands. |
||||||
|
|
||||||
{ |
:param subparsers: The subparsers instance from argparse. |
||||||
'copyright_name': "ACME, Inc.", |
|
||||||
'copyright_year': 2020, |
|
||||||
'domain_name': "example.com", |
|
||||||
'domain_tld': "example_com", |
|
||||||
} |
|
||||||
|
|
||||||
""" |
""" |
||||||
if not os.path.exists(path): |
sub = SubCommands(subparsers) |
||||||
log.warning("Variables file does not exist: %s" % path) |
sub.docs() |
||||||
return None |
sub.inventory() |
||||||
|
sub.script() |
||||||
ini = ConfigParser() |
|
||||||
ini.read(path) |
|
||||||
|
def variables_from_cli(context, variables): |
||||||
variables = dict() |
for token in variables: |
||||||
for section in ini.sections(): |
try: |
||||||
for key, value in ini.items(section): |
key, value = token.split(":") |
||||||
key = "%s_%s" % (section, key) |
context.add(key, smart_cast(value)) |
||||||
variables[key] = smart_cast(value) |
except ValueError: |
||||||
|
context.add(token, True) |
||||||
return variables |
|
||||||
|
|
||||||
|
class SubCommands(object): |
||||||
|
|
||||||
|
def __init__(self, subparsers): |
||||||
|
self.subparsers = subparsers |
||||||
|
|
||||||
|
def docs(self): |
||||||
|
sub = self.subparsers.add_parser( |
||||||
|
"docs", |
||||||
|
help="Output documentation instead of code." |
||||||
|
) |
||||||
|
|
||||||
|
sub.add_argument( |
||||||
|
"-o=", |
||||||
|
"--output-format=", |
||||||
|
choices=["html", "md", "plain", "rst"], |
||||||
|
default="md", |
||||||
|
dest="output_format", |
||||||
|
help="The output format; HTML, Markdown, plain text, or ReStructuredText." |
||||||
|
) |
||||||
|
|
||||||
|
self._add_script_options(sub) |
||||||
|
self._add_common_options(sub) |
||||||
|
|
||||||
|
def inventory(self): |
||||||
|
sub = self.subparsers.add_parser( |
||||||
|
"inventory", |
||||||
|
aliases=["inv"], |
||||||
|
help="Copy an inventory item to a local directory." |
||||||
|
) |
||||||
|
|
||||||
|
sub.add_argument( |
||||||
|
"name", |
||||||
|
help="The name of the inventory item. Use ? to list available items." |
||||||
|
) |
||||||
|
|
||||||
|
sub.add_argument( |
||||||
|
"-P=", |
||||||
|
"--path=", |
||||||
|
dest="to_path", |
||||||
|
help="The path to where the item should be copied. Defaults to the current working directory." |
||||||
|
) |
||||||
|
|
||||||
|
self._add_common_options(sub) |
||||||
|
|
||||||
|
def script(self): |
||||||
|
sub = self.subparsers.add_parser( |
||||||
|
"script", |
||||||
|
help="Output the commands." |
||||||
|
) |
||||||
|
|
||||||
|
sub.add_argument( |
||||||
|
"-c", |
||||||
|
"--color", |
||||||
|
action="store_true", |
||||||
|
dest="color_enabled", |
||||||
|
help="Enable code highlighting for terminal output." |
||||||
|
) |
||||||
|
|
||||||
|
sub.add_argument( |
||||||
|
"-s", |
||||||
|
"--shebang", |
||||||
|
action="store_true", |
||||||
|
dest="include_shebang", |
||||||
|
help="Add the shebang to the beginning of the output." |
||||||
|
) |
||||||
|
|
||||||
|
self._add_script_options(sub) |
||||||
|
self._add_common_options(sub) |
||||||
|
|
||||||
|
def _add_common_options(self, sub): |
||||||
|
sub.add_argument( |
||||||
|
"-D", |
||||||
|
"--debug", |
||||||
|
action="store_true", |
||||||
|
dest="debug_enabled", |
||||||
|
help="Enable debug mode. Produces extra output." |
||||||
|
) |
||||||
|
|
||||||
|
sub.add_argument( |
||||||
|
"-p", |
||||||
|
action="store_true", |
||||||
|
dest="preview_enabled", |
||||||
|
help="Preview mode." |
||||||
|
) |
||||||
|
|
||||||
|
def _add_script_options(self, sub): |
||||||
|
sub.add_argument( |
||||||
|
"-C=", |
||||||
|
"--context=", |
||||||
|
action="append", |
||||||
|
dest="variables", |
||||||
|
help="Context variables for use in pre-parsing the config and templates. In the form of: name:value" |
||||||
|
) |
||||||
|
|
||||||
|
sub.add_argument( |
||||||
|
"-i=", |
||||||
|
"--input-file=", |
||||||
|
default="commands.ini", |
||||||
|
dest="command_file", |
||||||
|
help="The path to the configuration file." |
||||||
|
) |
||||||
|
|
||||||
|
# sub.add_argument( |
||||||
|
# "-f=", |
||||||
|
# "--filter=", |
||||||
|
# action="append", |
||||||
|
# dest="filters", |
||||||
|
# help="Filter the commands in the form of: attribute:value" |
||||||
|
# ) |
||||||
|
|
||||||
|
# Capture filters. |
||||||
|
# if args.filters: |
||||||
|
# filters = dict() |
||||||
|
# for token in args.filters: |
||||||
|
# key, value = token.split(":") |
||||||
|
# if key not in filters: |
||||||
|
# filters[key] = list() |
||||||
|
# |
||||||
|
# filters[key].append(value) |
||||||
|
|
||||||
|
sub.add_argument( |
||||||
|
"-O=", |
||||||
|
"--option=", |
||||||
|
action="append", |
||||||
|
dest="options", |
||||||
|
help="Common command options in the form of: name:value" |
||||||
|
) |
||||||
|
|
||||||
|
sub.add_argument( |
||||||
|
"-P=", |
||||||
|
"--profile=", |
||||||
|
choices=["centos", "ubuntu"], |
||||||
|
default="ubuntu", |
||||||
|
dest="profile", |
||||||
|
help="The OS profile to use." |
||||||
|
) |
||||||
|
|
||||||
|
sub.add_argument( |
||||||
|
"-T=", |
||||||
|
"--template-path=", |
||||||
|
action="append", |
||||||
|
dest="template_locations", |
||||||
|
help="The location of template files that may be used with the template command." |
||||||
|
) |
||||||
|
|
||||||
|
sub.add_argument( |
||||||
|
"-w=", |
||||||
|
"--write=", |
||||||
|
dest="output_file", |
||||||
|
help="Write the output to disk." |
||||||
|
) |
||||||
|
|
||||||
|
sub.add_argument( |
||||||
|
"-V=", |
||||||
|
"--variables-file=", |
||||||
|
dest="variables_file", |
||||||
|
help="Load variables from a file." |
||||||
|
) |
||||||
|
|
||||||
|
# # Imports |
||||||
|
# |
||||||
|
# from commonkit import smart_cast |
||||||
|
# from configparser import ConfigParser |
||||||
|
# import logging |
||||||
|
# import os |
||||||
|
# from ..constants import LOGGER_NAME |
||||||
|
# |
||||||
|
# log = logging.getLogger(LOGGER_NAME) |
||||||
|
# |
||||||
|
# # Exports |
||||||
|
# |
||||||
|
# __all__ = ( |
||||||
|
# "context_from_cli", |
||||||
|
# "filters_from_cli", |
||||||
|
# "options_from_cli", |
||||||
|
# "variables_from_file", |
||||||
|
# ) |
||||||
|
# |
||||||
|
# # Functions |
||||||
|
# |
||||||
|
# |
||||||
|
# def context_from_cli(variables): |
||||||
|
# """Takes a list of variables given in the form of ``name:value`` and converts them to a dictionary. |
||||||
|
# |
||||||
|
# :param variables: A list of strings of ``name:value`` pairs. |
||||||
|
# :type variables: list[str] |
||||||
|
# |
||||||
|
# :rtype: dict |
||||||
|
# |
||||||
|
# The ``value`` of the pair passes through "smart casting" to convert it to the appropriate Python data type. |
||||||
|
# |
||||||
|
# """ |
||||||
|
# context = dict() |
||||||
|
# for i in variables: |
||||||
|
# key, value = i.split(":") |
||||||
|
# context[key] = smart_cast(value) |
||||||
|
# |
||||||
|
# return context |
||||||
|
# |
||||||
|
# |
||||||
|
# def filters_from_cli(filters): |
||||||
|
# """Takes a list of filters given in the form of ``name:value`` and converts them to a dictionary. |
||||||
|
# |
||||||
|
# :param filters: A list of strings of ``attribute:value`` pairs. |
||||||
|
# :type filters: list[str] |
||||||
|
# |
||||||
|
# :rtype: dict |
||||||
|
# |
||||||
|
# """ |
||||||
|
# _filters = dict() |
||||||
|
# for i in filters: |
||||||
|
# key, value = i.split(":") |
||||||
|
# if key not in filters: |
||||||
|
# _filters[key] = list() |
||||||
|
# |
||||||
|
# _filters[key].append(value) |
||||||
|
# |
||||||
|
# return _filters |
||||||
|
# |
||||||
|
# |
||||||
|
# def options_from_cli(options): |
||||||
|
# """Takes a list of variables given in the form of ``name:value`` and converts them to a dictionary. |
||||||
|
# |
||||||
|
# :param options: A list of strings of ``name:value`` pairs. |
||||||
|
# :type options: list[str] |
||||||
|
# |
||||||
|
# :rtype: dict |
||||||
|
# |
||||||
|
# The ``value`` of the pair passes through "smart casting" to convert it to the appropriate Python data type. |
||||||
|
# |
||||||
|
# """ |
||||||
|
# _options = dict() |
||||||
|
# for i in options: |
||||||
|
# key, value = i.split(":") |
||||||
|
# _options[key] = smart_cast(value) |
||||||
|
# |
||||||
|
# return _options |
||||||
|
# |
||||||
|
# |
||||||
|
# def variables_from_file(path): |
||||||
|
# """Loads variables from a given INI file. |
||||||
|
# |
||||||
|
# :param path: The path to the INI file. |
||||||
|
# :type path: str |
||||||
|
# |
||||||
|
# :rtype: dict | None |
||||||
|
# |
||||||
|
# The resulting dictionary flattens the sections and values. For example: |
||||||
|
# |
||||||
|
# .. code-block:: ini |
||||||
|
# |
||||||
|
# [copyright] |
||||||
|
# name = ACME, Inc. |
||||||
|
# year = 2020 |
||||||
|
# |
||||||
|
# [domain] |
||||||
|
# name = example.com |
||||||
|
# tld = example_com |
||||||
|
# |
||||||
|
# The dictionary would contain: |
||||||
|
# |
||||||
|
# .. code-block:: python |
||||||
|
# |
||||||
|
# { |
||||||
|
# 'copyright_name': "ACME, Inc.", |
||||||
|
# 'copyright_year': 2020, |
||||||
|
# 'domain_name': "example.com", |
||||||
|
# 'domain_tld': "example_com", |
||||||
|
# } |
||||||
|
# |
||||||
|
# """ |
||||||
|
# if not os.path.exists(path): |
||||||
|
# log.warning("Variables file does not exist: %s" % path) |
||||||
|
# return None |
||||||
|
# |
||||||
|
# ini = ConfigParser() |
||||||
|
# ini.read(path) |
||||||
|
# |
||||||
|
# variables = dict() |
||||||
|
# for section in ini.sections(): |
||||||
|
# for key, value in ini.items(section): |
||||||
|
# key = "%s_%s" % (section, key) |
||||||
|
# variables[key] = smart_cast(value) |
||||||
|
# |
||||||
|
# return variables |
||||||
|
@ -1,153 +1,181 @@ |
|||||||
# Imports |
# Imports |
||||||
|
|
||||||
from commonkit import highlight_code |
from commonkit import copy_tree, highlight_code, indent, write_file |
||||||
from commonkit.shell import EXIT |
from commonkit.shell import EXIT |
||||||
from ..parsers import load_commands, load_config |
from markdown import markdown |
||||||
|
import os |
||||||
|
from ..lib.factories import command_factory |
||||||
|
from ..constants import PROFILE |
||||||
|
from ..variables import PATH_TO_SCRIPT_TEASE |
||||||
|
|
||||||
# Exports |
# Exports |
||||||
|
|
||||||
__all__ = ( |
__all__ = ( |
||||||
"output_commands", |
"copy_inventory", |
||||||
"output_docs", |
"generate_docs", |
||||||
"output_script", |
"generate_script", |
||||||
) |
) |
||||||
|
|
||||||
# Functions |
# Functions |
||||||
|
|
||||||
|
|
||||||
def output_commands(path, color_enabled=False, context=None, filters=None, locations=None, options=None): |
def copy_inventory(name, to_path=None): |
||||||
"""Output commands found in a given configuration file. |
"""Copy an inventory item to a path. |
||||||
|
|
||||||
:param path: The path to the configuration file. |
:param name: The name of the inventory item. ``?`` will list available items. |
||||||
:type path: str |
:type name: str |
||||||
|
|
||||||
:param color_enabled: Indicates the output should be colorized. |
:param to_path: The path to where the item should be copied. Defaults to the current working directory. |
||||||
:type color_enabled: bool |
:type to_path: str |
||||||
|
|
||||||
:param context: The context to be applied to the file before parsing it as configuration. |
|
||||||
:type context: dict |
|
||||||
|
|
||||||
:param filters: Output only those commands which match the given filters. |
|
||||||
:type filters: dict |
|
||||||
|
|
||||||
:param locations: The locations (paths) of additional resources. |
|
||||||
:type locations: list[str] |
|
||||||
|
|
||||||
:param options: Options to be applied to all commands. |
|
||||||
:type options: dict |
|
||||||
|
|
||||||
:rtype: int |
:rtype: int |
||||||
:returns: An exit code. |
:returns: An exit code. |
||||||
|
|
||||||
""" |
""" |
||||||
commands = load_commands( |
path = os.path.join(PATH_TO_SCRIPT_TEASE, "data", "inventory") |
||||||
path, |
if name == "?": |
||||||
context=context, |
for d in os.listdir(path): |
||||||
filters=filters, |
print(d) |
||||||
locations=locations, |
|
||||||
options=options |
|
||||||
) |
|
||||||
if commands is None: |
|
||||||
return EXIT.ERROR |
|
||||||
|
|
||||||
output = list() |
return EXIT.OK |
||||||
for command in commands: |
|
||||||
statement = command.get_statement(cd=True) |
|
||||||
if statement is None: |
|
||||||
continue |
|
||||||
|
|
||||||
output.append(statement) |
from_path = os.path.join(path, name) |
||||||
output.append("") |
|
||||||
|
|
||||||
if color_enabled: |
if to_path is None: |
||||||
print(highlight_code("\n".join(output), language="bash")) |
to_path = os.path.join(os.getcwd(), name) |
||||||
else: |
os.makedirs(to_path) |
||||||
print("\n".join(output)) |
|
||||||
|
|
||||||
|
if copy_tree(from_path, to_path): |
||||||
return EXIT.OK |
return EXIT.OK |
||||||
|
|
||||||
|
return EXIT.ERROR |
||||||
|
|
||||||
def output_docs(path, context=None, filters=None, locations=None, options=None): |
|
||||||
"""Output documentation for commands found in a given configuration file. |
|
||||||
|
|
||||||
:param path: The path to the configuration file. |
def generate_docs(loader, output_file=None, output_format="md", profile=PROFILE.UBUNTU): |
||||||
:type path: str |
"""Generate documentation from a commands file. |
||||||
|
|
||||||
:param context: The context to be applied to the file before parsing it as configuration. |
:param loader: The loader instance. |
||||||
:type context: dict |
:type loader: BaseType[scripttease.lib.loaders.BaseLoader] |
||||||
|
|
||||||
:param filters: Output only those commands which match the given filters. |
:param output_file: The path to the output file. |
||||||
:type filters: dict |
:type output_file: str |
||||||
|
|
||||||
:param locations: The locations (paths) of additional resources. |
:param output_format: The output format; ``html``, ``md`` (Markdown, the default), ``plain`` (text), ``rst`` |
||||||
:type locations: list[str] |
(ReStructuredText). |
||||||
|
:type output_format: str |
||||||
|
|
||||||
:param options: Options to be applied to all commands. |
:param profile: The operating system profile to use. |
||||||
:type options: dict |
:type profile: str |
||||||
|
|
||||||
:rtype: int |
:rtype: int |
||||||
:returns: An exit code. |
:returns: An exit code. |
||||||
|
|
||||||
""" |
""" |
||||||
commands = load_commands( |
commands = command_factory(loader, profile=profile) |
||||||
path, |
|
||||||
context=context, |
|
||||||
filters=filters, |
|
||||||
locations=locations, |
|
||||||
options=options |
|
||||||
) |
|
||||||
if commands is None: |
if commands is None: |
||||||
return EXIT.ERROR |
return EXIT.ERROR |
||||||
|
|
||||||
count = 1 |
|
||||||
output = list() |
output = list() |
||||||
for command in commands: |
for command in commands: |
||||||
output.append("%s. %s" % (count, command.comment)) |
|
||||||
count += 1 |
|
||||||
|
|
||||||
print("\n".join(output)) |
if command.name in ("explain", "screenshot"): |
||||||
|
output.append(command.get_output(output_format)) |
||||||
|
elif command.name == "template": |
||||||
|
if output_format == "plain": |
||||||
|
output.append("+++") |
||||||
|
output.append(command.get_content()) |
||||||
|
output.append("+++") |
||||||
|
output.append("") |
||||||
|
elif output_format == "rst": |
||||||
|
output.append(".. code-block:: %s" % command.get_target_language()) |
||||||
|
output.append("") |
||||||
|
output.append(indent(command.get_content())) |
||||||
|
output.append("") |
||||||
|
else: |
||||||
|
output.append("```%s" % command.get_target_language()) |
||||||
|
output.append(command.get_content()) |
||||||
|
output.append("```") |
||||||
|
output.append("") |
||||||
|
else: |
||||||
|
statement = command.get_statement(include_comment=False, include_register=False, include_stop=False) |
||||||
|
if statement is not None: |
||||||
|
line = command.comment.replace("#", "") |
||||||
|
output.append("%s:" % line.capitalize()) |
||||||
|
output.append("") |
||||||
|
if output_format == "plain": |
||||||
|
output.append("---") |
||||||
|
output.append(statement) |
||||||
|
output.append("---") |
||||||
|
output.append("") |
||||||
|
elif output_format == "rst": |
||||||
|
output.append(".. code-block:: bash") |
||||||
|
output.append("") |
||||||
|
output.append(indent(statement)) |
||||||
|
output.append("") |
||||||
|
else: |
||||||
|
output.append("```bash") |
||||||
|
output.append(statement) |
||||||
|
output.append("```") |
||||||
|
output.append("") |
||||||
|
|
||||||
|
if output_format == "html": |
||||||
|
_output = markdown("\n".join(output), extensions=['fenced_code']) |
||||||
|
else: |
||||||
|
_output = "\n".join(output) |
||||||
|
|
||||||
|
print(_output) |
||||||
|
|
||||||
|
if output_file: |
||||||
|
write_file(output_file, _output) |
||||||
|
|
||||||
return EXIT.OK |
return EXIT.OK |
||||||
|
|
||||||
|
|
||||||
def output_script(path, color_enabled=False, context=None, filters=None, locations=None, options=None): |
def generate_script(loader, color_enabled=False, include_shebang=False, output_file=None, profile=PROFILE.UBUNTU): |
||||||
"""Output a script of commands found in a given configuration file. |
"""Generate statements from a commands file. |
||||||
|
|
||||||
:param path: The path to the configuration file. |
:param loader: The loader instance. |
||||||
:type path: str |
:type loader: BaseType[scripttease.lib.loaders.BaseLoader] |
||||||
|
|
||||||
:param color_enabled: Indicates the output should be colorized. |
:param color_enabled: Colorize the output. |
||||||
:type color_enabled: bool |
:type color_enabled: bool |
||||||
|
|
||||||
:param context: The context to be applied to the file before parsing it as configuration. |
:param include_shebang: Add the shebang to the beginning of the output. |
||||||
:type context: dict |
:type include_shebang: bool |
||||||
|
|
||||||
:param filters: Output only those commands which match the given filters. NOT IMPLEMENTED. |
|
||||||
:type filters: dict |
|
||||||
|
|
||||||
:param locations: The locations (paths) of additional resources. |
:param output_file: The path to the output file. |
||||||
:type locations: list[str] |
:type output_file: str |
||||||
|
|
||||||
:param options: Options to be applied to all commands. |
:param profile: The operating system profile to use. |
||||||
:type options: dict |
:type profile: str |
||||||
|
|
||||||
:rtype: int |
:rtype: int |
||||||
:returns: An exit code. |
:returns: An exit code. |
||||||
|
|
||||||
""" |
""" |
||||||
config = load_config( |
commands = command_factory(loader, profile=profile) |
||||||
path, |
if commands is None: |
||||||
context=context, |
|
||||||
locations=locations, |
|
||||||
options=options |
|
||||||
) |
|
||||||
if config is None: |
|
||||||
return EXIT.ERROR |
return EXIT.ERROR |
||||||
|
|
||||||
script = config.as_script() |
output = list() |
||||||
|
if include_shebang: |
||||||
|
output.append("#! /usr/bin/env bash") |
||||||
|
|
||||||
|
for command in commands: |
||||||
|
if command.name in ("explain", "screenshot"): |
||||||
|
continue |
||||||
|
|
||||||
|
statement = command.get_statement(include_comment=True) |
||||||
|
if statement is not None: |
||||||
|
output.append(statement) |
||||||
|
output.append("") |
||||||
|
|
||||||
if color_enabled: |
if color_enabled: |
||||||
print(highlight_code(script.to_string(), language="bash")) |
print(highlight_code("\n".join(output), language="bash")) |
||||||
else: |
else: |
||||||
print(script) |
print("\n".join(output)) |
||||||
|
|
||||||
|
if output_file: |
||||||
|
write_file(output_file, "\n".join(output)) |
||||||
|
|
||||||
return EXIT.OK |
return EXIT.OK |
||||||
|
@ -0,0 +1,6 @@ |
|||||||
|
[package] |
||||||
|
description = Install Nextcloud. |
||||||
|
docs = https://nextcloud.com |
||||||
|
tags = collaboration |
||||||
|
title = Nextcloud |
||||||
|
version = 0.1.0-d |
@ -0,0 +1,6 @@ |
|||||||
|
[package] |
||||||
|
description = Install PostgreSQL. |
||||||
|
docs = https://postgresql.org |
||||||
|
tags = database, postgres |
||||||
|
title = PostgreSQL |
||||||
|
version = 0.1.0-d |
@ -0,0 +1,6 @@ |
|||||||
|
[package] |
||||||
|
description = Set up an Ubuntu server. |
||||||
|
docs = https://ubuntu.com |
||||||
|
tags = operating system |
||||||
|
title = Ubuntu |
||||||
|
version = 0.1.0-d |
@ -0,0 +1,9 @@ |
|||||||
|
[create the deploy user] |
||||||
|
user: deploy |
||||||
|
groups: sudo, www-data |
||||||
|
home: /var/www |
||||||
|
sudo: yes |
||||||
|
|
||||||
|
[remove a user] |
||||||
|
user: bob |
||||||
|
op: remove |
@ -0,0 +1,9 @@ |
|||||||
|
from scripttease.lib.factories import command_factory |
||||||
|
from scripttease.lib.loaders import INILoader |
||||||
|
|
||||||
|
|
||||||
|
def test_user_commands(): |
||||||
|
ini = INILoader("tests/examples/users.ini") |
||||||
|
ini.load() |
||||||
|
commands = command_factory(ini) |
||||||
|
print(commands) |
Loading…
Reference in new issue