diff --git a/tools/init_project.py b/tools/init_project.py new file mode 100644 index 0000000..781bc4e --- /dev/null +++ b/tools/init_project.py @@ -0,0 +1,125 @@ + + +import logging +import os +from pathlib import Path +import shutil +import subprocess +import re +import sys +from typing import Sequence + +_invalid_file_char_regex = re.compile(r'[^a-zA-Z0-9_]') +_variable_regex = re.compile(r'@([A-Z_]+)@') +_logger = logging.getLogger(__name__) +_root: Path + +def _exec_checked(args: Sequence[str], **kwargs) -> None: + subprocess.run(args, stdout=sys.stdout, stderr=sys.stderr, check=True, **kwargs) + +def _exec_get_output(args: Sequence[str], **kwargs) -> str: + return subprocess.run(args, text=True, check=True, capture_output=True).stdout + +def _prompt(prefix: str = '> ') -> str: + sys.stdout.write(prefix) + sys.stdout.flush() + return sys.stdin.readline() + +def _escape_filename(name: str) -> str: + return _invalid_file_char_regex.sub('_', name) + +def verify_tools() -> None: + _logger.debug('Verifying all required tools are available...') + success = True + if shutil.which('git') is None: + _logger.error('git not found in PATH') + success = False + if shutil.which('scons') is None: + _logger.error('SCons not found in PATH') + success = False + if not success: + raise RuntimeError('one or more required tools could not be found') + +def download_spp() -> None: + _logger.debug('Checking if Scons++ is checked out...') + output = _exec_get_output(['git', 'submodule', 'status', 'external/scons-plus-plus']) + if output[0] in ('+', ' '): + return + assert output[0] == '-' + _logger.info('SCons++ not checkout out yet, doing it now.') + _exec_checked(['git', 'submodule', 'init']) + _exec_checked(['git', 'submodule', 'update', 'external/scons-plus-plus']) + +def update_spp() -> None: + _logger.debug('Updating SCons++ submodule...') + os.chdir(_root / 'external/scons-plus-plus') + try: + _exec_checked(['git', 'fetch', 'origin', 'master']) + _exec_checked(['git', 'checkout', 'master']) + output = _exec_get_output(['git', 'status', '--porcelain']) + if output.strip() == '': + return + finally: + os.chdir(_root) + _logger.info('Changes in SCons++ detected, creating commit.') + _exec_checked(['git', 'commit', '-m', 'Updated Scons++', 'external/scons-plus-plus']) + +def _replace_in_file(file: Path, **replacements) -> None: + _logger.debug('Patching file "%s"...', file) + bakfile = file.with_suffix(f'{file.suffix}.bak') + bakfile.unlink(missing_ok=True) + file.rename(bakfile) + + with bakfile.open('r') as fin, file.open('w') as fout: + for line in fin: + def _repl(match: re.Match) -> str: + return replacements.get(match.group(1), '') + fout.write(_variable_regex.sub(_repl, line)) + bakfile.unlink() + +def setup_project() -> None: + project_name = '' + while project_name == '': + print('Please enter a name for your project.') + project_name = _prompt().strip() + module_name = project_name + print(f'Please enter a name for the first module. Leave empty to use the project name ("{module_name}").') + new_name = _prompt().strip() + if new_name != '': + module_name = new_name + + module_folder_name = _escape_filename(project_name.lower()) + print(f'Please enter a folder name for the first module. Leave empty for "{module_folder_name}". Anything but [A-Za-z0-9_] will be replaced with underscores.') + new_name = _prompt().strip() + if new_name != '': + module_folder_name = _escape_filename(new_name) # just enforce nice names + + module_exe_name = _escape_filename(module_name.lower()) + print(f'Please enter a file name for the module executable. Leave empty for "{module_exe_name}". Omit the file suffix. Anything but [A-Za-z0-9_] will be replaced with underscores.') + new_name = _prompt().strip() + if new_name != '': + module_exe_name = _escape_filename(new_name) + + + _replace_in_file(_root / 'SConstruct', PROJECT_NAME=project_name, MODULE_FOLDER_NAME=module_folder_name) + _replace_in_file(_root / 'private/spp_template/SModule', MODULE_NAME=module_name, EXE_NAME=module_exe_name) + + template_folder = _root / 'private/spp_template' + module_folder = _root / 'private' / module_folder_name + _logger.debug(f'Moving {template_folder} to {module_folder}.') + + _logger.info('Creating a git commit for the setup.') + _exec_checked(['git', 'commit', '-m', 'Project setup', 'SConstruct', 'private']) + +if __name__ == '__main__': + logging.basicConfig(level=logging.DEBUG, format='%(message)s') + _root = Path(__file__).parent.parent + os.chdir(_root) + #try: + verify_tools() + download_spp() + update_spp() + setup_project() + #except Exception as e: + # _logger.error('There was an error running the script: %s.', e) + # sys.exit(1)