diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ca6e18d..86f52d9 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,12 +2,20 @@ .. Keep the current version number on line number 5 +0.0.4 +===== + +20120-01-09 + +* Rewrite naming scheme for output files + + 0.0.3 ===== 2019-09-30 -* Add support for `tools_directory` setting +* Add support for ``tools_directory`` setting * Use operating system's standard value for default location of the configuration file diff --git a/Makefile b/Makefile index b8eef18..88ca4a9 100644 --- a/Makefile +++ b/Makefile @@ -41,12 +41,12 @@ check: .PHONY: lint lint: - python3 -m pytest --codestyle --pylint -m 'codestyle or pylint' + python3 -m pytest --pycodestyle --pylint -m 'pycodestyle or pylint' .PHONY: pycodestyle pycodestyle: - python3 -m pytest --codestyle -m codestyle + python3 -m pytest --pycodestyle -m pycodestyle .PHONY: pylint @@ -65,7 +65,7 @@ pytest: .PHONY: review review: check - python3 -m pytest --codestyle --pylint + python3 -m pytest --pycodestyle --pylint .PHONY: clean diff --git a/README.rst b/README.rst index 2f194a5..bd7692f 100644 --- a/README.rst +++ b/README.rst @@ -33,23 +33,19 @@ current working directory. .. code:: [toolmaker.tool.defaults] - output_file_win = ${output_file}.pyz - tools_directory = ~/bin/.toolmaker + tools_directory = ~/.local/bin/.toolmaker - [toolmaker.tool.zapp:deptree.zapp] + [toolmaker.tool.zapp:deptree] entry_point = deptree.cli:main - output_file = deptree requirements = deptree - [toolmaker.tool.pex:http.pex] + [toolmaker.tool.pex:http] entry_point = http.server - output_file = http requirements = - [toolmaker.tool.shiv:shiv.shiv] + [toolmaker.tool.shiv:shiv] entry_point = shiv.cli:main - output_file = shiv requirements = shiv @@ -67,11 +63,88 @@ The action can be specified on the command line. Either one of: The default action when no flag is specified is to build the tools. -Tips ----- +Configuration +============= + +Place tools in current directory +-------------------------------- + +.. code:: + + [toolmaker.tool.defaults] + tools_directory = + tool_directory = + + [toolmaker.tool.zapp:foo] + # ./foo + + [toolmaker.tool.zapp:bar] + # ./bar + + +Place tools in specific directory +--------------------------------- + +.. code:: + + [toolmaker.tool.defaults] + tools_directory = /somewhere + tool_directory = + + [toolmaker.tool.zapp:foo] + # /somewhere/foo + + [toolmaker.tool.zapp:bar] + # /somewhere/bar + + +Place tools in subdirectories +----------------------------- + +.. code:: + + [toolmaker.tool.defaults] + tools_directory = /somewhere + + [toolmaker.tool.zapp:foo0] + # /somewhere/foo0/foo0 + + [toolmaker.tool.zapp:foo1] + tool_directory = foo0 + # /somewhere/foo0/foo1 + + [toolmaker.tool.zapp:foo2] + # /somewhere/foo2/foo2 + + [toolmaker.tool.zapp:foo3] + tool_file = foo0 + # /somewhere/foo3/foo0 + + +Example to use with GNU stow +---------------------------- + +To use in combination with `GNU Stow`_: + +.. code:: + + [toolmaker.tool.defaults] + tools_directory = ~/.local/bin/.toolmaker + + [toolmaker.tool.zapp:foo0] + # ~/.local/bin/.toolmaker/foo0/foo0 + + [toolmaker.tool.zapp:foo1] + tool_directory = foo0 + # ~/.local/bin/.toolmaker/foo0/foo1 + + [toolmaker.tool.zapp:foo2] + # ~/.local/bin/.toolmaker/foo2/foo2 + + [toolmaker.tool.zapp:foo3] + tool_file = foo0 + # ~/.local/bin/.toolmaker/foo3/foo0 -Place in a subdirectory of a directory that is available on your ``PATH`` -(typically your ``~/bin`` directory) and use in combination with `GNU Stow`_. Details diff --git a/example.cfg b/example.cfg index 48d822a..773707a 100644 --- a/example.cfg +++ b/example.cfg @@ -2,22 +2,19 @@ [toolmaker.tool.defaults] -output_file_win = ${output_file}.pyz +tools_directory = -[toolmaker.tool.zapp:deptree.zapp] +[toolmaker.tool.zapp:deptree] entry_point = deptree.cli:main -output_file = deptree requirements = deptree -[toolmaker.tool.pex:http.pex] +[toolmaker.tool.pex:http] entry_point = http.server -output_file = http requirements = -[toolmaker.tool.shiv:shiv.shiv] +[toolmaker.tool.shiv:shiv] entry_point = shiv.cli:main -output_file = shiv requirements = shiv diff --git a/setup.cfg b/setup.cfg index e0e3db2..e8f43be 100644 --- a/setup.cfg +++ b/setup.cfg @@ -12,15 +12,19 @@ author = sinoroc author_email = sinoroc.code+python@gmail.com description = toolmaker application license = Apache-2.0 +license_file = LICENSE.txt long_description = file: README.rst long_description_content_type = text/x-rst +project_urls = + GitLab = https://gitlab.com/sinoroc/toolmaker + GitHub = https://github.com/sinoroc/toolmaker url = https://pypi.org/project/toolmaker/ [options] install_requires = - importlib_metadata - pex[cachecontrol, requests] + importlib-metadata + pex shiv zapp package_dir = @@ -39,8 +43,6 @@ package = wheel zapp test = - astroid<2.3 - pylint<2.4 pytest pytest-pycodestyle pytest-pylint diff --git a/src/toolmaker/cli.py b/src/toolmaker/cli.py index d03ceef..0e06fd4 100644 --- a/src/toolmaker/cli.py +++ b/src/toolmaker/cli.py @@ -5,8 +5,6 @@ import argparse -import configparser -import logging from . import _meta from . import core @@ -56,41 +54,35 @@ def _create_args_parser(default_config_path, tools_names=None): def main(): """ CLI main function """ - logger = logging.getLogger(__name__) - logging.basicConfig(level=logging.INFO) - default_config_path = core.get_default_config_file_path() args_parser = _create_args_parser(default_config_path) args = args_parser.parse_args() - raw_config = None + config = None if args.config: - logger.info("Reading configuration from file '%s'", args.config) - raw_config = configparser.ConfigParser( - default_section='{}.tool.defaults'.format(_meta.PROJECT_NAME), - interpolation=configparser.ExtendedInterpolation(), - ) try: - raw_config.read_file(args.config) - except configparser.Error as config_error: + config = core.parse_config(args.config) + except core.ConfigurationFileError as config_error: args_parser.error(config_error) - config = core.parse_config(raw_config) - - tools_names = list(config['tools'].keys()) - if not args.all: - args_parser = _create_args_parser(default_config_path, tools_names) - args = args_parser.parse_args() - - if args.tools: - tools_names = args.tools - - if args.delete: - core.delete(config, tools_names) - elif args.rebuild: - core.build(config, tools_names, force=True) - else: - core.build(config, tools_names) + else: + tools_names = list(config['tools'].keys()) + if not args.all: + args_parser = _create_args_parser( + default_config_path, + tools_names, + ) + args = args_parser.parse_args() + + if args.tools: + tools_names = args.tools + + if args.delete: + core.delete(config, tools_names) + elif args.rebuild: + core.build(config, tools_names, force=True) + else: + core.build(config, tools_names) # EOF diff --git a/src/toolmaker/core.py b/src/toolmaker/core.py index 90d17cc..1cd73d6 100644 --- a/src/toolmaker/core.py +++ b/src/toolmaker/core.py @@ -5,11 +5,13 @@ """ +import configparser +import errno import logging import os import pathlib +import platform -import pex import pex.bin.pex import shiv import shiv.cli @@ -22,6 +24,10 @@ logging.basicConfig(level=logging.INFO) +class ConfigurationFileError(configparser.Error): + """Configuration file error""" + + def _pex(requirements, entry_point, output_file_path): cmd = [ '--entry-point={}'.format(entry_point), @@ -61,18 +67,30 @@ def _get_requirements(config): def _get_file_path(config): - directory_path = ( + path = ( pathlib.Path( os.path.expandvars(config['tools_directory']), ).expanduser() if 'tools_directory' in config else pathlib.Path.cwd() ) - file_name = config['output_file'] - if os.name == 'nt' and 'output_file_win' in config: - file_name = config['output_file_win'] - file_path = directory_path.joinpath(config['name'], file_name) - return file_path + + if 'tool_directory' in config: + tool_directory_name = config['tool_directory'] + if tool_directory_name: + path = path.joinpath(tool_directory_name) + else: + path = path.joinpath(config['name']) + + if 'tool_file' in config: + path = path.joinpath(config['tool_file']) + else: + path = path.joinpath(config['name']) + + if platform.system() == 'Windows': + path = path.with_suffix('.pyz') + + return path def _build_pex(config, force): @@ -139,16 +157,31 @@ def _parse_tool_config(raw_config, section_name): return tool_config -def parse_config(raw_config): - """ Parse the raw configration file to build a configuration dictionary. +def parse_config(config_file): + """ Parse the configuration file to build a configuration dictionary """ - config = { - 'tools': {}, - } - for section_name in raw_config.sections(): - tool_config = _parse_tool_config(raw_config, section_name) - if tool_config: - config['tools'][tool_config['name']] = tool_config + config = None + raw_config = configparser.ConfigParser( + default_section='{}.tool.defaults'.format(_meta.PROJECT_NAME), + interpolation=configparser.ExtendedInterpolation(), + ) + try: + LOGGER.info("Read configuration from '%s'", config_file.name) + raw_config.read_file(config_file) + except configparser.Error as config_error: + LOGGER.error( + "Can not read configuration from file '%s'", + config_file.name, + ) + raise ConfigurationFileError(str(config_error)) + else: + config = { + 'tools': {}, + } + for section_name in raw_config.sections(): + tool_config = _parse_tool_config(raw_config, section_name) + if tool_config: + config['tools'][tool_config['name']] = tool_config return config @@ -195,7 +228,6 @@ def delete(config, tools_names): try: output_dir_path.rmdir() except OSError as ose: - import errno if ose.errno == errno.ENOTEMPTY: LOGGER.warning( "Directory '%s' not empty", @@ -213,12 +245,13 @@ def get_default_config_file_path(): dir_name = _meta.PROJECT_NAME path = None config_path = None - if os.name == 'nt': + system = platform.system() + if system == 'Windows': if 'APPDATA' in os.environ: config_path = pathlib.Path(os.environ['APPDATA']) else: config_path = pathlib.Path.home().joinpath('AppData', 'Roaming') - elif os.name == 'posix': + elif system == 'Linux': if 'XDG_CONFIG_HOME' in os.environ: config_path = pathlib.Path(os.environ['XDG_CONFIG_HOME']) else: diff --git a/test/test_unit.py b/test/test_unit.py index 81d0222..6bdd558 100644 --- a/test/test_unit.py +++ b/test/test_unit.py @@ -2,6 +2,7 @@ """ +import tempfile import unittest import toolmaker @@ -20,4 +21,102 @@ def test_project_has_version_string(self): self.fail(version_exception) +def _make_config(str_config): + config = None + with tempfile.NamedTemporaryFile(mode='r+') as file_: + file_.write(str_config) + file_.seek(0) + config = toolmaker.core.parse_config(file_) + return config + + +def _get_path(config, tool_name): + path = str( + toolmaker.core._get_file_path( # pylint: disable=protected-access + config['tools'][tool_name], + ), + ) + return path + + +class TestGetFilePath(unittest.TestCase): + """Test 'get_file_path'""" + + def test_get_file_path_one(self): + """Install all in one specific directory""" + str_config = """ + [toolmaker.tool.defaults] + tools_directory = /somewhere + tool_directory = + + [toolmaker.tool.zapp:foo] + + [toolmaker.tool.zapp:bar] + """ + config = _make_config(str_config) + self.assertEqual( + _get_path(config, 'foo'), + '/somewhere/foo', + ) + self.assertEqual( + _get_path(config, 'bar'), + '/somewhere/bar', + ) + + def test_get_file_path_current(self): + """Install all in current directory""" + str_config = """ + [toolmaker.tool.defaults] + tools_directory = + tool_directory = + + [toolmaker.tool.zapp:foo] + + [toolmaker.tool.zapp:bar] + """ + config = _make_config(str_config) + self.assertEqual( + _get_path(config, 'foo'), + 'foo', + ) + self.assertEqual( + _get_path(config, 'bar'), + 'bar', + ) + + def test_get_file_path_subfolder(self): + """Install in subdirectories""" + str_config = """ + [toolmaker.tool.defaults] + tools_directory = /somewhere + + [toolmaker.tool.zapp:foo0] + + [toolmaker.tool.zapp:foo1] + tool_directory = foo0 + + [toolmaker.tool.zapp:foo2] + + [toolmaker.tool.zapp:foo3] + tool_file = foo0 + """ + config = _make_config(str_config) + self.assertEqual( + _get_path(config, 'foo0'), + '/somewhere/foo0/foo0', + ) + self.assertEqual( + _get_path(config, 'foo1'), + '/somewhere/foo0/foo1', + ) + self.assertEqual( + _get_path(config, 'foo2'), + '/somewhere/foo2/foo2', + ) + self.assertEqual( + _get_path(config, 'foo3'), + '/somewhere/foo3/foo0', + ) + + # EOF