parent
7a64eeff09
commit
6de3956dc6
29 changed files with 910 additions and 1217 deletions
@ -1 +1 @@ |
||||
7.0.0-a |
||||
7.0.0-a |
@ -1,9 +1,15 @@ |
||||
[project] |
||||
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 |
||||
type = cli |
||||
|
||||
[business] |
||||
code = PTL |
||||
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 configparser import ConfigParser |
||||
from commonkit.shell import EXIT |
||||
import logging |
||||
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) |
||||
|
||||
# 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) |
||||
for token in options: |
||||
try: |
||||
key, value = token.split(":") |
||||
_options[key] = smart_cast(value) |
||||
except ValueError: |
||||
_options[token] = True |
||||
|
||||
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: |
||||
def loader(path, context=None, options=None, template_locations=None): |
||||
# The path may have been given as a file name (steps.ini), path, or an inventory name. |
||||
input_locations = [ |
||||
path, |
||||
os.path.join(PATH_TO_SCRIPT_TEASE, "data", "inventory", path, "steps.ini"), |
||||
os.path.join(PATH_TO_SCRIPT_TEASE, "data", "inventory", 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" % 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] |
||||
name = ACME, Inc. |
||||
year = 2020 |
||||
# Load the commands. |
||||
if not _loader.load(): |
||||
log.error("Failed to load the input file: %s" % path) |
||||
return None |
||||
|
||||
[domain] |
||||
name = example.com |
||||
tld = example_com |
||||
return _loader |
||||
|
||||
The dictionary would contain: |
||||
|
||||
.. code-block:: python |
||||
def subcommands(subparsers): |
||||
"""Initialize sub-commands. |
||||
|
||||
{ |
||||
'copyright_name': "ACME, Inc.", |
||||
'copyright_year': 2020, |
||||
'domain_name': "example.com", |
||||
'domain_tld': "example_com", |
||||
} |
||||
:param subparsers: The subparsers instance from argparse. |
||||
|
||||
""" |
||||
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 |
||||
sub = SubCommands(subparsers) |
||||
sub.docs() |
||||
sub.inventory() |
||||
sub.script() |
||||
|
||||
|
||||
def variables_from_cli(context, variables): |
||||
for token in variables: |
||||
try: |
||||
key, value = token.split(":") |
||||
context.add(key, smart_cast(value)) |
||||
except ValueError: |
||||
context.add(token, True) |
||||
|
||||
|
||||
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 |
||||
|
||||
from commonkit import highlight_code |
||||
from commonkit import copy_tree, highlight_code, indent, write_file |
||||
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 |
||||
|
||||
__all__ = ( |
||||
"output_commands", |
||||
"output_docs", |
||||
"output_script", |
||||
"copy_inventory", |
||||
"generate_docs", |
||||
"generate_script", |
||||
) |
||||
|
||||
# Functions |
||||
|
||||
|
||||
def output_commands(path, color_enabled=False, context=None, filters=None, locations=None, options=None): |
||||
"""Output commands found in a given configuration file. |
||||
def copy_inventory(name, to_path=None): |
||||
"""Copy an inventory item to a path. |
||||
|
||||
:param path: The path to the configuration file. |
||||
:type path: str |
||||
:param name: The name of the inventory item. ``?`` will list available items. |
||||
:type name: str |
||||
|
||||
:param color_enabled: Indicates the output should be colorized. |
||||
:type color_enabled: bool |
||||
|
||||
: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 |
||||
:param to_path: The path to where the item should be copied. Defaults to the current working directory. |
||||
:type to_path: str |
||||
|
||||
:rtype: int |
||||
:returns: An exit code. |
||||
|
||||
""" |
||||
commands = load_commands( |
||||
path, |
||||
context=context, |
||||
filters=filters, |
||||
locations=locations, |
||||
options=options |
||||
) |
||||
if commands is None: |
||||
return EXIT.ERROR |
||||
path = os.path.join(PATH_TO_SCRIPT_TEASE, "data", "inventory") |
||||
if name == "?": |
||||
for d in os.listdir(path): |
||||
print(d) |
||||
|
||||
output = list() |
||||
for command in commands: |
||||
statement = command.get_statement(cd=True) |
||||
if statement is None: |
||||
continue |
||||
return EXIT.OK |
||||
|
||||
output.append(statement) |
||||
output.append("") |
||||
from_path = os.path.join(path, name) |
||||
|
||||
if color_enabled: |
||||
print(highlight_code("\n".join(output), language="bash")) |
||||
else: |
||||
print("\n".join(output)) |
||||
if to_path is None: |
||||
to_path = os.path.join(os.getcwd(), name) |
||||
os.makedirs(to_path) |
||||
|
||||
return EXIT.OK |
||||
if copy_tree(from_path, to_path): |
||||
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. |
||||
:type path: str |
||||
def generate_docs(loader, output_file=None, output_format="md", profile=PROFILE.UBUNTU): |
||||
"""Generate documentation from a commands file. |
||||
|
||||
:param context: The context to be applied to the file before parsing it as configuration. |
||||
:type context: dict |
||||
:param loader: The loader instance. |
||||
:type loader: BaseType[scripttease.lib.loaders.BaseLoader] |
||||
|
||||
:param filters: Output only those commands which match the given filters. |
||||
:type filters: dict |
||||
:param output_file: The path to the output file. |
||||
:type output_file: str |
||||
|
||||
:param locations: The locations (paths) of additional resources. |
||||
:type locations: list[str] |
||||
:param output_format: The output format; ``html``, ``md`` (Markdown, the default), ``plain`` (text), ``rst`` |
||||
(ReStructuredText). |
||||
:type output_format: str |
||||
|
||||
:param options: Options to be applied to all commands. |
||||
:type options: dict |
||||
:param profile: The operating system profile to use. |
||||
:type profile: str |
||||
|
||||
:rtype: int |
||||
:returns: An exit code. |
||||
|
||||
""" |
||||
commands = load_commands( |
||||
path, |
||||
context=context, |
||||
filters=filters, |
||||
locations=locations, |
||||
options=options |
||||
) |
||||
commands = command_factory(loader, profile=profile) |
||||
if commands is None: |
||||
return EXIT.ERROR |
||||
|
||||
count = 1 |
||||
output = list() |
||||
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 |
||||
|
||||
|
||||
def output_script(path, color_enabled=False, context=None, filters=None, locations=None, options=None): |
||||
"""Output a script of commands found in a given configuration file. |
||||
def generate_script(loader, color_enabled=False, include_shebang=False, output_file=None, profile=PROFILE.UBUNTU): |
||||
"""Generate statements from a commands file. |
||||
|
||||
:param path: The path to the configuration file. |
||||
:type path: str |
||||
:param loader: The loader instance. |
||||
: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 |
||||
|
||||
: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. NOT IMPLEMENTED. |
||||
:type filters: dict |
||||
:param include_shebang: Add the shebang to the beginning of the output. |
||||
:type include_shebang: bool |
||||
|
||||
:param locations: The locations (paths) of additional resources. |
||||
:type locations: list[str] |
||||
:param output_file: The path to the output file. |
||||
:type output_file: str |
||||
|
||||
:param options: Options to be applied to all commands. |
||||
:type options: dict |
||||
:param profile: The operating system profile to use. |
||||
:type profile: str |
||||
|
||||
:rtype: int |
||||
:returns: An exit code. |
||||
|
||||
""" |
||||
config = load_config( |
||||
path, |
||||
context=context, |
||||
locations=locations, |
||||
options=options |
||||
) |
||||
if config is None: |
||||
commands = command_factory(loader, profile=profile) |
||||
if commands is None: |
||||
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: |
||||
print(highlight_code(script.to_string(), language="bash")) |
||||
print(highlight_code("\n".join(output), language="bash")) |
||||
else: |
||||
print(script) |
||||
print("\n".join(output)) |
||||
|
||||
if output_file: |
||||
write_file(output_file, "\n".join(output)) |
||||
|
||||
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