plowman 0.1.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.
@@ -0,0 +1,11 @@
1
+ # BSD 3-Clause License
2
+
3
+ Copyright © 2025 Stephanos Kuma.
4
+
5
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10
+
11
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
plowman-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,64 @@
1
+ Metadata-Version: 2.3
2
+ Name: plowman
3
+ Version: 0.1.0
4
+ Summary: Dotfile farm manager
5
+ Home-page: https://plowman.readthedocs.io/en/stable/
6
+ License: BSD-3-Clause
7
+ Keywords: dotfiles
8
+ Author: Stephanos Kuma
9
+ Author-email: "Stephanos Kuma" <stephanos@kuma.ai>
10
+ Requires-Python: >=3.10
11
+ Classifier: Development Status :: 1 - Planning
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: BSD License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Requires-Dist: dj-settings (~=8.0)
17
+ Requires-Dist: jinja2 (~=3.1)
18
+ Requires-Dist: pyutilkit (~=0.10)
19
+ Project-URL: Documentation, https://plowman.readthedocs.io/en/stable/
20
+ Project-URL: Repository, https://github.com/spapanik/plowman/
21
+ Description-Content-Type: text/markdown
22
+
23
+ # plowman: Dotfile farm manager
24
+
25
+ [![build][build_badge]][build_url]
26
+ [![lint][lint_badge]][lint_url]
27
+ [![tests][tests_badge]][tests_url]
28
+ [![license][licence_badge]][licence_url]
29
+ [![codecov][codecov_badge]][codecov_url]
30
+ [![readthedocs][readthedocs_badge]][readthedocs_url]
31
+ [![pypi][pypi_badge]][pypi_url]
32
+ [![downloads][pepy_badge]][pepy_url]
33
+ [![build automation: yam][yam_badge]][yam_url]
34
+ [![Lint: ruff][ruff_badge]][ruff_url]
35
+
36
+ Long project description and tldr goes here
37
+
38
+ ## Links
39
+
40
+ - [Documentation]
41
+ - [Changelog]
42
+
43
+ [build_badge]: https://github.com/spapanik/plowman/actions/workflows/build.yml/badge.svg
44
+ [build_url]: https://github.com/spapanik/plowman/actions/workflows/build.yml
45
+ [lint_badge]: https://github.com/spapanik/plowman/actions/workflows/lint.yml/badge.svg
46
+ [lint_url]: https://github.com/spapanik/plowman/actions/workflows/lint.yml
47
+ [tests_badge]: https://github.com/spapanik/plowman/actions/workflows/tests.yml/badge.svg
48
+ [tests_url]: https://github.com/spapanik/plowman/actions/workflows/tests.yml
49
+ [licence_badge]: https://img.shields.io/pypi/l/plowman
50
+ [licence_url]: https://plowman.readthedocs.io/en/stable/LICENSE/
51
+ [codecov_badge]: https://codecov.io/github/spapanik/plowman/graph/badge.svg?token=Q20F84BW72
52
+ [codecov_url]: https://codecov.io/github/spapanik/plowman
53
+ [readthedocs_badge]: https://readthedocs.org/projects/plowman/badge/?version=latest
54
+ [readthedocs_url]: https://plowman.readthedocs.io/en/latest/
55
+ [pypi_badge]: https://img.shields.io/pypi/v/plowman
56
+ [pypi_url]: https://pypi.org/project/plowman
57
+ [pepy_badge]: https://pepy.tech/badge/plowman
58
+ [pepy_url]: https://pepy.tech/project/plowman
59
+ [yam_badge]: https://img.shields.io/badge/build%20automation-yamk-success
60
+ [yam_url]: https://github.com/spapanik/yamk
61
+ [ruff_badge]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v1.json
62
+ [ruff_url]: https://github.com/charliermarsh/ruff
63
+ [Documentation]: https://plowman.readthedocs.io/en/stable/
64
+ [Changelog]: https://plowman.readthedocs.io/en/stable/CHANGELOG/
@@ -0,0 +1,42 @@
1
+ # plowman: Dotfile farm manager
2
+
3
+ [![build][build_badge]][build_url]
4
+ [![lint][lint_badge]][lint_url]
5
+ [![tests][tests_badge]][tests_url]
6
+ [![license][licence_badge]][licence_url]
7
+ [![codecov][codecov_badge]][codecov_url]
8
+ [![readthedocs][readthedocs_badge]][readthedocs_url]
9
+ [![pypi][pypi_badge]][pypi_url]
10
+ [![downloads][pepy_badge]][pepy_url]
11
+ [![build automation: yam][yam_badge]][yam_url]
12
+ [![Lint: ruff][ruff_badge]][ruff_url]
13
+
14
+ Long project description and tldr goes here
15
+
16
+ ## Links
17
+
18
+ - [Documentation]
19
+ - [Changelog]
20
+
21
+ [build_badge]: https://github.com/spapanik/plowman/actions/workflows/build.yml/badge.svg
22
+ [build_url]: https://github.com/spapanik/plowman/actions/workflows/build.yml
23
+ [lint_badge]: https://github.com/spapanik/plowman/actions/workflows/lint.yml/badge.svg
24
+ [lint_url]: https://github.com/spapanik/plowman/actions/workflows/lint.yml
25
+ [tests_badge]: https://github.com/spapanik/plowman/actions/workflows/tests.yml/badge.svg
26
+ [tests_url]: https://github.com/spapanik/plowman/actions/workflows/tests.yml
27
+ [licence_badge]: https://img.shields.io/pypi/l/plowman
28
+ [licence_url]: https://plowman.readthedocs.io/en/stable/LICENSE/
29
+ [codecov_badge]: https://codecov.io/github/spapanik/plowman/graph/badge.svg?token=Q20F84BW72
30
+ [codecov_url]: https://codecov.io/github/spapanik/plowman
31
+ [readthedocs_badge]: https://readthedocs.org/projects/plowman/badge/?version=latest
32
+ [readthedocs_url]: https://plowman.readthedocs.io/en/latest/
33
+ [pypi_badge]: https://img.shields.io/pypi/v/plowman
34
+ [pypi_url]: https://pypi.org/project/plowman
35
+ [pepy_badge]: https://pepy.tech/badge/plowman
36
+ [pepy_url]: https://pepy.tech/project/plowman
37
+ [yam_badge]: https://img.shields.io/badge/build%20automation-yamk-success
38
+ [yam_url]: https://github.com/spapanik/yamk
39
+ [ruff_badge]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v1.json
40
+ [ruff_url]: https://github.com/charliermarsh/ruff
41
+ [Documentation]: https://plowman.readthedocs.io/en/stable/
42
+ [Changelog]: https://plowman.readthedocs.io/en/stable/CHANGELOG/
@@ -0,0 +1,174 @@
1
+ [build-system]
2
+ requires = [
3
+ "phosphorus>=0.10",
4
+ ]
5
+ build-backend = "phosphorus.construction.api"
6
+
7
+ [project]
8
+ name = "plowman"
9
+ dynamic = [
10
+ "version",
11
+ ]
12
+
13
+ authors = [
14
+ { name = "Stephanos Kuma", email = "stephanos@kuma.ai" },
15
+ ]
16
+ license = { text = "BSD-3-Clause" }
17
+
18
+ readme = "docs/README.md"
19
+ description = "Dotfile farm manager"
20
+ keywords = [
21
+ "dotfiles",
22
+ ]
23
+ classifiers = [
24
+ "Development Status :: 1 - Planning",
25
+ "Operating System :: OS Independent",
26
+ "Programming Language :: Python :: 3 :: Only",
27
+ "License :: OSI Approved :: BSD License",
28
+ "Intended Audience :: Developers",
29
+ ]
30
+
31
+ requires-python = ">=3.10"
32
+ dependencies = [
33
+ "dj_settings~=8.0",
34
+ "jinja2~=3.1",
35
+ "pyutilkit~=0.10",
36
+ ]
37
+
38
+ [project.urls]
39
+ homepage = "https://plowman.readthedocs.io/en/stable/"
40
+ repository = "https://github.com/spapanik/plowman/"
41
+ documentation = "https://plowman.readthedocs.io/en/stable/"
42
+
43
+ [project.scripts]
44
+ plm = "plowman.__main__:main"
45
+
46
+ [dependency-groups]
47
+ dev = [
48
+ "ipdb~=0.13",
49
+ "ipython~=8.37",
50
+ "ptpython~=3.0",
51
+ { include-group = "lint" },
52
+ { include-group = "test" },
53
+ { include-group = "docs" },
54
+ ]
55
+ lint = [
56
+ "mypy~=1.18",
57
+ "ruff~=0.14",
58
+ { include-group = "test_core" },
59
+ ]
60
+ test = [
61
+ "pytest-cov~=7.0",
62
+ { include-group = "test_core" },
63
+ ]
64
+ test_core = [
65
+ "pytest~=9.0",
66
+ ]
67
+ docs = [
68
+ "mkdocs~=1.6",
69
+ "mkdocs-material~=9.6",
70
+ "mkdocs-material-extensions~=1.3",
71
+ "pygments~=2.19",
72
+ "pymdown-extensions~=10.15",
73
+ ]
74
+
75
+ [tool.phosphorus.dynamic.version]
76
+ file = "src/plowman/__version__.py"
77
+
78
+ [tool.mypy]
79
+ check_untyped_defs = true
80
+ disallow_any_decorated = true
81
+ disallow_any_explicit = true
82
+ disallow_any_expr = false # many builtins are Any
83
+ disallow_any_generics = true
84
+ disallow_any_unimported = true
85
+ disallow_incomplete_defs = true
86
+ disallow_subclassing_any = true
87
+ disallow_untyped_calls = true
88
+ disallow_untyped_decorators = true
89
+ disallow_untyped_defs = true
90
+ extra_checks = true
91
+ ignore_missing_imports = true
92
+ no_implicit_reexport = true
93
+ show_column_numbers = true
94
+ show_error_codes = true
95
+ strict_equality = true
96
+ warn_redundant_casts = true
97
+ warn_return_any = true
98
+ warn_unused_configs = true
99
+ warn_unused_ignores = true
100
+ warn_unreachable = true
101
+
102
+ [[tool.mypy.overrides]]
103
+ module = "tests.*"
104
+ disallow_any_decorated = false # mock.MagicMock is Any
105
+
106
+ [tool.ruff]
107
+ src = [
108
+ "src",
109
+ ]
110
+ target-version = "py310"
111
+
112
+ [tool.ruff.lint]
113
+ select = [
114
+ "ALL",
115
+ ]
116
+ ignore = [
117
+ "C901", # Adding a limit to complexity is too arbitrary
118
+ "COM812", # Avoid conflicts with the formatter
119
+ "D10", # Not everything needs a docstring
120
+ "D203", # Prefer `no-blank-line-before-class` (D211)
121
+ "D213", # Prefer `multi-line-summary-first-line` (D212)
122
+ "PLR09", # Adding a limit to complexity is too arbitrary
123
+ ]
124
+
125
+ [tool.ruff.lint.per-file-ignores]
126
+ "tests/**" = [
127
+ "FBT001", # Test arguments are handled by pytest
128
+ "PLR2004", # Tests should contain magic number comparisons
129
+ "S101", # Pytest needs assert statements
130
+ ]
131
+
132
+ [tool.ruff.lint.flake8-tidy-imports]
133
+ ban-relative-imports = "all"
134
+
135
+ [tool.ruff.lint.flake8-tidy-imports.banned-api]
136
+ "mock".msg = "Use unittest.mock"
137
+ "pytz".msg = "Use zoneinfo"
138
+
139
+ [tool.ruff.lint.isort]
140
+ combine-as-imports = true
141
+ forced-separate = [
142
+ "tests",
143
+ ]
144
+ split-on-trailing-comma = false
145
+
146
+ [tool.pytest]
147
+ minversion = "9.0"
148
+ strict = true
149
+ addopts = ["-ra", "-v"]
150
+ testpaths = [
151
+ "tests",
152
+ ]
153
+
154
+ [tool.coverage.run]
155
+ branch = true
156
+ source = [
157
+ "src/",
158
+ ]
159
+ data_file = ".cov_cache/coverage.dat"
160
+ omit = [
161
+ "src/**/type_defs.py",
162
+ ]
163
+
164
+ [tool.coverage.report]
165
+ exclude_also = [
166
+ "if TYPE_CHECKING:",
167
+ "raise NotImplementedError",
168
+ "raise UnreachableCodeError",
169
+ ]
170
+ fail_under = 100
171
+ precision = 2
172
+ show_missing = true
173
+ skip_covered = true
174
+ skip_empty = true
File without changes
@@ -0,0 +1,9 @@
1
+ from plowman.commands.sow import SowCommand
2
+ from plowman.lib.cli import parse_args
3
+
4
+
5
+ def main() -> None:
6
+ args = parse_args()
7
+ match args.subcommand:
8
+ case "sow":
9
+ SowCommand(verbosity=args.verbosity).run()
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
File without changes
@@ -0,0 +1,22 @@
1
+ from pathlib import Path
2
+
3
+ from dj_settings.settings import ConfigParser
4
+
5
+ from plowman.lib.constants import CONFIG_PATH
6
+ from plowman.lib.exceptions import MissingConfigError
7
+
8
+
9
+ class BaseCommand:
10
+ __slots__ = ("config",)
11
+
12
+ def __init__(self) -> None:
13
+ self.config = self._get_config()
14
+
15
+ def _get_config(self) -> dict[Path, list[str]]:
16
+ if not CONFIG_PATH.exists():
17
+ raise MissingConfigError
18
+ config = ConfigParser([CONFIG_PATH]).data["config"]
19
+ return {Path(path): granaries for path, granaries in config.items()}
20
+
21
+ def run(self) -> None:
22
+ raise NotImplementedError
@@ -0,0 +1,37 @@
1
+ from __future__ import annotations
2
+
3
+ import filecmp
4
+ import shutil
5
+ from typing import TYPE_CHECKING
6
+
7
+ from plowman.commands.base import BaseCommand
8
+ from plowman.lib.constants import HOME
9
+
10
+ if TYPE_CHECKING:
11
+ from pathlib import Path
12
+
13
+
14
+ class SowCommand(BaseCommand):
15
+ __slots__ = ("verbosity",)
16
+
17
+ def __init__(self, verbosity: int) -> None:
18
+ super().__init__()
19
+ self.verbosity = verbosity
20
+
21
+ def sow_granary(self, granary_path: Path) -> None:
22
+ for file in granary_path.rglob("*"):
23
+ if file.is_dir():
24
+ continue
25
+ farm = HOME.joinpath(file.relative_to(granary_path)).parent
26
+ farm.mkdir(exist_ok=True, parents=True)
27
+ target = farm.joinpath(file.name)
28
+ if target.exists() and filecmp.cmp(file, target, shallow=False):
29
+ continue
30
+
31
+ target.unlink(missing_ok=True)
32
+ shutil.copy2(file, target)
33
+
34
+ def run(self) -> None:
35
+ for path, granaries in self.config.items():
36
+ for granary in granaries:
37
+ self.sow_granary(path.joinpath(granary))
File without changes
@@ -0,0 +1,44 @@
1
+ import sys
2
+ from argparse import ArgumentParser
3
+ from dataclasses import dataclass
4
+ from typing import Literal
5
+
6
+ from plowman.__version__ import __version__
7
+
8
+ sys.tracebacklimit = 0
9
+
10
+
11
+ @dataclass(frozen=True, slots=True)
12
+ class PlowmanArgs:
13
+ subcommand: Literal["sow"]
14
+ verbosity: int
15
+
16
+
17
+ def parse_args() -> PlowmanArgs:
18
+ parser = ArgumentParser(prog="plowman", description="Dotfile farm manager")
19
+ parser.add_argument(
20
+ "-V",
21
+ "--version",
22
+ action="version",
23
+ version=f"%(prog)s {__version__}",
24
+ help="print the version and exit",
25
+ )
26
+
27
+ parent_parser = ArgumentParser(add_help=False)
28
+ parent_parser.add_argument(
29
+ "-v",
30
+ "--verbose",
31
+ action="count",
32
+ default=0,
33
+ dest="verbosity",
34
+ help="increase the level of verbosity",
35
+ )
36
+
37
+ subparsers = parser.add_subparsers(dest="subcommand", required=True)
38
+ subparsers.add_parser("sow", parents=[parent_parser])
39
+
40
+ args = parser.parse_args()
41
+ if args.verbosity > 0:
42
+ sys.tracebacklimit = 1000
43
+
44
+ return PlowmanArgs(subcommand=args.subcommand, verbosity=args.verbosity)
@@ -0,0 +1,4 @@
1
+ from pathlib import Path
2
+
3
+ HOME = Path.home()
4
+ CONFIG_PATH = HOME.joinpath(".config", "plowman", "config.yaml")
@@ -0,0 +1,10 @@
1
+ from plowman.lib.constants import CONFIG_PATH
2
+
3
+
4
+ class MissingConfigError(FileNotFoundError):
5
+ def __init__(self) -> None:
6
+ msg = (
7
+ "Configuration file not found. "
8
+ f"Please create a configuration file at `{CONFIG_PATH}`."
9
+ )
10
+ super().__init__(msg)
File without changes