venver 0.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
venver-0.0.0/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ Automatically activate, and deactivate virutal environments when entering or leaving directories.
2
+ Copyright (C) 2026 Narvin Singh
3
+
4
+ This program is free software: you can redistribute it and/or modify
5
+ it under the terms of the GNU General Public License as published by
6
+ the Free Software Foundation, either version 3 of the License, or
7
+ (at your option) any later version.
8
+
9
+ This program is distributed in the hope that it will be useful,
10
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ GNU General Public License for more details.
13
+
14
+ You should have received a copy of the GNU General Public License
15
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
venver-0.0.0/PKG-INFO ADDED
@@ -0,0 +1,39 @@
1
+ Metadata-Version: 2.3
2
+ Name: venver
3
+ Version: 0.0.0
4
+ Summary: Automatically activate, and deactivate virutal environments when entering or leaving directories.
5
+ Author: Narvin Singh
6
+ Author-email: Narvin Singh <Narvin.A.Singh@gmail.com>
7
+ License: Automatically activate, and deactivate virutal environments when entering or leaving directories.
8
+ Copyright (C) 2026 Narvin Singh
9
+
10
+ This program is free software: you can redistribute it and/or modify
11
+ it under the terms of the GNU General Public License as published by
12
+ the Free Software Foundation, either version 3 of the License, or
13
+ (at your option) any later version.
14
+
15
+ This program is distributed in the hope that it will be useful,
16
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
17
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
+ GNU General Public License for more details.
19
+
20
+ You should have received a copy of the GNU General Public License
21
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
22
+ Classifier: Programming Language :: Python :: 3
23
+ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
24
+ Classifier: Operating System :: OS Independent
25
+ Requires-Dist: python-docs-theme~=2025.12 ; extra == 'doc'
26
+ Requires-Dist: sphinx~=9.1 ; extra == 'doc'
27
+ Requires-Python: >=3.12
28
+ Project-URL: Homepage, https://codeberg.org/venver
29
+ Project-URL: Repository, https://codeberg.org/venver
30
+ Project-URL: Bug Tracker, https://codeberg.org/venver/issues
31
+ Provides-Extra: doc
32
+ Description-Content-Type: text/x-rst
33
+
34
+ Venver
35
+ ######
36
+
37
+ Automatically activate, and deactivate virutal environments when entering or
38
+ leaving directories.
39
+
@@ -0,0 +1,6 @@
1
+ Venver
2
+ ######
3
+
4
+ Automatically activate, and deactivate virutal environments when entering or
5
+ leaving directories.
6
+
@@ -0,0 +1,107 @@
1
+ [project]
2
+ name = "venver"
3
+ version = "0.0.0"
4
+ authors = [
5
+ {name = "Narvin Singh", email = "Narvin.A.Singh@gmail.com"}
6
+ ]
7
+ classifiers = [
8
+ "Programming Language :: Python :: 3",
9
+ "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
10
+ "Operating System :: OS Independent"
11
+ ]
12
+ description = "Automatically activate, and deactivate virutal environments when entering or leaving directories."
13
+ readme = "README.rst"
14
+ license = {file = "LICENSE"}
15
+ requires-python = ">=3.12"
16
+
17
+ dependencies = []
18
+
19
+ [project.optional-dependencies]
20
+ doc = [
21
+ "python-docs-theme~=2025.12",
22
+ "sphinx~=9.1",
23
+ ]
24
+
25
+ [dependency-groups]
26
+ dev = [
27
+ "coverage~=7.13",
28
+ "mypy~=1.19",
29
+ "pre-commit~=4.5",
30
+ "pytest~=9.0",
31
+ "ruff~=0.15.1",
32
+ "sphinx-lint~=1.0",
33
+ "yamllint~=1.38",
34
+ ]
35
+
36
+ [project.urls]
37
+ Homepage = "https://codeberg.org/venver"
38
+ Repository = "https://codeberg.org/venver"
39
+ "Bug Tracker" = "https://codeberg.org/venver/issues"
40
+
41
+ [build-system]
42
+ requires = ["uv_build>=0.10.2,<0.11.0"]
43
+ build-backend = "uv_build"
44
+
45
+ [tool.mypy]
46
+ python_version = "3.14"
47
+ strict = true
48
+
49
+ [tool.ruff]
50
+ line-length = 100
51
+ target-version = "py314"
52
+
53
+ [tool.ruff.format]
54
+ skip-magic-trailing-comma = true
55
+
56
+ [tool.ruff.lint]
57
+ select = ["ALL"]
58
+ ignore = [
59
+ # flake8-commas
60
+ "COM812", # Trailing comma missing
61
+
62
+ # pydocstyle
63
+ "D203", # 1 blank line required before class docstring
64
+ "D205", # 1 blank line required between summary line and description
65
+ "D212", # Multi-line docstring summary should start at the first line
66
+ "D400", # First line should end with a period
67
+ "D415", # First line should end with a period, question mark, or exclamation point
68
+
69
+ # Error
70
+ "E501", # Line too long ({width} > {limit})
71
+ "E731", # Do not assign a lambda expression, use a def
72
+
73
+ # eradicate
74
+ "ERA001", # Found commented-out code
75
+
76
+ # flake8-fixme
77
+ "FIX002", # Line contains TODO, consider resolving the issue
78
+
79
+ # flake8-implicit-str-concat
80
+ "ISC001", # Implicitly concatenated string literals on one line
81
+
82
+ # flake8-pytest-style
83
+ "PT009", # Use a regular assert instead of unittest-style {assertion}
84
+ "PT027", # Use pytest.raises instead of unittest-style {assertion}
85
+
86
+ # flake8-todos
87
+ "TD003", # Missing issue link on the line following this TODO
88
+ ]
89
+ fixable = [
90
+ "F401", # unused import
91
+ "F541", # f-string without any placeholders
92
+
93
+ # isort
94
+ "I001", # Import block is un-sorted or un-formatted
95
+
96
+ # Ruff-specific rules
97
+ "RUF022", # `__all__` is not sorted
98
+ "RUF100", # Unused noqa directive
99
+ ]
100
+ isort.split-on-trailing-comma = false
101
+ pydocstyle.convention = "google"
102
+
103
+ [tool.ruff.lint.per-file-ignores]
104
+ "test/*" = [
105
+ "INP001", # File is part of an implicit namespace package
106
+ "S101", # Use of `assert` detected
107
+ ]
@@ -0,0 +1,8 @@
1
+ """The main package."""
2
+
3
+ from importlib import metadata
4
+
5
+ __project__ = "Venver"
6
+ __distribution__ = "venver"
7
+ __version__ = metadata.version("venver")
8
+ __authors__ = ["Narvin Singh"]
@@ -0,0 +1,74 @@
1
+ """The package entry point."""
2
+
3
+ import argparse
4
+ import logging
5
+
6
+ import venver as pkg
7
+
8
+ from . import venver
9
+
10
+
11
+ def apply_name(
12
+ name: str, desc: str, url: str, author: list[str] | None, *, unsafe: bool
13
+ ) -> None: # pragma: no cover
14
+ """Stub for apply"""
15
+ logging.getLogger(__name__).debug("applying name: %s and description: %s", name, desc)
16
+ venver.apply(name, description=desc, url=url, authors=author, unsafe=unsafe)
17
+
18
+
19
+ def revert_apply_name(*, unsafe: bool) -> None: # pragma: no cover
20
+ """Stub for revert_apply_name"""
21
+ logging.getLogger(__name__).debug("restoring project")
22
+ venver.restore(unsafe=unsafe)
23
+
24
+
25
+ def main(*, argv: list[str] | None = None, namespace: argparse.Namespace | None = None) -> int:
26
+ """The entry point function."""
27
+ parser = argparse.ArgumentParser(
28
+ prog="venver",
29
+ description="Automatically activate, and deactivate virutal environments when entering or leaving directories",
30
+ )
31
+ parser.add_argument("-v", "--version", action="version", version=f"venver {pkg.__version__}")
32
+ parser_common = argparse.ArgumentParser(add_help=False)
33
+ parser_common.add_argument(
34
+ "-l",
35
+ "--log-level",
36
+ choices=logging.getLevelNamesMapping().keys(),
37
+ default="DEBUG",
38
+ help="set the logging level",
39
+ )
40
+ parser_common.add_argument(
41
+ "--unsafe",
42
+ action="store_true",
43
+ help="if not set, then environment variables with tool paths must be defined",
44
+ )
45
+ subparsers = parser.add_subparsers(title="commands")
46
+ parser_apply = subparsers.add_parser(
47
+ "apply", parents=[parser_common], help="apply data to the project"
48
+ )
49
+ parser_apply.add_argument("-n", "--name", required=True, help="the project name to apply")
50
+ parser_apply.add_argument(
51
+ "-d", "--desc", required=True, help="the project description to apply"
52
+ )
53
+ parser_apply.add_argument("-a", "--author", action="append", help="a project author to apply")
54
+ parser_apply.add_argument(
55
+ "-u",
56
+ "--url",
57
+ default="https://codeberg.org",
58
+ help="the base URL of the project's remote repository to apply",
59
+ )
60
+ parser_apply.set_defaults(func=apply_name)
61
+ parser_restore = subparsers.add_parser(
62
+ "restore", parents=[parser_common], help="restore the project to its original state"
63
+ )
64
+ parser_restore.set_defaults(func=revert_apply_name)
65
+ args = parser.parse_args(argv, namespace)
66
+ logging.basicConfig(level=args.log_level)
67
+ logger = logging.getLogger(__name__)
68
+ logger.debug("args: %s", args)
69
+ args.func(**{k: v for k, v in args.__dict__.items() if k not in {"func", "log_level"}})
70
+ return 0
71
+
72
+
73
+ if __name__ == "__main__": # pragma: no cover
74
+ raise SystemExit(main())
File without changes
@@ -0,0 +1,150 @@
1
+ """The main module."""
2
+
3
+ import configparser
4
+ import functools
5
+ import json
6
+ import os
7
+ import pathlib
8
+ import shutil
9
+ import subprocess
10
+ import tomllib
11
+ from typing import TYPE_CHECKING
12
+
13
+ if TYPE_CHECKING:
14
+ from collections.abc import Mapping
15
+
16
+
17
+ @functools.lru_cache(1)
18
+ def _projroot() -> pathlib.Path: # pragma: no cover
19
+ return pathlib.Path(__file__).parent.parent.parent
20
+
21
+
22
+ @functools.lru_cache(1)
23
+ def _load_env() -> configparser.ConfigParser: # pragma: no cover
24
+ parser = configparser.ConfigParser(allow_unnamed_section=True)
25
+ parser.read(_projroot() / ".env")
26
+ return parser
27
+
28
+
29
+ def _tool_path(tool: str, default: str | None) -> str: # pragma: no cover
30
+ _load_env()
31
+ key = f"{tool.upper()}_PATH"
32
+ msg = f"set the environment variable `{key}`, or add it to your `.env` file"
33
+ path = os.getenv(key)
34
+ if not path:
35
+ parser = _load_env()
36
+ try:
37
+ path = parser.get(configparser.UNNAMED_SECTION, key)
38
+ except configparser.NoOptionError as exc:
39
+ raise RuntimeError(msg) from exc
40
+ if path:
41
+ return path
42
+ if default is None:
43
+ raise RuntimeError(msg)
44
+ return default
45
+
46
+
47
+ @functools.lru_cache(2)
48
+ def _git_path(*, unsafe: bool) -> str: # pragma: no cover
49
+ return _tool_path("git", "git" if unsafe else None)
50
+
51
+
52
+ @functools.lru_cache(2)
53
+ def _uv_path(*, unsafe: bool) -> str: # pragma: no cover
54
+ return _tool_path("uv", "uv" if unsafe else None)
55
+
56
+
57
+ def _replace_file(path: pathlib.Path, content: str) -> None: # pragma: no cover
58
+ with path.open("w") as f:
59
+ f.write(content)
60
+
61
+
62
+ def _update_file_raw(path: pathlib.Path, spec: Mapping[str, str]) -> None: # pragma: no cover
63
+ with path.open("r+") as f:
64
+ template = f.read()
65
+ f.seek(0)
66
+ content = functools.reduce(
67
+ lambda acc, curr: acc.replace(curr[0], curr[1]), spec.items(), template
68
+ )
69
+ f.write(content)
70
+ f.truncate()
71
+
72
+
73
+ def _update_file(path: pathlib.Path, spec: Mapping[str, str]) -> None: # pragma: no cover
74
+ _update_file_raw(path, {f"{{{{{k.upper()}}}}}": v for k, v in spec.items()})
75
+
76
+
77
+ def _rename_file(path: pathlib.Path, name: str) -> None: # pragma: no cover
78
+ path.rename(path.parent / name)
79
+
80
+
81
+ def _get_authors_str(authors: list[str] | None, *, unsafe: bool) -> str | None: # pragma: no cover
82
+ match authors:
83
+ case [] | None:
84
+ git = _git_path(unsafe=unsafe)
85
+ res = subprocess.run( # noqa: S603
86
+ [git, "config", "get", "user.name"], capture_output=True, check=False
87
+ )
88
+ author = res.stdout.decode().strip()
89
+ return json.dumps([author]) or None
90
+ case _:
91
+ return json.dumps(authors)
92
+
93
+
94
+ def apply( # pragma: no cover
95
+ name: str, *, description: str, url: str, authors: list[str] | None = None, unsafe: bool = False
96
+ ) -> None:
97
+ """Apply data to the project."""
98
+ uv = _uv_path(unsafe=unsafe)
99
+ authors_str = _get_authors_str(authors, unsafe=unsafe)
100
+ projroot = _projroot()
101
+ proper_name = name.title()
102
+ _update_file(projroot / "LICENSE", {"description": description})
103
+ _update_file(
104
+ projroot / "pyproject.toml", {"project": name, "description": description, "url": url}
105
+ )
106
+ _update_file_raw(projroot / "pyproject.toml", {"pypireg": name})
107
+ _replace_file(
108
+ projroot / "README.rst", f"{proper_name}\n{'#' * len(proper_name)}\n\n{description}.\n\n"
109
+ )
110
+ _update_file(projroot / "doc" / "conf.py", {"project": name})
111
+ _update_file(projroot / "doc" / "index.rst", {"project": name})
112
+ _update_file(
113
+ projroot / "src" / "pypireg" / "__init__.py",
114
+ {
115
+ "project": proper_name,
116
+ "distribution": name,
117
+ "authors": json.dumps(authors) if authors is not None else "None",
118
+ },
119
+ )
120
+ _update_file_raw(
121
+ projroot / "src" / "pypireg" / "__init__.py",
122
+ {"pypireg": name, "__authors__ = None": f"__authors__ = {authors_str}"},
123
+ )
124
+ _update_file(
125
+ projroot / "src" / "pypireg" / "__main__.py", {"project": name, "description": description}
126
+ )
127
+ _update_file_raw(projroot / "src" / "pypireg" / "__main__.py", {"pypireg": name})
128
+ _update_file_raw(projroot / "test" / "pypireg" / "test_main.py", {"pypireg": name})
129
+ _rename_file(projroot / "src" / "pypireg" / "pypireg.py", f"{name}.py")
130
+ _rename_file(projroot / "src" / "pypireg", name)
131
+ _rename_file(projroot / "test" / "pypireg", name)
132
+ shutil.rmtree(projroot / ".venv")
133
+ subprocess.run([uv, "sync"], cwd=projroot, check=False) # noqa: S603
134
+ subprocess.run([uv, "run", "ruff", "format"], cwd=projroot, check=False) # noqa: S603
135
+
136
+
137
+ def restore(*, unsafe: bool = False) -> None: # pragma: no cover
138
+ """Restore the project to its original state."""
139
+ git = _git_path(unsafe=unsafe)
140
+ uv = _uv_path(unsafe=unsafe)
141
+ projroot = _projroot()
142
+ with pathlib.Path(projroot / "pyproject.toml").open("rb") as f:
143
+ pyproj = tomllib.load(f)
144
+ name = pyproj["project"]["name"]
145
+ _rename_file(projroot / "src" / name / f"{name}.py", "pypireg.py")
146
+ _rename_file(projroot / "src" / name, "pypireg")
147
+ _rename_file(projroot / "test" / name, "pypireg")
148
+ subprocess.run([git, "restore", "."], cwd=projroot, check=False) # noqa: S603
149
+ shutil.rmtree(projroot / ".venv")
150
+ subprocess.run([uv, "sync"], cwd=projroot, check=False) # noqa: S603