...
 
Commits (55)
stages:
- test
pytest:
image: python:3.7
stage: test
script:
- pip install pipenv
- pipenv install --system --dev
- pytest b5/tests -v
...@@ -12,6 +12,8 @@ packaging = ">=16.0" ...@@ -12,6 +12,8 @@ packaging = ">=16.0"
[dev-packages] [dev-packages]
"e1839a8" = {path = ".", editable = true} "e1839a8" = {path = ".", editable = true}
pylint = ">=2.3.1"
pytest = ">=5.3.2"
[requires] [requires]
python_version = "3.7" python_version = "3.7"
This diff is collapsed.
import argparse
import os import os
import termcolor
import sys import sys
from .lib.state import State import termcolor
from .lib.module import load_module
from .lib.argumentparser import ExecuteArgumentParser
from .exceptions import B5ExecutionError from .exceptions import B5ExecutionError
from .lib.module import load_module
from .lib.state import State
def main(): def main():
try: try:
parser = argparse.ArgumentParser( parser = ExecuteArgumentParser('b5-execute', 'b5-execute is not intended to be called directly!')
prog='b5-execute', parser.add_arguments()
formatter_class=argparse.ArgumentDefaultsHelpFormatter, args = parser.parse()
description='b5-execute is not intended to be called directly!',
)
parser.add_argument(
'--state-file', nargs='?',
dest='state_file',
)
parser.add_argument(
'--module', nargs='?',
dest='module',
)
parser.add_argument(
'--method', nargs='?',
dest='method',
)
parser.add_argument(
'--args', nargs=argparse.REMAINDER,
dest='args'
)
args = parser.parse_args()
if not args.state_file or not args.module or not args.method: if not args.state_file or not args.module or not args.method:
raise B5ExecutionError('b5-execute is not intended to be called directly!') raise B5ExecutionError('b5-execute is not intended to be called directly!')
state = State.load(open(args.state_file, 'rb')) state = State.load(open(args.state_file, 'rb'))
if not 'modules' in state.config: if 'modules' not in state.config:
raise B5ExecutionError('No modules defined') raise B5ExecutionError('No modules defined')
if not args.module in state.config['modules']: if args.module not in state.config['modules']:
raise B5ExecutionError('Module not available') raise B5ExecutionError('Module not available')
module = load_module(state, args.module) module = load_module(state, args.module)
...@@ -49,6 +31,6 @@ def main(): ...@@ -49,6 +31,6 @@ def main():
os.chdir(state.run_path) os.chdir(state.run_path)
method(state, args.args) method(state, args.args)
except B5ExecutionError as e: except B5ExecutionError as error:
termcolor.cprint(str(e), 'red') termcolor.cprint(str(error), 'red')
sys.exit(1) sys.exit(1)
import argparse
import os import os
import shutil import shutil
import termcolor
import subprocess import subprocess
import sys import sys
import re import termcolor
from .lib.skeleton import Skeleton
from .lib.argumentparser import InitArgumentParser
from .exceptions import B5ExecutionError from .exceptions import B5ExecutionError
NON_URL_SKELETON = re.compile('^[A-Za-z0-9_-]+$')
def _run_cmd(cmd, error='Command execution failed, see above'): def _run_cmd(cmd, error='Command execution failed, see above'):
try: try:
subprocess.run( subprocess.run(
...@@ -26,40 +23,21 @@ def _run_cmd(cmd, error='Command execution failed, see above'): ...@@ -26,40 +23,21 @@ def _run_cmd(cmd, error='Command execution failed, see above'):
def main(): def main():
try: try:
parser = argparse.ArgumentParser( parser = InitArgumentParser('b5-init', 'b5-init might be used to setup new projects')
prog='b5-init', parser.add_arguments()
formatter_class=argparse.ArgumentDefaultsHelpFormatter, args = parser.parse()
description='b5-init might be used to setup new projects',
)
parser.add_argument(
'-s', '--skeleton', nargs='?',
dest='skeleton', default='basic'
)
parser.add_argument(
'-b', '--branch', nargs='?',
dest='branch',
)
parser.add_argument(
dest='path'
)
args = parser.parse_args()
skeleton = args.skeleton skeleton = Skeleton(args.skeleton)
branch = args.branch branch = args.branch
path = args.path path = args.path
if NON_URL_SKELETON.match(skeleton):
skeleton_url = 'https://git.team23.de/build/b5-skel-{skeleton}.git'.format(skeleton=skeleton)
else:
skeleton_url = skeleton
full_path = os.path.realpath(os.path.join(os.getcwd(), path)) full_path = os.path.realpath(os.path.join(os.getcwd(), path))
os.makedirs(full_path, exist_ok=True) os.makedirs(full_path, exist_ok=True)
if len(os.listdir(full_path)) > 0: if os.listdir(full_path):
raise B5ExecutionError('Cannot init an existing directory if not empty') raise B5ExecutionError('Cannot init an existing directory if not empty')
_run_cmd(['git', 'clone', skeleton_url, full_path], 'Could not clone skeleton repository, see above') _run_cmd(['git', 'clone', skeleton.get_url(), full_path], 'Could not clone skeleton repository, see above')
os.chdir(full_path) os.chdir(full_path)
if not branch is None: if not branch is None:
_run_cmd(['git', 'checkout', branch], 'Could not checkout required branch, see above') _run_cmd(['git', 'checkout', branch], 'Could not checkout required branch, see above')
...@@ -78,8 +56,8 @@ def main(): ...@@ -78,8 +56,8 @@ def main():
shutil.rmtree(init_path) shutil.rmtree(init_path)
# _run_cmd(['git', 'add', '-A']) # _run_cmd(['git', 'add', '-A'])
termcolor.cprint('Successful initialized {path}'.format(path=path), 'green') termcolor.cprint('Successful initialized {path}'.format(path=path), 'green')
termcolor.cprint(' skeleton used: {skeleton_url}'.format(skeleton_url=skeleton_url), 'green') termcolor.cprint(' skeleton used: {skeleton_url}'.format(skeleton_url=skeleton.get_url()), 'green')
termcolor.cprint(' project path: {full_path}'.format(full_path=full_path), 'green') termcolor.cprint(' project path: {full_path}'.format(full_path=full_path), 'green')
except B5ExecutionError as e: except B5ExecutionError as error:
termcolor.cprint(str(e), 'red') termcolor.cprint(str(error), 'red')
sys.exit(1) sys.exit(1)
import argparse
from .detect import DETECT
class ArgumentParser:
def __init__(self, prog='b5', description='', formatter_class=argparse.ArgumentDefaultsHelpFormatter):
self.parser = argparse.ArgumentParser(
prog=prog,
formatter_class=formatter_class,
description=description
)
self.defaults = {}
def parse(self, arguments=None, help_as_default=False):
if help_as_default:
if not arguments:
arguments = ['help']
args = self.parser.parse_args(arguments)
for key, value in self.defaults.items():
if not getattr(args, key):
setattr(args, key, value)
return args
def set_default(self, key, value):
self.defaults.update({key: value})
class MainArgumentParser(ArgumentParser):
"""
class for parsing all the b5 command line arguments.
Possible command line argument:
--config -c Path to config (inside run path)
--taskfile -t Path to Taskfile (inside run path)
--project-path -p Project path if not part of parent paths, normally b5 tries to get the project path by itself
--run-path -r Path inside the project b5 will execute in (cd into)
--detect -d Version Control System detection (git or hg)
--shell -s Shell to run the generated script in (should be bash)
--quiet -q Determine if the script should print out stuff
"""
def add_arguments(self):
self.__add_argument_config()
self.__add_argument_taskfile()
self.__add_argument_project_path()
self.__add_argument_run_path()
self.__add_argument_detect()
self.__add_argument_shell()
self.__add_argument_quiet()
self.__add_argument_command()
def __add_argument_config(self):
self.parser.add_argument(
'-c', '--config',
nargs='?',
action='append',
help='Path to config (inside run path)',
dest='configfiles',
)
def __add_argument_taskfile(self):
self.parser.add_argument(
'-t', '--taskfile',
nargs='?',
action='append',
help='Path to Taskfile (inside run path)',
dest='taskfiles',
)
def __add_argument_project_path(self):
self.parser.add_argument(
'-p', '--project-path',
nargs='?',
help='Project path if not part of parent paths, normally b5 tries to get the project path by itself',
dest='project_path',
)
def __add_argument_run_path(self):
self.parser.add_argument(
'-r', '--run-path',
nargs='?',
help='Path inside the project b5 will execute in (cd into)',
dest='run_path',
default='build',
)
def __add_argument_detect(self):
self.parser.add_argument(
'-d', '--detect',
nargs='?',
help='Project detection',
choices=DETECT,
dest='detect',
default='git',
)
def __add_argument_shell(self):
self.parser.add_argument(
'-s', '--shell', nargs='?',
help='Shell to run the generated script in (should be bash)',
dest='shell',
default='/bin/bash',
)
def __add_argument_quiet(self):
self.parser.add_argument(
'-q', '--quiet',
action='store_true',
dest='quiet',
default=False,
)
self.parser.set_defaults()
def __add_argument_command(self):
self.parser.add_argument('command')
self.parser.add_argument('command_args', nargs=argparse.REMAINDER)
class InitArgumentParser(ArgumentParser):
def add_arguments(self):
self.__add_argument_skeleton()
self.__add_argument_branch()
self.__add_argument_path()
def __add_argument_skeleton(self):
self.parser.add_argument(
'-s', '--skeleton',
nargs='?',
dest='skeleton',
default='basic',
)
def __add_argument_branch(self):
self.parser.add_argument(
'-b', '--branch',
nargs='?',
dest='branch',
)
def __add_argument_path(self):
self.parser.add_argument(
dest='path'
)
class ExecuteArgumentParser(ArgumentParser):
def add_arguments(self):
self.__add_argument_state_file()
self.__add_argument_module()
self._add_argument_method()
self.__add_argument_args()
def __add_argument_state_file(self):
self.parser.add_argument(
'--state-file', nargs='?',
dest='state_file',
)
def __add_argument_module(self):
self.parser.add_argument(
'--module', nargs='?',
dest='module',
)
def _add_argument_method(self):
self.parser.add_argument(
'--method', nargs='?',
dest='method',
)
def __add_argument_args(self):
self.parser.add_argument(
'--args',
nargs=argparse.REMAINDER,
dest='args'
)
class TemplateArgumentParser(ArgumentParser):
def add_arguments(self):
self.__add_argument_overwrite()
self.__add_argument_template_file()
self.__add_argument_output_file()
def __add_argument_overwrite(self):
self.parser.add_argument(
'-o', '--overwrite', nargs='?',
help='Control if existing files should be overwritten',
dest='overwrite', default='ask',
choices=['yes', 'if-older', 'no', 'ask', 'ask-if-older']
)
def __add_argument_template_file(self):
self.parser.add_argument('template_file')
def __add_argument_output_file(self):
self.parser.add_argument('output_file', nargs='?')
import yaml
import os import os
import yaml
from .version import ensure_config_version from .version import ensure_config_version
from ..exceptions import B5ExecutionError from ..exceptions import B5ExecutionError
...@@ -15,8 +16,8 @@ def find_configs(state, configs): ...@@ -15,8 +16,8 @@ def find_configs(state, configs):
'config': config, 'config': config,
'path': config_path, 'path': config_path,
}) })
#if not found_configs: # if not found_configs:
# raise B5ExecutionError('No config found, tried %s inside %s' % (', '.join(configs), run_path)) # raise B5ExecutionError('No config found, tried %s inside %s' % (', '.join(configs), run_path))
return found_configs return found_configs
...@@ -40,7 +41,6 @@ def merge_config(cur_config, new_config): ...@@ -40,7 +41,6 @@ def merge_config(cur_config, new_config):
return result_config return result_config
def validate_config(config): def validate_config(config):
if 'version' in config: if 'version' in config:
ensure_config_version(config['version']) ensure_config_version(config['version'])
...@@ -51,8 +51,8 @@ def load_config(state): ...@@ -51,8 +51,8 @@ def load_config(state):
configfiles = state.configfiles configfiles = state.configfiles
config = {} config = {}
for configfile in configfiles: for configfile in configfiles:
fh = open(configfile['path'], 'r') file_handle = open(configfile['path'], 'r')
file_config = yaml.safe_load(fh) file_config = yaml.safe_load(file_handle)
if not isinstance(file_config, dict): if not isinstance(file_config, dict):
file_config = {} file_config = {}
config = merge_config(config, file_config) config = merge_config(config, file_config)
......
...@@ -4,14 +4,14 @@ from importlib import import_module ...@@ -4,14 +4,14 @@ from importlib import import_module
def import_string(dotted_path): def import_string(dotted_path):
try: try:
module_path, class_name = dotted_path.rsplit('.', 1) module_path, class_name = dotted_path.rsplit('.', 1)
except ValueError as err: except ValueError:
raise ImportError("%s doesn't look like a module path" % dotted_path) raise ImportError("%s doesn't look like a module path" % dotted_path)
module = import_module(module_path) module = import_module(module_path)
try: try:
return getattr(module, class_name) return getattr(module, class_name)
except AttributeError as err: except AttributeError:
raise ImportError('Module "%s" does not define a "%s" attribute/class' % ( raise ImportError('Module "%s" does not define a "%s" attribute/class' % (
module_path, class_name) module_path, class_name)
) )
\ No newline at end of file
from ..modules import MODULES
from ..exceptions import B5ExecutionError
from .importutils import import_string from .importutils import import_string
from ..exceptions import B5ExecutionError
from ..modules import MODULES
def load_module(state, module_key): def load_module(state, module_key):
if not 'modules' in state.config or not module_key in state.config['modules']: if 'modules' not in state.config or module_key not in state.config['modules']:
raise RuntimeError('Module %s is not defined in config' % module_key) raise RuntimeError('Module %s is not defined in config' % module_key)
module_config = state.config['modules'][module_key] module_config = state.config['modules'][module_key]
...@@ -17,8 +17,10 @@ def load_module(state, module_key): ...@@ -17,8 +17,10 @@ def load_module(state, module_key):
module_import_path = module_class_key module_import_path = module_class_key
if module_class_key in MODULES: if module_class_key in MODULES:
module_import_path = MODULES[module_class_key] module_import_path = MODULES[module_class_key]
if not '.' in module_import_path: if '.' not in module_import_path:
raise B5ExecutionError('Module seems not to be valid (key=%s/import=%s), please check config' % (module_key, module_import_path)) raise B5ExecutionError('Module seems not to be valid (key=%s/import=%s), please check config' %
(module_key, module_import_path)
)
try: try:
module_class = import_string(module_import_path) module_class = import_string(module_import_path)
......
import os
import re
import shlex import shlex
import tempfile import tempfile
import os
from .module import load_module from .module import load_module
RE_KEY_ESCAPE = re.compile('[^a-zA-Z0-9]+')
CONFIG_SUB = '%s_%s'
CONFIG_KEYS = '%s_KEYS'
def modules_script_source(state): def modules_script_source(state):
script = [] script = []
if 'modules' in state.config: if 'modules' in state.config:
...@@ -15,12 +21,6 @@ def modules_script_source(state): ...@@ -15,12 +21,6 @@ def modules_script_source(state):
def config_script_source(config, prefix='CONFIG'): def config_script_source(config, prefix='CONFIG'):
import re
RE_KEY_ESCAPE = re.compile('[^a-zA-Z0-9]+')
CONFIG_SUB = '%s_%s'
CONFIG_KEYS = '%s_KEYS'
def _gen_config(config_node, prefix): def _gen_config(config_node, prefix):
script = [] script = []
...@@ -85,7 +85,6 @@ def construct_script_source(state): ...@@ -85,7 +85,6 @@ def construct_script_source(state):
# run_path and parse Taskfile's # run_path and parse Taskfile's
script.append('cd %s\n' % shlex.quote(state.run_path)) script.append('cd %s\n' % shlex.quote(state.run_path))
for taskfile in state.taskfiles: for taskfile in state.taskfiles:
#script.append('source %s\n' % shlex.quote(taskfile['path']))
script.append(open(taskfile['path'], 'r').read()) script.append(open(taskfile['path'], 'r').read())
return '\n'.join(script) return '\n'.join(script)
...@@ -108,16 +107,16 @@ fi ...@@ -108,16 +107,16 @@ fi
) )
class StoredScriptSource(object): class StoredScriptSource:
def __init__(self, state, source): def __init__(self, state, source):
self.state = state self.state = state
self.source = source self.source = source
self.fh = tempfile.NamedTemporaryFile(suffix='b5-compiled', delete=False) self.file_handle = tempfile.NamedTemporaryFile(suffix='b5-compiled', delete=False)
self.fh.write(self.source.encode('utf-8')) self.file_handle.write(self.source.encode('utf-8'))
self.fh.close() self.file_handle.close()
def close(self): def close(self):
os.unlink(self.fh.name) os.unlink(self.file_handle.name)
def __enter__(self): def __enter__(self):
return self return self
...@@ -127,4 +126,4 @@ class StoredScriptSource(object): ...@@ -127,4 +126,4 @@ class StoredScriptSource(object):
@property @property
def name(self): def name(self):
return self.fh.name return self.file_handle.name
import os
import re
import urllib.request
from urllib.error import URLError
class Skeleton:
NON_URL_SKELETON = re.compile('^[A-Za-z0-9_-]+$')
def __init__(self, skeleton_identified):
self._skeleton_identified = skeleton_identified
def get_url(self):
try:
return self._url
except AttributeError:
skeleton_url = self._skeleton_identified
if self.NON_URL_SKELETON.match(skeleton_url):
skeleton_url = 'https://git.team23.de/build/b5-skel-{skeleton}.git'.format(skeleton=self._skeleton_identified)
# If it's not a public repository, clone using ssh in order to allow ssh key file auth
if not self.__is_public_repository(skeleton_url):
skeleton_url = 'git@git.team23.de:build/b5-skel-{skeleton}.git'.format(skeleton=self._skeleton_identified)
self._url = skeleton_url
return self._url
def __is_public_repository(self, url):
request = urllib.request.urlopen(url)
request_url = request.geturl()
if url == request_url or url.rsplit('.', 1)[0] == request_url:
try:
if request.getcode() == 200:
return True
except URLError:
pass
return False
import yaml
import tempfile
import os import os
import tempfile
import yaml
class StoredState(object): class StoredState:
def __init__(self, state): def __init__(self, state):
self.state = state self.state = state
if not self.state.stored_name is None: if self.state.stored_name is not None:
raise RuntimeError('You may only store the state once') raise RuntimeError('You may only store the state once')
self.fh = tempfile.NamedTemporaryFile(suffix='b5-state', mode='w', encoding='utf-8', delete=False) self.file_handle = tempfile.NamedTemporaryFile(suffix='b5-state', mode='w', encoding='utf-8', delete=False)
self.state.stored_name = self.name self.state.stored_name = self.name
yaml.dump({ yaml.dump({
key: getattr(self.state, key) key: getattr(self.state, key)
for key in state.KEYS for key in state.KEYS
}, self.fh, default_flow_style=False) }, self.file_handle, default_flow_style=False)
self.fh.close() self.file_handle.close()
def close(self): def close(self):
os.unlink(self.fh.name) os.unlink(self.file_handle.name)
self.state.stored_name = None self.state.stored_name = None
def __enter__(self): def __enter__(self):
...@@ -29,10 +30,10 @@ class StoredState(object): ...@@ -29,10 +30,10 @@ class StoredState(object):
@property @property
def name(self): def name(self):
return self.fh.name return self.file_handle.name
class State(object): class State:
KEYS = ('project_path', 'run_path', 'taskfiles', 'configfiles', 'config', 'args', 'stored_name') KEYS = ('project_path', 'run_path', 'taskfiles', 'configfiles', 'config', 'args', 'stored_name')
taskfiles = [] taskfiles = []
...@@ -44,7 +45,7 @@ class State(object): ...@@ -44,7 +45,7 @@ class State(object):
if not hasattr(self, key): if not hasattr(self, key):
setattr(self, key, None) setattr(self, key, None)
for key in kwargs: for key in kwargs:
if not key in self.KEYS: if key not in self.KEYS:
raise RuntimeError('Key %s is not a valid state attribute' % key) raise RuntimeError('Key %s is not a valid state attribute' % key)
setattr(self, key, kwargs[key]) setattr(self, key, kwargs[key])
...@@ -52,5 +53,5 @@ class State(object): ...@@ -52,5 +53,5 @@ class State(object):
return StoredState(self) return StoredState(self)
@classmethod @classmethod
def load(cls, fh): def load(cls, file_handle):
return cls(**yaml.safe_load(fh)) return cls(**yaml.safe_load(file_handle))
...@@ -16,4 +16,3 @@ def find_taskfiles(state, taskfiles): ...@@ -16,4 +16,3 @@ def find_taskfiles(state, taskfiles):
if not found_taskfiles: if not found_taskfiles:
raise B5ExecutionError('No Taskfiles found, tried %s inside %s' % (', '.join(taskfiles), run_path)) raise B5ExecutionError('No Taskfiles found, tried %s inside %s' % (', '.join(taskfiles), run_path))
return found_taskfiles return found_taskfiles
import argparse
import os import os
import subprocess import subprocess
import sys import sys
import termcolor import termcolor
from .lib.argumentparser import MainArgumentParser
from . import VERSION
from .exceptions import B5ExecutionError from .exceptions import B5ExecutionError
from .lib.config import load_config
from .lib.detect import detect_project_path, DETECT
from .lib.taskfile import find_taskfiles
from .lib.config import find_configs from .lib.config import find_configs
from .lib.config import load_config
from .lib.detect import detect_project_path
from .lib.script import StoredScriptSource, construct_script_source, construct_script_run from .lib.script import StoredScriptSource, construct_script_source, construct_script_run
from .lib.state import State from .lib.state import State
from . import VERSION from .lib.taskfile import find_taskfiles
def main(): def main():
try: try:
# Parse all arguments # Parse all arguments
parser = argparse.ArgumentParser( parser = MainArgumentParser('b5')
prog='b5', parser.add_arguments()
formatter_class=argparse.ArgumentDefaultsHelpFormatter parser.set_default('taskfiles', ['~/.b5/Taskfile', 'Taskfile', 'Taskfile.local'])
) parser.set_default('configfiles', ['~/.b5/config.yml', 'config.yml', 'config.local.yml', 'local.yml'])
parser.add_argument( args = parser.parse(sys.argv[1:], True)
'-q', '--quiet',
action='store_true',
dest='quiet',
)
parser.add_argument(
'-p', '--project-path', nargs='?',
help='Project path if not part of parent paths, normally b5 tries to get the project path by itself',
dest='project_path',
)
parser.add_argument(
'-r', '--run-path', nargs='?',
help='Path inside the project b5 will execute in (cd into)',
dest='run_path', default='build',
)
parser.add_argument(
'-d', '--detect', nargs='?',
help='Project detection',
default='git', choices=DETECT,
dest='detect',
)
parser.add_argument(
'-c', '--config', nargs='?', action='append',
help='Path to config (inside run path)',
dest='configfiles',
)
parser.add_argument(
'-t', '--taskfile', nargs='?', action='append',
help='Path to Taskfile (inside run path)',
dest='taskfiles',
)
parser.add_argument(
'-s', '--shell', nargs='?',
help='Shell to run the generated script in (should be bash)',
dest='shell',
default='/bin/bash',
)
parser.add_argument('command')
parser.add_argument('command_args', nargs=argparse.REMAINDER)
parser.set_defaults(quiet=False)
sys_args = sys.argv[1:]
if not sys_args:
sys_args = ['help']
args = parser.parse_args(args=sys_args)
if args.taskfiles is None:
args.taskfiles = ['~/.b5/Taskfile', 'Taskfile', 'Taskfile.local']
if args.configfiles is None:
args.configfiles = ['~/.b5/config.yml', 'config.yml', 'config.local.yml', 'local.yml']
# State vars # State vars
state = State( state = State(
project_path = args.project_path, project_path=args.project_path,
run_path = None, run_path=None,
taskfiles = [], taskfiles=[],
config = {}, config={},
args = vars(args) args=vars(args)
) )
# Find project dir # Find project dir
...@@ -112,12 +65,10 @@ def main(): ...@@ -112,12 +65,10 @@ def main():
construct_script_run(state), construct_script_run(state),
]) ])
with StoredScriptSource(state, script_source) as source: with StoredScriptSource(state, script_source) as source:
# print(source.source)
# return
if state.run_path: if state.run_path:
os.chdir(state.run_path) os.chdir(state.run_path)
try: try:
result = subprocess.run( subprocess.run(
[ [
args.shell, args.shell,
source.name, source.name,
...@@ -132,7 +83,6 @@ def main(): ...@@ -132,7 +83,6 @@ def main():
termcolor.cprint('Task execution failed, see above', color='red') termcolor.cprint('Task execution failed, see above', color='red')
sys.exit(1) sys.exit(1)
_print('Task exited ok', color='green') _print('Task exited ok', color='green')
#print(result) except B5ExecutionError as error:
except B5ExecutionError as e: termcolor.cprint(str(error), 'red')
termcolor.cprint(str(e), 'red')
sys.exit(1) sys.exit(1)
from ..lib.config import merge_config
import re import re
from ..lib.config import merge_config
CONFIG_PREFIX_RE = re.compile('[^A-Z0-9]') CONFIG_PREFIX_RE = re.compile('[^A-Z0-9]')
MODULES = { MODULES = {
...@@ -15,7 +15,7 @@ MODULES = { ...@@ -15,7 +15,7 @@ MODULES = {
} }
class BaseModule(object): class BaseModule:
DEFAULT_CONFIG = {} DEFAULT_CONFIG = {}
def __init__(self, name, config, state, **kwargs): def __init__(self, name, config, state, **kwargs):
......
...@@ -50,7 +50,6 @@ class ComposerModule(BaseModule): ...@@ -50,7 +50,6 @@ class ComposerModule(BaseModule):
) )
'''.format( '''.format(
vendor_path=shlex.quote(self.config['vendor_path']), vendor_path=shlex.quote(self.config['vendor_path']),
base_path=shlex.quote(self.config['base_path']),
))) )))
script.append(self._script_function_source('composer', ''' script.append(self._script_function_source('composer', '''
......
...@@ -52,7 +52,6 @@ class PipenvModule(BaseModule): ...@@ -52,7 +52,6 @@ class PipenvModule(BaseModule):
base_path=shlex.quote(self.config['base_path']), base_path=shlex.quote(self.config['base_path']),
pipenv_bin=shlex.quote(self.config['pipenv_bin']), pipenv_bin=shlex.quote(self.config['pipenv_bin']),
install_dev='--dev' if self.config['install_dev'] else '', install_dev='--dev' if self.config['install_dev'] else '',
name=self.name,
))) )))
script.append(self._script_function_source('update', ''' script.append(self._script_function_source('update', '''
......
import argparse
import datetime import datetime
import os import os
import sys import sys
import termcolor import termcolor
import jinja2 import jinja2
from ..lib.argumentparser import TemplateArgumentParser
from .. import VERSION from .. import VERSION
from . import BaseModule from . import BaseModule
class TemplateModule(BaseModule): class TemplateModule(BaseModule):
def execute_render(self, state, sys_args): def execute_render(self, state, sys_args):
parser = argparse.ArgumentParser( parser = TemplateArgumentParser('{name}:render'.format(name=self.name))
prog='{name}:render'.format(name=self.name), parser.add_arguments()
formatter_class=argparse.ArgumentDefaultsHelpFormatter args = parser.parse(sys_args)
)
parser.add_argument(
'-o', '--overwrite', nargs='?',
help='Control if existing files should be overwritten',
dest='overwrite', default='ask',
choices=['yes', 'if-older', 'no', 'ask', 'ask-if-older']
)
parser.add_argument('template_file')
parser.add_argument('output_file', nargs='?')
args = parser.parse_args(args=sys_args)
template_file = os.path.realpath(os.path.join(state.run_path, args.template_file)) template_file = os.path.realpath(os.path.join(state.run_path, args.template_file))
output_file = None output_file = None
...@@ -85,18 +75,18 @@ class TemplateModule(BaseModule): ...@@ -85,18 +75,18 @@ class TemplateModule(BaseModule):
'output_file': output_file if output_file else '-', 'output_file': output_file if output_file else '-',
}, },
) )
except jinja2.UndefinedError as e: except jinja2.UndefinedError as error:
termcolor.cprint('Template could not be rendered (%s), error message' % args.template_file, color='red') termcolor.cprint('Template could not be rendered (%s), error message' % args.template_file, color='red')
termcolor.cprint(e.message, color='yellow') termcolor.cprint(error.message, color='yellow')
sys.exit(1) sys.exit(1)
if output_file: if output_file:
try: try:
with open(output_file, 'w') as fh: with open(output_file, 'w') as file_handle:
fh.write(rendered) file_handle.write(rendered)
except IOError as e: except IOError as error:
termcolor.cprint('Template output could not be saved (%s)' % args.output_file, color='red') termcolor.cprint('Template output could not be saved (%s)' % args.output_file, color='red')
termcolor.cprint(e.message, color='yellow') termcolor.cprint(str(error), color='yellow')
sys.exit(1) sys.exit(1)
else: else:
print(rendered) print(rendered)
......
...@@ -66,7 +66,6 @@ class VirtualenvModule(BaseModule): ...@@ -66,7 +66,6 @@ class VirtualenvModule(BaseModule):
"$@" "$@"
) )
'''.format( '''.format(
base_path=shlex.quote(self.config['base_path']),
activate_path=shlex.quote(os.path.join( activate_path=shlex.quote(os.path.join(
self.config['env_path'], self.config['env_path'],
'bin', 'bin',
......
import unittest
from b5.lib.argumentparser import MainArgumentParser, InitArgumentParser, ExecuteArgumentParser, TemplateArgumentParser
class TestMainArgumentParser(unittest.TestCase):
def setUp(self):
self.parser = MainArgumentParser()
self.parser.add_arguments()
def test_config_file_can_be_set(self):
arguments = self.parser.parse(['--config', 'test.yml', 'test'])
self.assertIn('test.yml', arguments.configfiles)
def test_default_configfiles_will_be_set(self):
self.parser.set_default('configfiles', ['~/.b5/config.yml', 'config.yml', 'config.local.yml', 'local.yml'])
arguments = self.parser.parse(['test'])
self.assertIn('~/.b5/config.yml', arguments.configfiles)
self.assertIn('config.yml', arguments.configfiles)
self.assertIn('config.local.yml', arguments.configfiles)
self.assertIn('local.yml', arguments.configfiles)
def test_taskfile_can_be_set(self):
arguments = self.parser.parse(['--taskfile', 'DifferentTaskFile', 'test'])
self.assertIn('DifferentTaskFile', arguments.taskfiles)
def test_default_taskfiles_will_be_set(self):
self.parser.set_default('taskfiles', ['~/.b5/Taskfile', 'Taskfile', 'Taskfile.local'])
arguments = self.parser.parse(['test'])
self.assertIn('~/.b5/Taskfile', arguments.taskfiles)
self.assertIn('Taskfile', arguments.taskfiles)
self.assertIn('Taskfile.local', arguments.taskfiles)
def test_default_run_path_is_set(self):
arguments = self.parser.parse(['test'])
self.assertEqual('build', arguments.run_path)
def test_run_path_can_be_set(self):
arguments = self.parser.parse(['--run-path', 'not_the_build_folder', 'test'])
self.assertEqual('not_the_build_folder', arguments.run_path)
def test_default_shell_is_set(self):
arguments = self.parser.parse(['test'])
self.assertEqual('/bin/bash', arguments.shell)
def test_shell_can_be_set(self):
arguments = self.parser.parse(['--shell', '/bin/sh', 'test'])
self.assertEqual('/bin/sh', arguments.shell)
def test_detect_default_is_set_to_git(self):
arguments = self.parser.parse(['test'])
self.assertEqual('git', arguments.detect)
def test_detect_can_be_set_to_mercurial(self):
arguments = self.parser.parse(['--detect', 'hg', 'test'])
self.assertEqual('hg', arguments.detect)
def test_quiet_is_false_by_default(self):
arguments = self.parser.parse(['test'])
self.assertFalse(arguments.quiet)
def test_quiet_can_be_set_to_true(self):
arguments = self.parser.parse(['--quiet', 'true', 'test'])
self.assertTrue(arguments.quiet)
class TestInitArgumentParserTest(unittest.TestCase):
def setUp(self):
self.parser = InitArgumentParser()
self.parser.add_arguments()
def test_skeleton_can_be_set(self):
arguments = self.parser.parse(['--skeleton', 'test', 'path'])
self.assertEqual('test', arguments.skeleton)
def test_skeleton_can_be_set_with_shortcut(self):
arguments = self.parser.parse(['-s', 'test', 'path'])
self.assertEqual('test', arguments.skeleton)
def test_skeleton_has_basic_as_default(self):
arguments = self.parser.parse(['path'])
self.assertEqual('basic', arguments.skeleton)
def test_branch_can_be_set(self):
arguments = self.parser.parse(['--branch', 'test', 'path'])
self.assertEqual('test', arguments.branch)
def test_branch_can_be_set_with_shortcut(self):
arguments = self.parser.parse(['-b', 'test', 'path'])
self.assertEqual('test', arguments.branch)
class TestExecuteArgumentParser(unittest.TestCase):
def setUp(self):
self.parser = ExecuteArgumentParser()
self.parser.add_arguments()
def test_state_file_can_be_set(self):
arguments = self.parser.parse(['--state-file', 'test'])
self.assertEqual('test', arguments.state_file)
def test_module_can_be_set(self):
arguments = self.parser.parse(['--module', 'test'])
self.assertEqual('test', arguments.module)
def test_method_can_be_set(self):
arguments = self.parser.parse(['--method', 'test'])
self.assertEqual('test', arguments.method)
def test_args_can_be_set(self):
arguments = self.parser.parse(['--args', 'test'])
self.assertIn('test', arguments.args)
class TestTemplateArgumentParser(unittest.TestCase):
def setUp(self):
self.parser = TemplateArgumentParser()
self.parser.add_arguments()
def test_overwrite_can_be_set(self):
arguments = self.parser.parse(['--overwrite', 'yes', 'template_file', 'output_file'])
self.assertEqual('yes', arguments.overwrite)
def test_overwrite_defaults_to_ask(self):
arguments = self.parser.parse(['template_file', 'output_file'])
self.assertEqual('ask', arguments.overwrite)
def test_template_file_can_be_set(self):
arguments = self.parser.parse(['template_file', 'output_file'])
self.assertEqual('template_file', arguments.template_file)
def test_output_file_can_be_set(self):
arguments = self.parser.parse(['template_file', 'output_file'])
self.assertEqual('output_file', arguments.output_file)
if __name__ == '__main__':
unittest.main()
...@@ -35,4 +35,18 @@ task:pypi:release() { ...@@ -35,4 +35,18 @@ task:pypi:release() {
python3 setup.py sdist && \ python3 setup.py sdist && \
twine upload dist/* twine upload dist/*
) )
} }
\ No newline at end of file
task:lint() {
(
cd .. && \
pipenv:run pylint --rcfile=pylintrc --exit-zero b5/
)
}
task:test() {
(
cd .. && \
pipenv:run pytest b5/tests -v
)
}
...@@ -27,7 +27,7 @@ OR ...@@ -27,7 +27,7 @@ OR
```bash ```bash
git clone git@git.team23.de:build/b5.git git clone git@git.team23.de:build/b5.git
cd b5 cd b5
pip install . pip3 install .
``` ```
## Development installation (using live version of b5 repository) ## Development installation (using live version of b5 repository)
......
...@@ -65,6 +65,16 @@ Execute a development shell for the project. May differ based on the project. ...@@ -65,6 +65,16 @@ Execute a development shell for the project. May differ based on the project.
most commonly start a docker shell (like `docker-compose run --rm web /bin/bash --login`). We use `b5 django:shell` most commonly start a docker shell (like `docker-compose run --rm web /bin/bash --login`). We use `b5 django:shell`
instead. This behavior is true for other frameworks providing its own development shell, too. instead. This behavior is true for other frameworks providing its own development shell, too.
## Testing
### test
Will exeecute the test suite.
### lint
Will execute the linter and give you details if anything does not follow the coding guidelines.
## Deployment ## Deployment
### deploy ### deploy
......
This diff is collapsed.