Add missing changes
This commit is contained in:
parent
eea89f26cc
commit
451ffc2667
3 changed files with 270 additions and 301 deletions
196
blend
196
blend
|
@ -1,32 +1,38 @@
|
|||
#!/usr/bin/env python3
|
||||
# Copyright (C) 2023 Rudra Saraswat
|
||||
#
|
||||
#
|
||||
# This file is part of blend.
|
||||
#
|
||||
#
|
||||
# blend is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
#
|
||||
# blend is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with blend. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import os, sys, getpass, time
|
||||
import os
|
||||
import sys
|
||||
import glob
|
||||
import time
|
||||
import shutil
|
||||
import socket
|
||||
import getpass
|
||||
import pexpect
|
||||
import argparse
|
||||
import subprocess
|
||||
|
||||
__version = '2.0.0'
|
||||
|
||||
### Colors
|
||||
# Colors
|
||||
|
||||
|
||||
class colors:
|
||||
reset = '\033[0m'
|
||||
bold = '\033[01m'
|
||||
|
@ -63,17 +69,22 @@ class colors:
|
|||
cyan = '\033[46m'
|
||||
lightgrey = '\033[47m'
|
||||
|
||||
### END
|
||||
# END
|
||||
|
||||
# Helper functions
|
||||
|
||||
### Helper functions
|
||||
|
||||
def info(msg):
|
||||
print (colors.bold + colors.fg.cyan + '>> i: ' + colors.reset + colors.bold + msg + colors.reset)
|
||||
print(colors.bold + colors.fg.cyan + '>> i: ' +
|
||||
colors.reset + colors.bold + msg + colors.reset)
|
||||
|
||||
|
||||
def error(err):
|
||||
print (colors.bold + colors.fg.red + '>> e: ' + colors.reset + colors.bold + err + colors.reset)
|
||||
print(colors.bold + colors.fg.red + '>> e: ' +
|
||||
colors.reset + colors.bold + err + colors.reset)
|
||||
|
||||
# END
|
||||
|
||||
### END
|
||||
|
||||
distro_map = {
|
||||
'arch': 'docker.io/library/archlinux',
|
||||
|
@ -84,6 +95,7 @@ distro_map = {
|
|||
|
||||
default_distro = 'arch'
|
||||
|
||||
|
||||
def get_distro():
|
||||
try:
|
||||
return distro_map[args.distro]
|
||||
|
@ -91,9 +103,10 @@ def get_distro():
|
|||
error(f"{args.distro} isn't supported by blend.")
|
||||
exit()
|
||||
|
||||
|
||||
def list_containers():
|
||||
_list = subprocess.run(['podman', 'ps', '-a', '--no-trunc', '--size', '--format',
|
||||
'{{.Names}}:{{.Mounts}}'], stdout=subprocess.PIPE).stdout.decode('utf-8').strip()
|
||||
'{{.Names}}:{{.Mounts}}'], stdout=subprocess.PIPE).stdout.decode('utf-8').strip()
|
||||
if len(_list) == 0:
|
||||
info('No containers. Create one by installing a package (`blend install hello`), or manually create one (`blend create-container arch`).')
|
||||
else:
|
||||
|
@ -103,21 +116,25 @@ def list_containers():
|
|||
print(f"{colors.bold}{i}.{colors.reset} {container.split(':')[0]}")
|
||||
return False
|
||||
|
||||
|
||||
def check_container(name):
|
||||
_list = subprocess.run(['podman', 'ps', '-a', '--no-trunc', '--size', '--format',
|
||||
'{{.Names}}:{{.Mounts}}'], stdout=subprocess.PIPE).stdout.decode('utf-8').strip()
|
||||
'{{.Names}}:{{.Mounts}}'], stdout=subprocess.PIPE).stdout.decode('utf-8').strip()
|
||||
for container in _list.splitlines(keepends=False):
|
||||
if ('blend' in container.split(':')[1] or 'distrobox' in container.split(':')[1]) and name.strip() == container.split(':')[0]:
|
||||
return True
|
||||
return True
|
||||
return False
|
||||
|
||||
def check_container_status(name):
|
||||
|
||||
def check_container_status(name):
|
||||
return host_get_output("podman inspect --type container " + name + " --format \"{{.State.Status}}\"")
|
||||
|
||||
def core_start_container(name):
|
||||
subprocess.call(['podman', 'start', name], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
|
||||
start_time = time.time() - 1000 # workaround
|
||||
def core_start_container(name):
|
||||
subprocess.call(['podman', 'start', name],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
|
||||
start_time = time.time() - 1000 # workaround
|
||||
if check_container_status(name) != 'running':
|
||||
print('')
|
||||
error('the entry point failed to run; try again later')
|
||||
|
@ -125,12 +142,14 @@ def core_start_container(name):
|
|||
subprocess.call(['podman', 'logs', '--since', str(start_time), name])
|
||||
exit(1)
|
||||
|
||||
logproc = pexpect.spawn('podman', args=['logs', '-f', '--since', str(start_time), name], timeout=300)
|
||||
logproc = pexpect.spawn(
|
||||
'podman', args=['logs', '-f', '--since', str(start_time), name], timeout=300)
|
||||
logproc.logfile_read = sys.stdout.buffer
|
||||
|
||||
logproc.expect('Completed container setup.')
|
||||
logproc.terminate()
|
||||
|
||||
|
||||
def core_create_container():
|
||||
name = args.container_name
|
||||
distro = args.distro
|
||||
|
@ -145,32 +164,39 @@ def core_create_container():
|
|||
podman_command.extend(['--network', 'host'])
|
||||
podman_command.extend(['--security-opt', 'label=disable'])
|
||||
podman_command.extend(['--user', 'root:root', '--pid', 'host'])
|
||||
podman_command.extend(['--label', 'manager=blend']) # identify as blend container
|
||||
# identify as blend container
|
||||
podman_command.extend(['--label', 'manager=blend'])
|
||||
|
||||
# Env variables
|
||||
podman_command.extend(['--env', 'HOME=' + os.path.expanduser('~')])
|
||||
podman_command.extend(['--env', 'CONTAINER_NAME=' + name])
|
||||
|
||||
# Volumes
|
||||
podman_command.extend(['--volume', '/:/run/host:rslave'])
|
||||
podman_command.extend(['--volume', '/tmp:/tmp:rslave'])
|
||||
podman_command.extend(['--volume', f"{os.path.expanduser('~')}:{os.path.expanduser('~')}:rslave"])
|
||||
podman_command.extend(['--volume', f"/run/user/{os.geteuid()}:/run/user/{os.geteuid()}:rslave"])
|
||||
podman_command.extend(
|
||||
['--volume', f"{os.path.expanduser('~')}:{os.path.expanduser('~')}:rslave"])
|
||||
podman_command.extend(
|
||||
['--volume', f"/run/user/{os.geteuid()}:/run/user/{os.geteuid()}:rslave"])
|
||||
|
||||
# Volumes (config files)
|
||||
podman_command.extend(['--volume', f"/etc/hosts:/etc/hosts:ro"])
|
||||
podman_command.extend(['--volume', f"/etc/localtime:/etc/localtime:ro"])
|
||||
podman_command.extend(['--volume', f"/etc/resolv.conf:/etc/resolv.conf:ro"])
|
||||
podman_command.extend(
|
||||
['--volume', f"/etc/resolv.conf:/etc/resolv.conf:ro"])
|
||||
|
||||
# Volumes (files and tools)
|
||||
podman_command.extend(['--volume', '/usr/bin/init-blend:/usr/bin/init-blend:ro',
|
||||
'--entrypoint', '/usr/bin/init-blend']) # our entrypoint
|
||||
podman_command.extend(['--volume', '/usr/bin/host-blend:/usr/bin/host-blend:ro']) # and the tool to run commands on the host
|
||||
'--entrypoint', '/usr/bin/init-blend']) # our entrypoint
|
||||
# and the tool to run commands on the host
|
||||
podman_command.extend(
|
||||
['--volume', '/usr/bin/host-blend:/usr/bin/host-blend:ro'])
|
||||
podman_command.extend(['--volume', '/var/log/journal'])
|
||||
|
||||
podman_command.extend(['--mount', 'type=devpts,destination=/dev/pts',
|
||||
'--userns', 'keep-id',
|
||||
'--annotation', 'run.oci.keep_original_groups=1'])
|
||||
|
||||
|
||||
podman_command.extend([get_distro()])
|
||||
|
||||
# User (for init-blend)
|
||||
|
@ -189,17 +215,24 @@ def core_create_container():
|
|||
|
||||
core_start_container(name)
|
||||
|
||||
core_get_output = lambda cmd: subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout.decode('UTF-8').strip()
|
||||
|
||||
host_get_output = lambda cmd: subprocess.run(['bash', '-c', cmd],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout.decode('UTF-8').strip()
|
||||
def core_get_output(cmd): return subprocess.run(
|
||||
cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout.decode('UTF-8').strip()
|
||||
|
||||
|
||||
def host_get_output(cmd): return subprocess.run(['bash', '-c', cmd],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout.decode('UTF-8').strip()
|
||||
|
||||
|
||||
def core_get_retcode(cmd): return subprocess.run(['podman', 'exec', '--user', getpass.getuser(), '-it', args.container_name, 'bash', '-c', cmd],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode
|
||||
|
||||
core_get_retcode = lambda cmd: subprocess.run(['podman', 'exec', '--user', getpass.getuser(), '-it', args.container_name, 'bash', '-c', cmd],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode
|
||||
|
||||
def core_run_container(cmd):
|
||||
if os.getcwd() == os.path.expanduser('~') or os.getcwd().startswith(os.path.expanduser('~') + '/'):
|
||||
subprocess.call(['podman', 'exec', '--user', getpass.getuser(), '-w', os.getcwd(), '-it', args.container_name, 'bash', '-c', cmd])
|
||||
if os.getcwd() == os.path.expanduser('~') or os.getcwd().startswith(os.path.expanduser('~') + '/'):
|
||||
subprocess.call(['podman', 'exec', '--user', getpass.getuser(),
|
||||
'-w', os.getcwd(), '-it', args.container_name, 'bash', '-c', cmd])
|
||||
|
||||
|
||||
def core_install_pkg(pkg):
|
||||
if args.distro == 'fedora-rawhide':
|
||||
|
@ -210,8 +243,10 @@ def core_install_pkg(pkg):
|
|||
elif args.distro == 'arch':
|
||||
if core_get_retcode('[ -f /usr/bin/yay ]') != 0:
|
||||
core_run_container('sudo pacman -Sy')
|
||||
core_run_container('sudo pacman --noconfirm -Syu --needed git base-devel')
|
||||
core_run_container('TEMP_DIR="$(mktemp -d)"; cd "${TEMP_DIR}"; git clone https://aur.archlinux.org/yay.git; cd yay; makepkg --noconfirm -si; rm -rf "${TEMP_DIR}"')
|
||||
core_run_container(
|
||||
'sudo pacman --noconfirm -Syu --needed git base-devel')
|
||||
core_run_container(
|
||||
'TEMP_DIR="$(mktemp -d)"; cd "${TEMP_DIR}"; git clone https://aur.archlinux.org/yay.git; cd yay; makepkg --noconfirm -si; rm -rf "${TEMP_DIR}"')
|
||||
core_run_container(f'yay -Sy')
|
||||
if args.noconfirm == True:
|
||||
core_run_container(f'yay --noconfirm -Syu {pkg}')
|
||||
|
@ -224,6 +259,7 @@ def core_install_pkg(pkg):
|
|||
else:
|
||||
core_run_container(f'sudo apt-get install {pkg}')
|
||||
|
||||
|
||||
def core_remove_pkg(pkg):
|
||||
if args.distro == 'fedora-rawhide':
|
||||
if args.noconfirm == True:
|
||||
|
@ -242,6 +278,7 @@ def core_remove_pkg(pkg):
|
|||
core_run_container(f'sudo apt-get purge {pkg}')
|
||||
core_run_container(f'sudo apt-get autoremove --purge -y {pkg}')
|
||||
|
||||
|
||||
def core_search_pkg(pkg):
|
||||
if args.distro == 'fedora-rawhide':
|
||||
core_run_container(f'dnf search {pkg}')
|
||||
|
@ -252,6 +289,7 @@ def core_search_pkg(pkg):
|
|||
core_run_container(f'sudo apt-get update')
|
||||
core_run_container(f'apt-cache search {pkg}')
|
||||
|
||||
|
||||
def core_show_pkg(pkg):
|
||||
if args.distro == 'fedora-rawhide':
|
||||
core_run_container(f'dnf info {pkg}')
|
||||
|
@ -262,6 +300,7 @@ def core_show_pkg(pkg):
|
|||
core_run_container(f'sudo apt-get update')
|
||||
core_run_container(f'apt-cache show {pkg}')
|
||||
|
||||
|
||||
def install_blend():
|
||||
if len(args.pkg) == 0:
|
||||
error('no packages to install')
|
||||
|
@ -272,6 +311,7 @@ def install_blend():
|
|||
core_create_container()
|
||||
core_install_pkg(pkg)
|
||||
|
||||
|
||||
def remove_blend():
|
||||
if len(args.pkg) == 0:
|
||||
error('no packages to remove')
|
||||
|
@ -282,6 +322,7 @@ def remove_blend():
|
|||
error(f"container {args.container_name} doesn't exist")
|
||||
core_remove_pkg(pkg)
|
||||
|
||||
|
||||
def search_blend():
|
||||
if len(args.pkg) == 0:
|
||||
error('no packages to search for')
|
||||
|
@ -291,6 +332,7 @@ def search_blend():
|
|||
error(f"container {args.container_name} doesn't exist")
|
||||
core_search_pkg(pkg)
|
||||
|
||||
|
||||
def show_blend():
|
||||
if len(args.pkg) == 0:
|
||||
error('no packages to show')
|
||||
|
@ -301,6 +343,7 @@ def show_blend():
|
|||
error(f"container {args.container_name} doesn't exist")
|
||||
core_show_pkg(pkg)
|
||||
|
||||
|
||||
def sync_blends():
|
||||
if args.distro == 'fedora-rawhide':
|
||||
core_run_container(f'dnf makecache')
|
||||
|
@ -309,6 +352,7 @@ def sync_blends():
|
|||
elif args.distro.startswith('ubuntu-'):
|
||||
core_run_container(f'sudo apt-get update')
|
||||
|
||||
|
||||
def update_blends():
|
||||
if args.distro == 'fedora-rawhide':
|
||||
if args.noconfirm == True:
|
||||
|
@ -329,38 +373,42 @@ def update_blends():
|
|||
else:
|
||||
error(f'distribution {args.distro} is not supported')
|
||||
|
||||
|
||||
def enter_container():
|
||||
if os.environ.get('BLEND_NO_CHECK') == None:
|
||||
if not check_container(args.container_name):
|
||||
core_create_container()
|
||||
if check_container_status(args.container_name) != 'running':
|
||||
core_start_container(args.container_name)
|
||||
podman_args = []
|
||||
podman_args = ['--env', 'LC_ALL=C.UTF-8']
|
||||
sudo = []
|
||||
if os.environ.get('SUDO_USER') == None:
|
||||
podman_args = ['--user', getpass.getuser()]
|
||||
else:
|
||||
sudo = ['sudo', '-u', os.environ.get('SUDO_USER'), f'PATH={os.path.expanduser("~/.local/share/bin/blend_bin")}:/usr/bin']
|
||||
sudo = ['sudo', '-u', os.environ.get(
|
||||
'SUDO_USER'), f'PATH={os.path.expanduser("~/.local/share/bin/blend_bin")}:/usr/bin']
|
||||
for name, val in os.environ.items():
|
||||
if name not in ['LANG', 'LC_CTYPE', 'PATH', 'HOST', 'HOSTNAME', 'SHELL'] and not name.startswith('_'):
|
||||
if name not in ['LANG', 'LC_CTYPE', 'LC_ALL', 'PATH', 'HOST', 'HOSTNAME', 'SHELL'] and not name.startswith('_'):
|
||||
podman_args.append('--env')
|
||||
podman_args.append(name + '=' + val)
|
||||
if os.environ.get('BLEND_COMMAND') == None or os.environ.get('BLEND_COMMAND') == '':
|
||||
if args.pkg == []:
|
||||
if os.getcwd() == os.path.expanduser('~') or os.getcwd().startswith(os.path.expanduser('~') + '/'):
|
||||
exit(subprocess.call([*sudo, 'podman', 'exec', *podman_args, '-w', os.getcwd(), '-it', args.container_name, 'bash']))
|
||||
exit(subprocess.call([*sudo, 'podman', 'exec', *podman_args,
|
||||
'-w', os.getcwd(), '-it', args.container_name, 'bash']))
|
||||
else:
|
||||
exit(subprocess.call([*sudo, 'podman', 'exec', *podman_args, '-w', '/run/host' + os.getcwd(), '-it', args.container_name, 'bash']))
|
||||
exit(subprocess.call([*sudo, 'podman', 'exec', *podman_args, '-w',
|
||||
'/run/host' + os.getcwd(), '-it', args.container_name, 'bash']))
|
||||
else:
|
||||
if os.getcwd() == os.path.expanduser('~') or os.getcwd().startswith(os.path.expanduser('~') + '/'):
|
||||
exit(subprocess.call([*sudo, 'podman', 'exec', *podman_args, '-w', os.getcwd(), '-it', args.container_name, *args.pkg]))
|
||||
exit(subprocess.call([*sudo, 'podman', 'exec', *podman_args,
|
||||
'-w', os.getcwd(), '-it', args.container_name, *args.pkg]))
|
||||
else:
|
||||
exit(subprocess.call([*sudo, 'podman', 'exec', *podman_args, '-w', '/run/host' + os.getcwd(), '-it', args.container_name, *args.pkg]))
|
||||
exit(subprocess.call([*sudo, 'podman', 'exec', *podman_args, '-w',
|
||||
'/run/host' + os.getcwd(), '-it', args.container_name, *args.pkg]))
|
||||
else:
|
||||
if os.getcwd() == os.path.expanduser('~') or os.getcwd().startswith(os.path.expanduser('~') + '/'):
|
||||
exit(subprocess.call([*sudo, 'podman', 'exec', *podman_args, '-w', os.getcwd(), '-it', args.container_name, 'bash', '-c', os.environ.get('BLEND_COMMAND')]))
|
||||
exit(subprocess.call([*sudo, 'podman', 'exec', *podman_args, '-w', os.getcwd(
|
||||
), '-it', args.container_name, 'bash', '-c', os.environ.get('BLEND_COMMAND')]))
|
||||
else:
|
||||
exit(subprocess.call([*sudo, 'podman', 'exec', *podman_args, '-w', '/run/host' + os.getcwd(), '-it', args.container_name, 'bash']))
|
||||
exit(subprocess.call([*sudo, 'podman', 'exec', *podman_args, '-w',
|
||||
'/run/host' + os.getcwd(), '-it', args.container_name, 'bash']))
|
||||
|
||||
|
||||
def create_container():
|
||||
for container in args.pkg:
|
||||
|
@ -369,15 +417,22 @@ def create_container():
|
|||
args.distro = container
|
||||
core_create_container()
|
||||
|
||||
|
||||
def remove_container():
|
||||
for container in args.pkg:
|
||||
info(f'removing container {container}')
|
||||
subprocess.run(['podman', 'stop', container], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
|
||||
subprocess.run(['podman', 'rm', '-f', container], stdout=subprocess.DEVNULL)
|
||||
subprocess.run(['podman', 'stop', container],
|
||||
stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
|
||||
subprocess.run(['podman', 'rm', '-f', container],
|
||||
stdout=subprocess.DEVNULL)
|
||||
for bin in os.listdir(os.path.expanduser('~/.local/bin/blend_bin')):
|
||||
if bin.endswith(f'.{container}'):
|
||||
os.remove(os.path.join(os.path.expanduser('~/.local/bin/blend_bin'), bin))
|
||||
|
||||
|
||||
def start_containers():
|
||||
_list = subprocess.run(['podman', 'ps', '-a', '--no-trunc', '--size', '--format',
|
||||
'{{.Names}}:{{.Mounts}}'], stdout=subprocess.PIPE).stdout.decode('utf-8').strip()
|
||||
'{{.Names}}:{{.Mounts}}'], stdout=subprocess.PIPE).stdout.decode('utf-8').strip()
|
||||
if len(_list) == 0:
|
||||
info('No containers. Create one by installing a package (`blend install hello`), or manually create one (`blend create-container -d arch`).')
|
||||
for container in _list.splitlines(keepends=False):
|
||||
|
@ -385,6 +440,7 @@ def start_containers():
|
|||
info(f'starting container {container}')
|
||||
subprocess.call(['podman', 'start', container])
|
||||
|
||||
|
||||
if shutil.which('podman') is None:
|
||||
error("podman isn't installed, which is a hard dep")
|
||||
exit(1)
|
||||
|
@ -407,21 +463,27 @@ epilog = f'''
|
|||
|
||||
parser = argparse.ArgumentParser(description=description, usage=argparse.SUPPRESS,
|
||||
epilog=epilog, formatter_class=argparse.RawTextHelpFormatter)
|
||||
command_map = { 'enter': enter_container,
|
||||
'exec': enter_container,
|
||||
'create-container': core_create_container,
|
||||
'remove-container': remove_container,
|
||||
'list-containers': list_containers,
|
||||
'start-containers': start_containers,
|
||||
'sync': sync_blends,
|
||||
'help': 'help',
|
||||
'version': 'version' }
|
||||
parser.add_argument('command', choices=command_map.keys(), help=argparse.SUPPRESS)
|
||||
parser.add_argument('pkg', action='store', type=str, nargs='*', help=argparse.SUPPRESS)
|
||||
parser.add_argument('-cn', '--container-name', action='store', nargs=1, metavar='CONTAINER NAME', help=argparse.SUPPRESS)
|
||||
parser.add_argument('-y', '--noconfirm', action='store_true', help=argparse.SUPPRESS)
|
||||
parser.add_argument('-d', '--distro', action='store', nargs=1, metavar='DISTRO', help=argparse.SUPPRESS)
|
||||
parser.add_argument('-v', '--version', action='version', version=f'%(prog)s {__version}', help=argparse.SUPPRESS)
|
||||
command_map = {'enter': enter_container,
|
||||
'exec': enter_container,
|
||||
'create-container': core_create_container,
|
||||
'remove-container': remove_container,
|
||||
'list-containers': list_containers,
|
||||
'start-containers': start_containers,
|
||||
'sync': sync_blends,
|
||||
'help': 'help',
|
||||
'version': 'version'}
|
||||
parser.add_argument('command', choices=command_map.keys(),
|
||||
help=argparse.SUPPRESS)
|
||||
parser.add_argument('pkg', action='store', type=str,
|
||||
nargs='*', help=argparse.SUPPRESS)
|
||||
parser.add_argument('-cn', '--container-name', action='store',
|
||||
nargs=1, metavar='CONTAINER NAME', help=argparse.SUPPRESS)
|
||||
parser.add_argument('-y', '--noconfirm',
|
||||
action='store_true', help=argparse.SUPPRESS)
|
||||
parser.add_argument('-d', '--distro', action='store', nargs=1,
|
||||
metavar='DISTRO', help=argparse.SUPPRESS)
|
||||
parser.add_argument('-v', '--version', action='version',
|
||||
version=f'%(prog)s {__version}', help=argparse.SUPPRESS)
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
parser.print_help()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue