diff --git a/README.md b/README.md index a617539..a416edc 100644 --- a/README.md +++ b/README.md @@ -45,14 +45,21 @@ ### Example Usage ``` sh # Create a standard script example -$ python ./py2shell.py --output basic.sh --datasource templates/basic.json +$ ./py2shell.py --output basic.sh --datasource basic ``` ``` sh # Create a standard script example and make it executeable -$ python ./py2shell.py --output basic.sh --datasource templates/basic.json --exec +$ ./py2shell.py --output basic.sh --datasource basic --exec ``` +### Advanced Examples +``` sh +# Create an advanced script example and make it executeable +$ ./py2shell.py --output basic.sh --datasource shift::shifting_args --exec +``` + + ### Test your scripts 1. All script you create are saved into a locally generated directory named **scripts**. 2. The previously created script is executed using the following commands. diff --git a/auto_shell_scripting/TemplateBuilder.py b/auto_shell_scripting/TemplateBuilder.py index 1e00763..ace09ec 100755 --- a/auto_shell_scripting/TemplateBuilder.py +++ b/auto_shell_scripting/TemplateBuilder.py @@ -6,6 +6,7 @@ import string import time + @dataclass class TemplateBuilder: value: str @@ -40,14 +41,33 @@ def buildCaseStatement(self, case_statement: dict) -> str: if key == "switch": for val in value: for parameter, statement in val.items(): - switch_array += "\t{}) {};;\n".format(parameter, statement) + switch_array += "\t{}) {};;\n".format( + parameter, statement) statement_builder += "case ${} in\n".format(goal) statement_builder += switch_array statement_builder += "esac\n" return statement_builder + def buildNestedCaseStatement(self, case_statement: dict) -> str: + print("Building, case statement") + switch_array = "" + statement_builder = "" + goal = "" + for key, value in case_statement: + if key == "goal": + goal = value + if key == "switch": + for val in value: + for parameter, statement in val.items(): + switch_array += "\t\t{}) {};;\n".format( + parameter, statement) + statement_builder += "\tcase ${} in\n".format(goal) + statement_builder += switch_array + statement_builder += "\tesac\n" + return statement_builder + def buildForStatement(self, loop_statement: dict) -> str: - print("Building, loop statement") + print("Building, loop statement") statement_builder = "" for_statements = "" goal = "" @@ -73,28 +93,35 @@ def buildWhileStatement(self, loop_statement: dict) -> str: goal = value if key == "run": for k in value: - while_statements += "\t{}".format(self.iterateRun(k)) + if k.keys().__contains__('conditions'): + condition = [_v for _k, _v in k.items()][0] + while_statements += "{}".format( + self.getConditionBuilder(condition)) + else: + while_statements += "\t{}".format(self.iterateRun(k)) if while_statements: statement_builder += "while [ {} ]; do\n".format(goal) statement_builder += while_statements statement_builder += "done\n" return statement_builder - + def getConditionBuilder(self, value) -> str: template = "" for arr in value: # Get Condition Type - condition_type = [ v for k,v in arr.items() ][0] + condition_type = [v for k, v in arr.items()][0] if condition_type == "if": template += self.buildIfStatement(arr.items()) elif condition_type == "case": template += self.buildCaseStatement(arr.items()) + elif condition_type == "case.nested": + template += self.buildNestedCaseStatement(arr.items()) elif condition_type == "for": template += self.buildForStatement(arr.items()) elif condition_type == "while": template += self.buildWhileStatement(arr.items()) return template - + def buildFunction(self, function: dict) -> str: print("Building, function statement") statement_builder = "" @@ -103,19 +130,44 @@ def buildFunction(self, function: dict) -> str: for key, value in function: if key == "name": name = value - if key == "statements": - for stmt in value: + if key == "statements": + for stmt in value: if stmt.keys().__contains__('conditions'): - condition = [ v for k,v in stmt.items() ][0] + condition = [v for k, v in stmt.items()][0] statements += self.getConditionBuilder(condition) else: - statements += self.iterateRun(stmt) + statements += self.iterateRun(stmt) + if key == "control" or key == "onliner": + statements += self.buildOnliner(value) if statements: - statement_builder += "{}(){}".format(name,'{') - statement_builder += "\t" + statements + "\n" + statement_builder += "{}(){}".format(name, '{') + statement_builder += statements + "\n" statement_builder += "}\n" return statement_builder + def replaceMetaTag(self, variable: str) -> str: + variable = variable.replace('__SPACE__', ' ') + variable = variable.replace('__BEGIN__', '\"') + variable = variable.replace('__NEWLINE__', '\\n') + variable = variable.replace('__END__', '\"') + return variable + + def buildControl(self, value: dict) -> str: + line = "" + key = list(value.keys()) + values = list(value.values()) + for v in values[0]: + line += self.replaceMetaTag(v) + return "\n{} {}".format(key[0], line) + + def buildOnliner(self, value: dict) -> str: + line = "" + key = list(value.keys()) + values = list(value.values()) + for v in values[0]: + line += self.replaceMetaTag(v) + return "\n{} {}".format(key[0], line) + def iterateRun(self, template_data: dict) -> str: line_statement = "" run_type = "" @@ -130,33 +182,51 @@ def iterateRun(self, template_data: dict) -> str: elif run_type == "command_call" and key != "type": line_statement += "{} {}\n".format(key, value) except AttributeError as ae: - print("Check datasource syntax and ensure you are using the correct datatype (array, object)") + print( + "Check datasource syntax and ensure you are using the correct datatype (array, object)") print(ae) exit(1) return line_statement def getTemplateData(self, datasource: str) -> dict: template_data = {} - with open(datasource, "r") as f: - template_data = json.load(f) + try: + # Remove extension if present + datasource = datasource.replace('::', '/') + datasource = "templates/{}.json".format(datasource) + if os.path.exists(datasource): + with open(datasource, "r") as f: + template_data = json.load(f) + else: + print("\033[35mWarning: \033[33mMissing or unable to find template (name: {})\033[0m".format( + datasource)) + except Exception as e: + print( + "Error: Missing or unable to find template (name: {})".format(datasource)) return template_data def generate_template(self, template_data: dict) -> str: template = "" for key, value in template_data.items(): + if key in ["purpose"]: + print("\033[36mPURPOSE: \033[33m{}\033[0m".format(value)) # Writes the initial shell - if key in [ "shell.type" ]: + if key in ["shell.type"]: print("Shell Type is {}".format(value)) template += "#!/usr/bin/env {}\n\n".format(value) # template += "set -x\n\n" - elif key in [ "define.variables" ]: + elif key in ["define.variables"]: # Creates a lot of variables - for k,v in value.items(): - template += "{}={}\n".format(k,v) - elif key in [ "conditions" ]: + for k, v in value.items(): + template += "{}={}\n".format(k, v) + elif key in ["conditions"]: # Iterate template += self.getConditionBuilder(value) - elif key in [ "functions" ]: + elif key in ["functions"]: for arr in value: template += self.buildFunction(arr.items()) + elif key in ["control"] or key in ["command_call"]: + template += self.buildControl(value) + elif key in ["onliner"]: + template += self.buildOnliner(value) return template diff --git a/auto_shell_scripting/utils/TemplateLister.py b/auto_shell_scripting/utils/TemplateLister.py new file mode 100755 index 0000000..f395964 --- /dev/null +++ b/auto_shell_scripting/utils/TemplateLister.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3.10 + +from dataclasses import dataclass +import json +import os +import string +import time + + +@dataclass +class TemplateLister: + + def getTemplates(self, path) -> str: + results = [] + purpose = " - " + for entry in os.listdir(path): + full_path = os.path.join(path, entry) + if os.path.isdir(full_path): + results.extend(self.getTemplates(full_path)) + else: + strOutput = open(full_path,"r") + obj = json.loads(strOutput.read()) + try: + purpose += "\033[32m{}".format(obj['purpose']) + except Exception as e: + purpose = " - \033[31m{}".format('missing purpose') + pass + results.append(entry.replace('.json', purpose)) + return results + + def createMenu(self): + counter = 0 + templates = sorted(self.getTemplates("templates")) + print("\033[36m==== Available Templates: ====\033[0m") + for template in templates: + counter += 1 + print("\033[33m{}: \033[35m{}\033[0m".format(counter, template)) diff --git a/changelog.md b/changelog.md new file mode 100644 index 0000000..cde005c --- /dev/null +++ b/changelog.md @@ -0,0 +1,43 @@ +# Changelog + +All notable changes to this project will be documented in this file. + + + +## [1.0.1] - 2023-04-12 + +### Added + +- Adding setup shellscript +- More templates +- More template logic determinators + +### Changed + +- New Project logo +- Fix the standard template +- Reformatting all scripts +- Reformatting all templates + +## [1.0.0] - 2023-01-06 + +### Added + +- Initial Commit + + +[1.1.0]: https://github.com/denezt/automate-shell-scripting/compare/v1.0.0...v1.0.1 +[1.0.0]: https://github.com/denezt/automate-shell-scripting/releases/tag/v1.0.0 \ No newline at end of file diff --git a/img/logo.png b/img/logo.png index f161c45..3720a94 100644 Binary files a/img/logo.png and b/img/logo.png differ diff --git a/py2shell.py b/py2shell.py index 00aeb69..3673121 100755 --- a/py2shell.py +++ b/py2shell.py @@ -1,38 +1,62 @@ +#!/usr/bin/env python + import os import argparse from auto_shell_scripting import TemplateBuilder as TemplateBuilder +from auto_shell_scripting.utils import TemplateLister as TemplateLister # Set Program Parameters -parser = argparse.ArgumentParser(prog='py2shell', description='Tool for automating shell scripts creation') -parser.add_argument('--make-executable', action='store_true', default=False, help='Make script executable') -parser.add_argument('--exec', action='store_true', default=False, help='Make script executable') -parser.add_argument('--datasource', type=str, required=True, help='Path to JSON data source') -parser.add_argument('--output', type=str, required=True, help='Name of target output script name') +parser = argparse.ArgumentParser( + prog='py2shell', description='Tool for automating shell scripts creation') +parser.add_argument('--make-executable', action='store_true', + default=False, help='Make script executable') +parser.add_argument('--exec', action='store_true', + default=False, help='Make script executable') +parser.add_argument('--datasource', type=str, help='Path to JSON data source') +parser.add_argument('--output', type=str, + help='Name of target output script name') +parser.add_argument("--debug", action="store_true", help="Enable debug mode") +parser.add_argument("--templates", action="store_true", + help="Show available templates") + args = parser.parse_args() -print(args) + # Read the JSON file containing the template data datasource = args.datasource output = args.output +view_templates = args.templates + +if args.debug: + print(args) if '__main__' == __name__: - if output.endswith('.sh') and datasource.endswith('.json'): - templateBuilder = TemplateBuilder.TemplateBuilder(value="", collector="") - template_data = templateBuilder.getTemplateData(datasource=datasource) - # Generate the template - template = templateBuilder.generate_template(template_data) - print(template) - # Write the template to a file - if output.endswith('.sh'): - if not os.path.exists("scripts"): - os.mkdir("scripts") - with open("scripts/{}".format(output), "w") as f: - f.write(template) - if args.make_executable or args.exec: - script_name = "scripts/{}".format(output) - if os.path.isfile(script_name): - print("Making, script executeable") - try: - os.chmod("{}".format(script_name),777) - except FileNotFoundError as fnfe: - print(fnfe) - print("Template generated successfully!") + if output is not None and datasource is not None: + try: + if output.endswith('.sh'): + templateBuilder = TemplateBuilder.TemplateBuilder( + value="", collector="") + template_data = templateBuilder.getTemplateData( + datasource=datasource) + # Generate the template + template = templateBuilder.generate_template(template_data) + print(template) + # Write the template to a file + if output.endswith('.sh'): + if not os.path.exists("scripts"): + os.mkdir("scripts") + with open("scripts/{}".format(output), "w") as f: + f.write(template) + if args.make_executable or args.exec: + script_name = "scripts/{}".format(output) + if os.path.isfile(script_name): + print("Making, script executeable") + try: + os.chmod("{}".format(script_name), 777) + except FileNotFoundError as fnfe: + print(fnfe) + print("Template generated successfully!") + except AttributeError as ae: + pass + if view_templates: + t = TemplateLister.TemplateLister() + t.createMenu() diff --git a/setup.sh b/setup.sh new file mode 100755 index 0000000..a5cd76d --- /dev/null +++ b/setup.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +initialize() { + retry=2 + _local_utils=('jq') + for ((i = 0; $i < $retry; i++)); do + apt update -y + for util in ${_local_utils[*]}; do + if [ -z "$(command -v $util)" ]; then + apt-get install $util -y + fi + done + done +} + +usage() { + printf "\033[36mUSAGE:\033[0m\n" + printf "\033[35m$0 \033[33m--action=\033[32mCOMMAND\033[0m\n" + printf "\n" +} + +commands() { + printf "\033[36mCOMMANDS:\033[0m\n" + printf "\033[35mInitial Install \033[32m[ init, initialize, setup, install ]\033[0m\n" + printf "\n" +} + +help_menu() { + printf "\033[36mSetup Py2Shell\033[0m\n" + printf "\033[35mExecute Action \033[32m[ action:COMMAND, --action=COMMAND ]\033[0m\n" + printf "\n" + commands + usage + exit 0 +} + +for argv in $@; do + case $argv in + action:* | --action=*) _action=$(echo $argv | cut -d':' -f2 | cut -d'=' -f2) ;; + -h | -help | --help) help_menu ;; + esac +done + +printf "action:\t$_action\n" + +case $_action in +init | initialize | setup | install) initialize ;; +esac diff --git a/templates/basic.json b/templates/basic.json new file mode 100644 index 0000000..2e2407e --- /dev/null +++ b/templates/basic.json @@ -0,0 +1,32 @@ +{ + "purpose": "A basic BASH shell script.", + "shell.type": "bash", + "define.variables": { + "var1": "Hello", + "var2": "There", + "var3": "Bye", + "var4": "There" + }, + "onliner": { + "printf": [ + "__BEGIN__", + "${var1}", + "__SPACE__", + "${var2}", + "!", + "__NEWLINE__", + "__END__" + ] + }, + "command_call": { + "printf": [ + "__BEGIN__", + "${var3}", + "__SPACE__", + "${var4}", + "!", + "__NEWLINE__", + "__END__" + ] + } +} \ No newline at end of file diff --git a/templates/ex1/template_data.json b/templates/data/template_data.json similarity index 53% rename from templates/ex1/template_data.json rename to templates/data/template_data.json index 62355c6..29f6e28 100755 --- a/templates/ex1/template_data.json +++ b/templates/data/template_data.json @@ -1,27 +1,28 @@ { - "shell.type" : "bash", - "define.variables" : { + "purpose": "A template to test build a generic script.", + "shell.type": "bash", + "define.variables": { "a": "Hello", "b": "Bye", "c": "World", "i": 0, "option": "$1" }, - "conditions" : [ + "conditions": [ { - "type" : "if", - "goal" : "1 == 1", - "run" : [ - { - "type" :"parameter", - "printf":"$a, $b\\n" - } + "type": "if", + "goal": "1 == 1", + "run": [ + { + "type": "parameter", + "printf": "$a, $b\\n" + } ] }, { - "type" : "case", - "goal" : "option", - "switch" : [ + "type": "case", + "goal": "option", + "switch": [ { "-a": "printf 'enter statement for value (-a)\\n'" }, @@ -34,21 +35,23 @@ ] }, { - "type" : "while", - "goal" : "$i -lt 10", - "run" : [ + "type": "while", + "goal": "$i -lt 10", + "run": [ { - "type" : "parameter", + "type": "parameter", "printf": "loop...\\n" - },{ - "type" : "declare", + }, + { + "type": "declare", "i": "$((i+1))" } ] - },{ - "type" : "case", - "goal" : "b", - "switch" : [ + }, + { + "type": "case", + "goal": "b", + "switch": [ { "-a": "printf 'enter statement for value (-a)\\n'" }, @@ -61,18 +64,18 @@ ] }, { - "type" : "for", - "goal" : "i=0; $i < 10; i++", - "run" : [ + "type": "for", + "goal": "i=0; $i < 10; i++", + "run": [ { - "type" : "declare", + "type": "declare", "j": "$((j+1))" - },{ - "type" : "parameter", + }, + { + "type": "parameter", "printf": "loop $j\\n" } ] } ] -} - +} \ No newline at end of file diff --git a/templates/shift/shifting_args.json b/templates/shift/shifting_args.json new file mode 100644 index 0000000..f7daa48 --- /dev/null +++ b/templates/shift/shifting_args.json @@ -0,0 +1,59 @@ +{ + "purpose": "An example of using the shift command to pass parameters.", + "shell.type": "bash", + "define.variables": { + "a": "Hello", + "b": "Bye", + "c": "World", + "i": 0, + "option": "$1" + }, + "functions": [ + { + "name": "help_menu", + "onliner": { + "printf": [ + "__BEGIN__", + "$a", + "__SPACE__", + "$b", + "__NEWLINE__", + "__END__" + ] + }, + "control": { + "exit": [ + "0" + ] + } + } + ], + "conditions": [ + { + "type": "while", + "goal": "$# -gt 0", + "run": [ + { + "type": "parameter", + "printf": "loop...\\n" + }, + { + "conditions": [ + { + "type": "case.nested", + "goal": "1", + "switch": [ + { + "-a": "INPUT=$2;echo -ne \"$INPUT\\n\";shift 2;[ -z \"$2\" ] && exit 0" + }, + { + "*": "help_menu" + } + ] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/templates/standard.json b/templates/standard.json index 6d62dad..07e865e 100644 --- a/templates/standard.json +++ b/templates/standard.json @@ -1,6 +1,7 @@ { - "shell.type" : "bash", - "define.variables" : { + "purpose" : "Standard script with help menu, conditions and conditional loops.", + "shell.type": "bash", + "define.variables": { "var1": "Hello", "var2": "There", "var3": "Bye", @@ -9,85 +10,88 @@ "option": "$1" }, "functions": [ - { - "name":"help_menu", - "statements": [ - { - "conditions": [ - { - "type" : "if", - "goal" : "1 -gt 1", - "run" : [ + { + "name": "help_menu", + "statements": [ + { + "conditions": [ { - "type" : "parameter", - "printf":"$var1, $var2\\n" + "type": "if", + "goal": "1 -gt 1", + "run": [ + { + "type": "parameter", + "printf": "$var1, $var2\\n" + } + ] } - ] - } - ] - }, - { - "conditions": [ + ] + }, { - "type" : "if", - "goal" : "1 == 1", - "run" : [ + "conditions": [ { - "type" : "parameter", - "printf": "$var3, $var4\\n" + "type": "if", + "goal": "1 == 1", + "run": [ + { + "type": "parameter", + "printf": "$var3, $var4\\n" + } + ] } - ] - } - ] - }, - { - "type" : "command_call", - "exit": "0" - } - ] - } + ] + }, + { + "type": "command_call", + "exit": "0" + } + ] + } ], - "conditions" : [ + "conditions": [ { - "type" : "if", - "goal" : "1 == 1", - "run" : [ - { - "printf":"$var1, $var2\\n" - } + "type": "if", + "goal": "1 == 1", + "run": [ + { + "type": "parameter", + "printf": "$var1, $var2\\n" + } ] }, { - "type" : "case", - "goal" : "option", - "switch" : [ + "type": "case", + "goal": "option", + "switch": [ { "-h|-help|--help": "help_menu" } ] }, { - "type" : "while", - "goal" : "$i -lt 10", - "run" : [ + "type": "while", + "goal": "$i -lt 10", + "run": [ { - "type" : "parameter", + "type": "parameter", "printf": "loop...\\n" - },{ - "type" : "declare", + }, + { + "type": "declare", "i": "$((i+1))" } ] }, { - "type" : "for", - "goal" : "i=0; $i < 10; i++", - "run" : [ + "type": "for", + "goal": "i=0; $i < 10; i++", + "run": [ { - "type" : "declare", + "type": "declare", "j": "$((j+1))" - },{ - "type" : "parameter", + }, + { + "type": "parameter", "printf": "loop $j\\n" } ]