parent
							
								
									7a64eeff09
								
							
						
					
					
						commit
						6de3956dc6
					
				
				 29 changed files with 910 additions and 1217 deletions
			
			
		| @ -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("" % (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 options_from_cli(options): | ||||
|     _options = dict() | ||||
|     for token in options: | ||||
|         try: | ||||
|             key, value = token.split(":") | ||||
|             _options[key] = smart_cast(value) | ||||
|         except ValueError: | ||||
|             _options[token] = True | ||||
| 
 | ||||
|     return _options | ||||
| 
 | ||||
| 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] | ||||
| 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 | ||||
| 
 | ||||
|     :rtype: dict | ||||
|     # 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 | ||||
| 
 | ||||
|     The ``value`` of the pair passes through "smart casting" to convert it to the appropriate Python data type. | ||||
|     # Load the commands. | ||||
|     if not _loader.load(): | ||||
|         log.error("Failed to load the input file: %s" % path) | ||||
|         return None | ||||
| 
 | ||||
|     """ | ||||
|     context = dict() | ||||
|     for i in variables: | ||||
|         key, value = i.split(":") | ||||
|         context[key] = smart_cast(value) | ||||
|     return _loader | ||||
| 
 | ||||
|     return context | ||||
| 
 | ||||
| def subcommands(subparsers): | ||||
|     """Initialize sub-commands. | ||||
| 
 | ||||
| def filters_from_cli(filters): | ||||
|     """Takes a list of filters given in the form of ``name:value`` and converts them to a dictionary. | ||||
|     :param subparsers: The subparsers instance from argparse. | ||||
| 
 | ||||
|     :param filters: A list of strings of ``attribute:value`` pairs. | ||||
|     :type filters: list[str] | ||||
|     """ | ||||
|     sub = SubCommands(subparsers) | ||||
|     sub.docs() | ||||
|     sub.inventory() | ||||
|     sub.script() | ||||
| 
 | ||||
|     :rtype: dict | ||||
| 
 | ||||
|     """ | ||||
|     _filters = dict() | ||||
|     for i in filters: | ||||
|         key, value = i.split(":") | ||||
|         if key not in filters: | ||||
|             _filters[key] = list() | ||||
| 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) | ||||
| 
 | ||||
|         _filters[key].append(value) | ||||
| 
 | ||||
|     return _filters | ||||
| class SubCommands(object): | ||||
| 
 | ||||
|     def __init__(self, subparsers): | ||||
|         self.subparsers = subparsers | ||||
| 
 | ||||
| def options_from_cli(options): | ||||
|     """Takes a list of variables given in the form of ``name:value`` and converts them to a dictionary. | ||||
|     def docs(self): | ||||
|         sub = self.subparsers.add_parser( | ||||
|             "docs", | ||||
|             help="Output documentation instead of code." | ||||
|         ) | ||||
| 
 | ||||
|     :param options: A list of strings of ``name:value`` pairs. | ||||
|     :type options: list[str] | ||||
|         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." | ||||
|         ) | ||||
| 
 | ||||
|     :rtype: dict | ||||
|         self._add_script_options(sub) | ||||
|         self._add_common_options(sub) | ||||
| 
 | ||||
|     The ``value`` of the pair passes through "smart casting" to convert it to the appropriate Python data type. | ||||
|     def inventory(self): | ||||
|         sub = self.subparsers.add_parser( | ||||
|             "inventory", | ||||
|             aliases=["inv"], | ||||
|             help="Copy an inventory item to a local directory." | ||||
|         ) | ||||
| 
 | ||||
|     """ | ||||
|     _options = dict() | ||||
|     for i in options: | ||||
|         key, value = i.split(":") | ||||
|         _options[key] = smart_cast(value) | ||||
|         sub.add_argument( | ||||
|             "name", | ||||
|             help="The name of the inventory item. Use ? to list available items." | ||||
|         ) | ||||
| 
 | ||||
|     return _options | ||||
|         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 variables_from_file(path): | ||||
|     """Loads variables from a given INI file. | ||||
|     def script(self): | ||||
|         sub = self.subparsers.add_parser( | ||||
|             "script", | ||||
|             help="Output the commands." | ||||
|         ) | ||||
| 
 | ||||
|     :param path: The path to the INI file. | ||||
|     :type path: str | ||||
|         sub.add_argument( | ||||
|             "-c", | ||||
|             "--color", | ||||
|             action="store_true", | ||||
|             dest="color_enabled", | ||||
|             help="Enable code highlighting for terminal output." | ||||
|         ) | ||||
| 
 | ||||
|     :rtype: dict | None | ||||
|         sub.add_argument( | ||||
|             "-s", | ||||
|             "--shebang", | ||||
|             action="store_true", | ||||
|             dest="include_shebang", | ||||
|             help="Add the shebang to the beginning of the output." | ||||
|         ) | ||||
| 
 | ||||
|     The resulting dictionary flattens the sections and values. For example: | ||||
|         self._add_script_options(sub) | ||||
|         self._add_common_options(sub) | ||||
| 
 | ||||
|     .. code-block:: ini | ||||
|     def _add_common_options(self, sub): | ||||
|         sub.add_argument( | ||||
|             "-D", | ||||
|             "--debug", | ||||
|             action="store_true", | ||||
|             dest="debug_enabled", | ||||
|             help="Enable debug mode. Produces extra output." | ||||
|         ) | ||||
| 
 | ||||
|         [copyright] | ||||
|         name = ACME, Inc. | ||||
|         year = 2020 | ||||
|         sub.add_argument( | ||||
|             "-p", | ||||
|             action="store_true", | ||||
|             dest="preview_enabled", | ||||
|             help="Preview mode." | ||||
|         ) | ||||
| 
 | ||||
|         [domain] | ||||
|         name = example.com | ||||
|         tld = example_com | ||||
|     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" | ||||
|         ) | ||||
| 
 | ||||
|     The dictionary would contain: | ||||
|         sub.add_argument( | ||||
|             "-i=", | ||||
|             "--input-file=", | ||||
|             default="commands.ini", | ||||
|             dest="command_file", | ||||
|             help="The path to the configuration file." | ||||
|         ) | ||||
| 
 | ||||
|     .. code-block:: python | ||||
|         # 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" | ||||
|         ) | ||||
| 
 | ||||
|         { | ||||
|             'copyright_name': "ACME, Inc.", | ||||
|             'copyright_year': 2020, | ||||
|             'domain_name': "example.com", | ||||
|             'domain_tld': "example_com", | ||||
|         } | ||||
|         sub.add_argument( | ||||
|             "-P=", | ||||
|             "--profile=", | ||||
|             choices=["centos", "ubuntu"], | ||||
|             default="ubuntu", | ||||
|             dest="profile", | ||||
|             help="The OS profile to use." | ||||
|         ) | ||||
| 
 | ||||
|     """ | ||||
|     if not os.path.exists(path): | ||||
|         log.warning("Variables file does not exist: %s" % path) | ||||
|         return None | ||||
|         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." | ||||
|         ) | ||||
| 
 | ||||
|     ini = ConfigParser() | ||||
|     ini.read(path) | ||||
|         sub.add_argument( | ||||
|             "-w=", | ||||
|             "--write=", | ||||
|             dest="output_file", | ||||
|             help="Write the output to disk." | ||||
|         ) | ||||
| 
 | ||||
|     variables = dict() | ||||
|     for section in ini.sections(): | ||||
|         for key, value in ini.items(section): | ||||
|             key = "%s_%s" % (section, key) | ||||
|             variables[key] = smart_cast(value) | ||||
|         sub.add_argument( | ||||
|             "-V=", | ||||
|             "--variables-file=", | ||||
|             dest="variables_file", | ||||
|             help="Load variables from a file." | ||||
|         ) | ||||
| 
 | ||||
|     return variables | ||||
| # # 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) | ||||
| 
 | ||||
|     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