Skip to content

Instantly share code, notes, and snippets.

@taikedz
Created July 4, 2019 17:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save taikedz/a3d7ec718e4669c3d8e05c484b708058 to your computer and use it in GitHub Desktop.
Save taikedz/a3d7ec718e4669c3d8e05c484b708058 to your computer and use it in GitHub Desktop.
Controlled runner and argument parser

A pair of python3 scripts for importing. I wrote these to facilitate writing wrappers external commands, when replacing some shell scripts. There might be better ways to do it (including checking for libraries in lieu of commands) but in absence of that possibility (looking at you, docker-compose!), these have made things much easier...!

The arguments.py script allows loading a parser with some defaults, as well as passing your own argparse definitions to it. It then returns a usable dicitonary in which to look up items.

The runner.sh script provides a convenience set of functions for runnning external commands, as well as a dry run mode predicated on use of --dry-run from the arguments.sh script. It also accepts a simple dict mapping extra environment variables into the existing environment

Example

import runner
import arguments
import sys

my_options = {
    "--server": {"help": "the server to ping"}
}

parser = arguments.Parser(my_options)
args = parser.parse(sys.argv[1:])
myenv = {"PATH":"/home/user/.local/bin"} # Note - this will overwrite the default path, not append

runner.run(["ping", arguments["server"], "-c", "4"], addenv=myenv)

There. That was easy, right?

(C) Tai Kedzierski Provided under LGPLv3 https://www.gnu.org/licenses/lgpl-3.0.txt

# (C) Tai Kedzierski
# Provided under LGPLv3
# https://www.gnu.org/licenses/lgpl-3.0.txt
import argparse
import runner
class Parser:
def __init__(self, custom_selectors=None):
self.arg_parser = argparse.ArgumentParser()
self.addDefinition("--show-commands", {"help":"print external commands that are run", "action": "store_true"})
self.addDefinition("--config", {"help":"config file path"})
self.addDefinition("--dry-run", {"help":"do not execute commands, just list them", "action": "store_true"})
if custom_selectors:
self.addDefinitions(custom_selectors)
def addDefinition(self, argname, argparams):
self.arg_parser.add_argument(argname, **argparams)
def addDefinitions(self, paramlist):
for argname in paramlist:
self.addDefinition(argname, paramlist[argname])
def parse(self, effective_arguments):
v = vars(self.arg_parser.parse_args(effective_arguments) )
runner.setShowCommands(v["show_commands"])
return v
# (C) Tai Kedzierski
# Provided under LGPLv3
# https://www.gnu.org/licenses/lgpl-3.0.txt
import subprocess
import re
import os
show_commands = False
def run(command_array, cwd='./', dry_run=False, addenv={}, stdout=None, stderr=None, string=False):
"""
Convenience function for controlling command runs
Returns the process handler object (from subprocess.Popen) or None if dry_run is True
Arguments ------
command_array : a string array representing the external command to run
cwd: the working directory to run in
dry_run: if True, does not run. Use --show-commands to print out what would have been run
addenv: a str->str dictionary of environment variables and values to add to the runtime environment
stdout:
stderr:
use subprocess.PIPE to capture output
string: if True, treat the output as text-only. Use this if you intend to use .communicate() on the process handler
"""
cwd = os.path.abspath(cwd)
if show_commands:
print("Add env: "+"".join(
[ "\n %s=%s"%(x,addenv[x]) for x in addenv]
) )
print("CWD: "+cwd)
print("Command: "+quote(command_array) )
print("---->")
myenv = gatherEnv(addenv)
if dry_run != True:
return subprocess.Popen(command_array, cwd=cwd, env=myenv, stdout=stdout, stderr=stderr, universal_newlines=string)
def irun(command_array, cwd='./', dry_run=False, addenv={}, input=None, timeout=None, string=True):
"""
Interactive runner convenience function
Returns stdout and stderr as text pipes, unless string is False ; returns None if dry_run is True
Arguments ------
command_array : a string array representing the external command to run
cwd: the working directory to run in
dry_run: if True, does not run. Use --show-commands to print out what would have been run
addenv: a str->str dictionary of environment variables and values to add to the runtime environment
input: input string data to write to the process
timeout: see `help(subprocess.Popen.communicate)` in a python interactive session. Presumably, time to wait for process termination until giving up
string: if True, treat the output as text-only. Use this if you intend to use .communicate() on the process handler
"""
_pipe = subprocess.PIPE
ph = run(command_array, cwd=cwd, dry_run=dry_run, addenv=addenv, stdout=_pipe, stderr=_pipe, string=string)
return ph.communicate(input=input, timeout=timeout)
def quote(command_array):
new_array = []
for token in command_array:
if re.match('.*\s+', token):
new_array.append( '"%s"'%token )
else:
new_array.append(token)
return " ".join(new_array)
def gatherEnv(addenv):
myenv = os.environ.copy()
for k in addenv:
myenv[k] = addenv[k]
return myenv
def setShowCommands(show):
global show_commands
show_commands = show
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment