Skip to content

Commit

Permalink
Merge pull request #35 from NeonGraal/add_config_path
Browse files Browse the repository at this point in the history
Add config path
  • Loading branch information
NeonGraal authored Apr 13, 2018
2 parents 9fe7716 + b9b4ec3 commit 38f099c
Show file tree
Hide file tree
Showing 14 changed files with 676 additions and 623 deletions.
2 changes: 1 addition & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .idea/script-venv.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

75 changes: 41 additions & 34 deletions script_venv/cli.py
Original file line number Diff line number Diff line change
@@ -1,62 +1,69 @@
# -*- coding: utf-8 -*-

"""Console script for script_venv."""

from typing import Iterable # noqa: F401
from typing import Iterable, cast # noqa: F401

import click

from .config import VenvConfig
from script_venv.factory import ConfigDependenciesImpl
from .config import VenvConfig, ConfigDependencies
from .script_venv import ScriptVenvGroup

_IGNORE_UNKNOWN = dict(ignore_unknown_options=True, )


@click.command(name="sv", cls=ScriptVenvGroup)
@click.command(name="sv", cls=ScriptVenvGroup, context_settings=_IGNORE_UNKNOWN)
@click.version_option()
def main() -> None:
@click.option('--config-search-path', '-S', type=click.STRING,
help='Path to load .sv_cfg files from')
@click.pass_context
def main(ctx, config_search_path) -> None:
"""Console script for script_venv."""
pass
if not isinstance(ctx.obj, VenvConfig):
deps = cast(ConfigDependencies, ctx.obj) or ConfigDependenciesImpl()
ctx.obj = VenvConfig(deps=deps)
if config_search_path:
ctx.obj.search_path(config_search_path)
ctx.obj.load()


@main.command(name=":register")
@click.option('--per-user', '-u', is_flag=True, help='Register in "~/.sv_cfg"')
@click.option('--is-local', '-l', is_flag=True, help='Register as local venv')
@click.option('--is-global', '-g', is_flag=True, help='Register as global venv')
@click.argument('venv', required=True)
@click.argument('package', nargs=-1)
@main.command(name=":list")
@click.pass_obj
def register_package(obj, venv: str,
package: Iterable[str],
per_user: bool,
is_local: bool,
is_global: bool) -> int: # pragma: no cover
"""Register packages and their scripts in venv"""
config = VenvConfig(deps=obj)
config.register(venv, package, per_user, is_local if per_user else not is_global)
return 0
def list_venvs(obj) -> None:
"""List known scripts and venvs"""
if not isinstance(obj, VenvConfig): # pragma: no cover
raise TypeError("ctx.obj must be a VEnvConfig")
obj.list()


@main.command(name=":create", context_settings=dict(ignore_unknown_options=True,))
@main.command(name=":create", context_settings=_IGNORE_UNKNOWN)
@click.option('--clean', '-C', is_flag=True, help='If the venv exists, clean it before applying requirements')
@click.option('--update', '-U', is_flag=True, help='Update prerequisites, requirements, and pip')
@click.argument('venv_or_script', required=True)
@click.argument('install_params', nargs=-1)
@click.pass_obj
def create_venv(obj, venv_or_script: str,
install_params: Iterable[str],
clean: bool, update: bool) -> None: # pragma: no cover
clean: bool, update: bool) -> None:
"""Create or clean venv and apply requirements
appending any install parameters provided"""
config = VenvConfig(deps=obj)
config.load(False)
config.load(True)
config.create(venv_or_script, *install_params, clean=clean, update=update)
if not isinstance(obj, VenvConfig): # pragma: no cover
raise TypeError("ctx.obj must be a VEnvConfig")
obj.create(venv_or_script, *install_params, clean=clean, update=update)


@main.command(name=":list")
@main.command(name=":register", context_settings=_IGNORE_UNKNOWN)
@click.option('--config-path', '-P', type=click.STRING)
@click.option('--venv-path', '-V', type=click.STRING)
@click.argument('venv', required=True)
@click.argument('package', nargs=-1, required=True)
@click.pass_obj
def list_venvs(obj) -> None:
"""List known scripts and venvs"""
config = VenvConfig(deps=obj)
config.load(False)
config.load(True)
config.list()
def register_package(obj, venv: str,
package: Iterable[str],
config_path: str,
venv_path: str) -> int:
"""Register packages and their scripts in venv"""
if not isinstance(obj, VenvConfig): # pragma: no cover
raise TypeError("ctx.obj must be a VEnvConfig")
obj.register(venv, package, config_path=config_path, venv_path=venv_path)
return 0
94 changes: 57 additions & 37 deletions script_venv/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

""" Config file processing """
from configparser import ConfigParser
from click import echo
from os import path

from os import getcwd, path
from pathlib import Path
from types import MappingProxyType
from typing import Mapping, Set, Dict, Iterable, Tuple, Any, IO # noqa: F401
from typing import Mapping, Set, Dict, Iterable, Tuple, Any, IO, Union # noqa: F401

from .venv import VEnv, VEnvDependencies
from .venv import VEnv, VEnvDependencies, abs_path

# noinspection SpellCheckingInspection
"""
Expand All @@ -26,11 +26,13 @@
_s = "SCRIPTS"
_p = "prerequisites"
_r = "requirements"
_l = "local"
_g = "global"
_l = "location"


class ConfigDependencies(object): # pragma: no cover
def echo(self, msg: str):
raise NotImplementedError()

def exists(self, path: Path) -> bool:
raise NotImplementedError()

Expand All @@ -51,44 +53,52 @@ class VenvConfig(object):
def __init__(self, deps: ConfigDependencies) -> None:
self.deps = deps
self.configs = set() # type: Set[str]
self._search_path = [path.join('~', '.config'), "$PARENTS", "$CWD"]
self._scripts = {} # type: Dict[str, str]
self._venvs = {} # type: Dict[str, VEnv]
self._scripts_proxy = MappingProxyType(self._scripts)
self._venvs_proxy = MappingProxyType(self._venvs)

@staticmethod
def _file_path(per_user: bool) -> Tuple[str, Path]:
config_file = (Path('~') if per_user else Path('')) / '.sv_cfg'
return config_file.as_posix(), Path(path.expanduser(str(config_file))).absolute()
def _file_path(raw_path: Union[str, Path]) -> Tuple[str, Path]:
config_file = Path(raw_path) / '.sv_cfg'
return config_file.as_posix(), abs_path(config_file)

@staticmethod
def _packages_section(config: ConfigParser, venv: str, section: str) -> Set[str]:
value = config.get(venv, section, fallback=None) or ''
return {r for r in value.splitlines() if r}

def load(self, per_user: bool) -> None:
config_file, config_file_path = self._file_path(per_user)
def _config_paths(self):
for p in self._search_path:
if p.upper() == '$PARENTS':
cwd = getcwd()
drive, full_path = path.splitdrive(cwd)
parts = full_path.split(path.sep)
for i in range(2, len(parts)):
this_path = path.join(drive + path.sep, *parts[:i])
yield path.join(path.relpath(this_path, start=cwd))
elif p.upper() == '$CWD':
yield '.'
else:
yield p

def _load_file(self, path: str):
config_file, config_file_path = self._file_path(Path(path))

if not self.deps.exists(config_file_path): # pragma: no cover
return

self.configs.add(config_file)

config = ConfigParser(allow_no_value=True)
with self.deps.read(config_file_path) as in_config:
config.read_file(in_config)

for v in config:
if v.islower():
if per_user:
is_local = config.has_option(v, _l)
else:
is_local = not config.has_option(v, _g)

new_venv = VEnv(v, self.deps.venv_deps(),
local=is_local,
new_venv = VEnv(v, self.deps.venv_deps(), path,
requirements=self._packages_section(config, v, _r),
prerequisites=self._packages_section(config, v, _p),
location=config.get(v, _l, fallback=None)
)
self._venvs.setdefault(v, new_venv)

Expand All @@ -97,28 +107,38 @@ def load(self, per_user: bool) -> None:
for s in scripts:
v = scripts[s] or s
self._scripts[s] = v
new_venv = VEnv(v, self.deps.venv_deps(), local=per_user)
new_venv = VEnv(v, self.deps.venv_deps(), path)
self._venvs.setdefault(v, new_venv)

ignored = [s for s in config.sections() if not (s.islower() or s == _s)]

if ignored:
echo("Ignored the following sections of %s: %s" % (config_file, ', '.join(sorted(ignored))))
self.deps.echo("Ignored the following sections of %s: %s" % (config_file, ', '.join(sorted(ignored))))

def search_path(self, full_path):
if isinstance(full_path, str):
self._search_path = full_path.split(path.pathsep)
elif full_path:
self._search_path = list(full_path)

def load(self) -> None:
for p in self._config_paths():
self._load_file(p)

def list(self):
echo("Configs: %s" % sorted(self.configs))
self.deps.echo("Configs: %s" % sorted(self.configs))
scripts = {} # type: Dict[str,Set[str]]
for s in self.scripts:
scripts.setdefault(self.scripts[s], set()).add(s)
for v in self.venvs:
venv = self.venvs[v]
echo(str(venv))
self.deps.echo(str(venv))
if v in scripts:
echo("\tScripts: %s" % ', '.join(sorted(scripts[v])))
self.deps.echo("\tScripts: %s" % ', '.join(sorted(scripts[v])))
if venv.prerequisites:
echo("\tPrerequisites: %s" % "\n\t\t".join(sorted(venv.prerequisites)))
self.deps.echo("\tPrerequisites: %s" % "\n\t\t".join(sorted(venv.prerequisites)))
if venv.requirements:
echo("\tRequirements: %s" % "\n\t\t".join(sorted(venv.requirements)))
self.deps.echo("\tRequirements: %s" % "\n\t\t".join(sorted(venv.requirements)))

@property
def scripts(self) -> Mapping[str, str]:
Expand All @@ -129,8 +149,10 @@ def venvs(self) -> Mapping[str, VEnv]:
return self._venvs_proxy

def register(self, name: str, packages: Iterable[str],
per_user: bool, is_local: bool) -> None:
config_file, config_file_path = self._file_path(per_user)
config_path: str = None, venv_path: str = None) -> None:
if not config_path:
config_path = self._search_path[-1]
config_file, config_file_path = self._file_path(config_path)

config = ConfigParser(allow_no_value=True)
if self.deps.exists(config_file_path):
Expand All @@ -141,20 +163,18 @@ def register(self, name: str, packages: Iterable[str],
config.add_section(_s)
requirements = self._packages_section(config, name, _r)

venv = VEnv(name, self.deps.venv_deps(),
local=is_local,
venv = VEnv(name, self.deps.venv_deps(), config_path,
requirements=requirements,
prerequisites=self._packages_section(config, name, _p))
prerequisites=self._packages_section(config, name, _p),
location=venv_path)

for p, s in self.deps.scripts(venv, packages):
echo("Registering %s from %s into %s" % (s, p, name))
config.set(_s, s, name)
self.deps.echo("Registering %s from %s into %s" % (s, p, name))
config.set(_s, s, name)

if not config.has_section(name):
config.add_section(name)
config.set(name, _r, '\n'.join(sorted(requirements | set(packages))))
if per_user == is_local:
config.set(name, _l if per_user else _g, None)

self.deps.write(config, config_file_path)

Expand All @@ -166,7 +186,7 @@ def create(self, venv_or_script: str, *extra_params: str,
v = self._scripts[venv_or_script]
venv = self._venvs[v]
else:
echo("Unable to find venv or script %s" % venv_or_script)
self.deps.echo("Unable to find venv or script %s" % venv_or_script)
return

venv.create(clean=clean, update=update)
Expand Down
7 changes: 7 additions & 0 deletions script_venv/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
""" Config file processing """
import venv

import click
from click import echo
from configparser import ConfigParser
import os
Expand All @@ -18,6 +19,9 @@ class ConfigDependenciesImpl(ConfigDependencies): # pragma: no cover
def venv_deps(self) -> VEnvDependencies:
return VEnvDependenciesImpl()

def echo(self, msg: str):
click.echo(msg)

def exists(self, path: Path) -> bool:
return path.exists()

Expand Down Expand Up @@ -55,6 +59,9 @@ class VEnvDependenciesImpl(VEnvDependencies): # pragma: no cover
def creator(self, path: Path, clear: bool = False) -> None:
venv.create(str(path), with_pip=True, clear=clear)

def echo(self, msg: str):
click.echo(msg)

def exists(self, path: Path) -> bool:
return path.exists()

Expand Down
Loading

0 comments on commit 38f099c

Please sign in to comment.