Commit 6883d444 authored by ∞'s avatar 💻

cli2 v2.2 support

parent 824634c4
Pipeline #7325 failed with stage
in 15 seconds
[console_scripts]
shyml = shyml:cli
shyml = shyml:cli.entry_point
......@@ -2,6 +2,7 @@
name: hello
script: echo hello $@
help: Example
---
name: install
......
......@@ -3,7 +3,6 @@ Orchestrate shell script units from a single sh.yml file.
"""
import cli2
import colorama
import os
import shlex
import subprocess
......@@ -15,45 +14,11 @@ import yaml
LOGO = f'{cli2.c.green}Sh{cli2.c.yellow}Y{cli2.c.red}ml{cli2.c.reset}'
cli = cli2.Group(__doc__)
class JobCommand:
def __init__(self, name):
self.__name__ = name
def __call__(self, path=None, job=None, *args):
"""
Render jobs defined in ./sh.yml or any sh.yml file.
"""
if not path:
yield from help()
return
if not os.path.exists(path):
print(f'{path} does not exist')
sys.exit(1)
schema = Schema.factory(path)
if not job:
yield from ls(schema)
return
if job not in schema:
yield f'{cli2.c.red}{job}{cli2.c.reset} not found in {schema.path}'
return
self.job = schema[job]
yield from self.execute()
class JobRun(JobCommand):
def execute(self):
class ConsoleScript(cli2.Group):
def run(self, *args):
fd, path = tempfile.mkstemp(prefix='.shyml', dir='.')
with open(path, 'w') as f:
for line in self.job.schema.script(self.job.name):
f.write(line + '\n')
f.write(self.schema.script(self.current))
shell = os.getenv('shell', '/bin/bash -eux')
shell_arg = shell.split(' ') + [path]
......@@ -61,7 +26,7 @@ class JobRun(JobCommand):
# 1337 ArGv InJeC710n H4ck: proxying argv from: shyml sh.yml JOB foobar
# And: proxying argv from: ./sh.yml JOB foobar
# Will cause $1 to be "foobar" in the script content of JOB
shell_arg += cli.argv[3:]
shell_arg += list(args)
proc = subprocess.Popen(
shell_arg,
......@@ -73,86 +38,62 @@ class JobRun(JobCommand):
os.unlink(path)
sys.exit(proc.returncode)
def help(self, *args, **kwargs):
if args and args[0] in self.schema:
return self.schema[args[0]].help
return super().help(*args, **kwargs)
class JobDebug(JobCommand):
def execute(self):
yield from self.job.schema.script(self.job.name)
run = JobRun('run')
cli.add(run)
debug = JobDebug('debug')
cli.add(debug)
def debug(self, name):
return self.schema[name].script()
def __call__(self, *argv):
if not argv:
print('Missing path to sh.yml argument')
sys.exit(1)
@cli.cmd
def ls(schema, prefix=None):
if schema:
yield f'{LOGO} has found the following jobs:'
yield ''
if not os.path.exists(argv[0]):
print('File not found: ' + argv[0])
sys.exit(1)
width = len(max(schema.keys(), key=len)) + 1
self.add(self.help, doc='Show help for command')
self.add(self.debug, doc='Show script for command')
for name in sorted(schema.keys()):
display = name
job = schema[name]
line = ' '
self.schema = Schema.factory(argv[0])
for name, job in self.schema.items():
self.add(self.run, name=name, doc=job.help)
if len(argv) > 1:
self.current = argv[1]
argv = argv[1:]
return super().__call__(*argv)
line += job.color_code
line += name
line += cli2.c.reset
line += (width - len(display)) * ' '
line += job.help.split('\n')[0]
cli = ConsoleScript(__doc__)
yield line
yield ''
yield 'Print out script of a job: ./sh.yml debug JOB'
yield 'Print out help of a job: ./sh.yml help JOB'
else:
yield f'{cli2.c.red}Could not parse{cli2.c.reset}: {schema.path} !'
class JobCommand:
def __init__(self, name):
self.__name__ = name
@cli.cmd
def help(path, job=None):
"""
Show help for a job.
def __call__(self, path=None, job=None, *args):
"""
Render jobs defined in ./sh.yml or any sh.yml file.
"""
if not path:
return help()
To get the list of jobs that you can get help for, run shyml without
argument.
"""
schema = Schema.factory(path)
if not os.path.exists(path):
return print(f'{path} does not exist')
if not schema:
yield 'sh.yml not found, please start with README'
return
schema = Schema.factory(path)
if not job:
yield 'Job not specified, showing job list'
yield from ls(schema)
return
if not job:
return ls(schema)
if job not in schema:
yield f'{cli2.c.red}{job}{cli2.c.reset} not found in {schema.path}'
return
if job not in schema:
return f'{cli2.c.red}{job}{cli2.c.reset} not found in {schema.path}'
out = [
' '.join([
'Showing help for',
''.join([
cli2.c.green,
job,
cli2.c.reset,
]),
]),
schema[job].help,
f'Output generated bash job:',
f'{cli2.c.green}shyml debug {job}{cli2.c.yellow}',
'',
f'Run the generated script for this job:',
f'{cli2.c.green}shyml {job}{cli2.c.reset}',
]
yield '\n'.join(out)
self.job = schema[job]
return self.execute()
class Job(dict):
......@@ -169,7 +110,7 @@ class Job(dict):
job.requires = [job.requires]
job.help = doc.get('help', '')
job.color = doc.get('color', 'yellow')
job.color_code = getattr(colorama.Fore, job.color.upper(), cli2.c.reset)
job.color_code = getattr('cli2.c', job.color.upper(), cli2.c.reset)
return job
def visit(self, schema):
......@@ -190,6 +131,7 @@ class Job(dict):
self.parent = job
def script(self):
out = []
env = dict()
for name in self.requires:
if self.schema[name].env:
......@@ -197,10 +139,10 @@ class Job(dict):
env.update(self.env)
for key, value in env.items():
yield ''.join(['export ', str(key), '=', shlex.quote(str(value))])
out.append(''.join(['export ', str(key), '=', shlex.quote(str(value))]))
for name in self.requires:
yield 'shyml_' + name
out.append('shyml_' + name)
script = self.get('script')
if not script:
......@@ -211,7 +153,8 @@ class Job(dict):
for chunk in script:
if chunk:
yield chunk
out.append(chunk)
return '\n'.join(out)
class Schema(dict):
......@@ -222,23 +165,23 @@ class Schema(dict):
self.path = path
def script(self, *jobs):
out = []
for job in self.values():
yield 'shyml_' + job.name + '() {'
out.append('shyml_' + job.name + '() {')
if job.help:
for line in job.help.strip().split('\n'):
yield f' # {line}'
for line in self[job.name].script():
yield f' {line}'
yield '}'
out.append(f' # {line}')
out.append(self[job.name].script().replace('\n', ' \n'))
out.append('}')
for hook in self.hooks['before']:
if hook.name == jobs[0]:
continue
yield 'shyml_' + hook.name
out.append('shyml_' + hook.name)
for name in jobs:
if name not in self:
print(''.join([
out.append(''.join([
cli2.c.red,
'Job not found: ',
cli2.c.reset,
......@@ -251,7 +194,9 @@ class Schema(dict):
'\n'.join([f'- {i}' for i in sorted(self.keys())]),
]))
sys.exit(1)
yield 'shyml_' + name + ' "$@"'
out.append('shyml_' + name + ' "$@"')
return '\n'.join(out)
def parse(self):
with open(self.path, 'r') as f:
......@@ -284,5 +229,3 @@ class Schema(dict):
self.parse()
return self
import cli2
from cli2.test import autotest
import pytest
@pytest.mark.parametrize('path,cmd', [
('tests/hello_help.txt', 'shyml sh.yml help hello'),
('tests/hello_debug.txt', 'shyml sh.yml debug hello'),
('tests/install_help.txt', 'shyml sh.yml help install'),
('tests/typo.txt', 'shyml sh.yml foo'),
('tests/typo_help.txt', 'shyml help sh.yml foo'),
('tests/typo_debug.txt', 'shyml debug sh.yml foo'),
('tests/hello_help.txt', './sh.yml help hello'),
('tests/hello_debug.txt', './sh.yml debug hello'),
('tests/install_help.txt', './sh.yml help install'),
('tests/test_debug.txt', './sh.yml debug test'),
('tests/test_args.txt', './sh.yml hello test'),
])
def test_shyml(path, cmd):
cli2.autotest(path, cmd)
autotest(path, cmd)
command: shyml sh.yml debug hello
command: ./sh.yml debug hello
retcode: 0
stdout:
shyml_hello() {
echo hello $@
}
shyml_install() {
# Setup and activate a venv for a python executable
#
# If venv=none, it will not do any venv.
# If venv=user, it will use pip install --user.
if [ "${venv-}" = "user" ]; then
pip_install="pip install --user"
elif [ "${venv-}" != "none" ]; then
export python="${python-python3}"
export path="${path-.venv.$python}"
test -d $path || virtualenv --python=$python $path
set +eux; echo activating $path; source $path/bin/activate; set -eux
fi
${pip_install-pip install} -Ue .${1-}
}
shyml_test() {
# Test sh.yml
shyml_install [test] && pytest -v $@
}
shyml_testrewrite() {
# Test sh.yml
FIXTURE_REWRITE=1 shyml_test
}
shyml_hello "$@"
echo hello $@
command: shyml sh.yml help hello
command: ./sh.yml help hello
retcode: 0
stdout:
Showing help for hello
Output generated bash job:
shyml debug hello
Run the generated script for this job:
shyml hello
Example
command: shyml sh.yml help install
command: ./sh.yml help install
retcode: 0
stdout:
Showing help for install
Setup and activate a venv for a python executable
If venv=none, it will not do any venv.
If venv=user, it will use pip install --user.
Output generated bash job:
shyml debug install
Run the generated script for this job:
shyml install
command: ./sh.yml hello test
retcode: 0
stdout:
hello test
stderr:
+ shyml_hello test
+ echo hello test
command: ./sh.yml debug test
retcode: 0
stdout:
shyml_install [test] && pytest -v $@
command: shyml sh.yml foo
retcode: 0
stdout:
foo not found in sh.yml
command: shyml debug sh.yml foo
retcode: 0
stdout:
foo not found in sh.yml
command: shyml help sh.yml foo
retcode: 0
stdout:
foo not found in sh.yml
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment