baldwin 0.0.7__tar.gz → 0.0.9__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.
Potentially problematic release.
This version of baldwin might be problematic. Click here for more details.
- baldwin-0.0.9/LICENSE.txt +18 -0
- {baldwin-0.0.7 → baldwin-0.0.9}/PKG-INFO +13 -4
- {baldwin-0.0.7 → baldwin-0.0.9}/README.md +4 -1
- baldwin-0.0.9/baldwin/__init__.py +4 -0
- baldwin-0.0.9/baldwin/__main__.py +6 -0
- {baldwin-0.0.7 → baldwin-0.0.9}/baldwin/lib.py +49 -16
- {baldwin-0.0.7 → baldwin-0.0.9}/baldwin/main.py +27 -23
- baldwin-0.0.9/baldwin/py.typed +0 -0
- baldwin-0.0.9/baldwin/utils.py +46 -0
- baldwin-0.0.9/man/baldwin.1 +151 -0
- {baldwin-0.0.7 → baldwin-0.0.9}/pyproject.toml +70 -41
- baldwin-0.0.9/tests/conftest.py +24 -0
- baldwin-0.0.9/tests/pyproject.toml +10 -0
- baldwin-0.0.9/tests/test_commit.py +101 -0
- baldwin-0.0.9/tests/test_format.py +85 -0
- baldwin-0.0.9/tests/test_git_wrapper.py +25 -0
- baldwin-0.0.9/tests/test_info.py +19 -0
- baldwin-0.0.9/tests/test_init.py +81 -0
- baldwin-0.0.9/tests/test_install_units.py +27 -0
- baldwin-0.0.7/LICENSE.txt +0 -21
- baldwin-0.0.7/baldwin/__init__.py +0 -2
- {baldwin-0.0.7 → baldwin-0.0.9}/baldwin/resources/default_gitattributes.txt +0 -0
- {baldwin-0.0.7 → baldwin-0.0.9}/baldwin/resources/default_gitignore.txt +0 -0
- {baldwin-0.0.7 → baldwin-0.0.9}/baldwin/resources/prettier.config.json +0 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 baldwin authors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
|
6
|
+
associated documentation files (the "Software"), to deal in the Software without restriction,
|
|
7
|
+
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
|
8
|
+
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all copies or
|
|
12
|
+
substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
|
15
|
+
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
16
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
17
|
+
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
|
|
18
|
+
OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -1,30 +1,37 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: baldwin
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.9
|
|
4
4
|
Summary: Simple tracking of your home directory with easy-to-read diffs.
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: command line,file management,git,version control
|
|
7
7
|
Author: Andrew Udvare
|
|
8
8
|
Author-email: audvare@gmail.com
|
|
9
|
-
Requires-Python: >=3.
|
|
9
|
+
Requires-Python: >=3.10,<3.14
|
|
10
10
|
Classifier: Development Status :: 2 - Pre-Alpha
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
12
12
|
Classifier: License :: OSI Approved :: MIT License
|
|
13
13
|
Classifier: Programming Language :: Python
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
17
|
Classifier: Programming Language :: Python :: 3.13
|
|
16
18
|
Classifier: Typing :: Typed
|
|
17
19
|
Requires-Dist: binaryornot (>=0.4.4,<0.5.0)
|
|
18
20
|
Requires-Dist: click (>=8.1.8,<9.0.0)
|
|
21
|
+
Requires-Dist: colorlog (>=6.9.0,<7.0.0)
|
|
19
22
|
Requires-Dist: gitpython (>=3.1.44,<4.0.0)
|
|
20
23
|
Requires-Dist: platformdirs (>=4.3.6,<5.0.0)
|
|
24
|
+
Requires-Dist: tomlkit (>=0.13.2,<0.14.0)
|
|
25
|
+
Requires-Dist: typing-extensions (>=4.13.2,<5.0.0)
|
|
21
26
|
Project-URL: Documentation, https://baldwin.readthedocs.org
|
|
27
|
+
Project-URL: Homepage, https://tatsh.github.io/baldwin/
|
|
28
|
+
Project-URL: Issues, https://github.com/Tatsh/baldwin/issues
|
|
22
29
|
Project-URL: Repository, https://github.com/Tatsh/baldwin
|
|
23
|
-
Project-URL: issues, https://github.com/Tatsh/baldwin/issues
|
|
24
30
|
Description-Content-Type: text/markdown
|
|
25
31
|
|
|
26
32
|
# Simple home directory versioning
|
|
27
33
|
|
|
34
|
+
[](https://github.com/pre-commit/pre-commit)
|
|
28
35
|
[](https://github.com/Tatsh/baldwin/actions/workflows/qa.yml)
|
|
29
36
|
[](https://github.com/Tatsh/baldwin/actions/workflows/tests.yml)
|
|
30
37
|
[](https://coveralls.io/github/Tatsh/baldwin?branch=master)
|
|
@@ -32,7 +39,9 @@ Description-Content-Type: text/markdown
|
|
|
32
39
|

|
|
33
40
|

|
|
34
41
|

|
|
35
|
-
](https://img.shields.io/github/commits-since/Tatsh/baldwin/v0.0.9/master)
|
|
43
|
+
|
|
44
|
+
[](https://bsky.app/profile/tatsh.bsky.social)
|
|
36
45
|
|
|
37
46
|
This is a conversion of my simple scripts to version my home directory with very specific excludes
|
|
38
47
|
and formatting every file upon commit so that readable diffs can be generated.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# Simple home directory versioning
|
|
2
2
|
|
|
3
|
+
[](https://github.com/pre-commit/pre-commit)
|
|
3
4
|
[](https://github.com/Tatsh/baldwin/actions/workflows/qa.yml)
|
|
4
5
|
[](https://github.com/Tatsh/baldwin/actions/workflows/tests.yml)
|
|
5
6
|
[](https://coveralls.io/github/Tatsh/baldwin?branch=master)
|
|
@@ -7,7 +8,9 @@
|
|
|
7
8
|

|
|
8
9
|

|
|
9
10
|

|
|
10
|
-
](https://img.shields.io/github/commits-since/Tatsh/baldwin/v0.0.9/master)
|
|
12
|
+
|
|
13
|
+
[](https://bsky.app/profile/tatsh.bsky.social)
|
|
11
14
|
|
|
12
15
|
This is a conversion of my simple scripts to version my home directory with very specific excludes
|
|
13
16
|
and formatting every file upon commit so that readable diffs can be generated.
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
"""Baldwin library."""
|
|
2
|
-
from
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
3
4
|
from dataclasses import dataclass
|
|
4
|
-
from datetime import
|
|
5
|
+
from datetime import datetime, timezone
|
|
5
6
|
from importlib import resources
|
|
6
7
|
from itertools import chain
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
from shlex import quote
|
|
9
10
|
from shutil import which
|
|
10
|
-
from typing import Literal
|
|
11
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
11
12
|
import logging
|
|
12
13
|
import os
|
|
13
14
|
import subprocess as sp
|
|
@@ -15,6 +16,10 @@ import subprocess as sp
|
|
|
15
16
|
from binaryornot.check import is_binary
|
|
16
17
|
from git import Actor, Repo
|
|
17
18
|
import platformdirs
|
|
19
|
+
import tomlkit
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from collections.abc import Iterable
|
|
18
23
|
|
|
19
24
|
log = logging.getLogger(__name__)
|
|
20
25
|
|
|
@@ -60,23 +65,31 @@ def init() -> None:
|
|
|
60
65
|
|
|
61
66
|
def auto_commit() -> None:
|
|
62
67
|
"""Automated commit of changed and untracked files."""
|
|
68
|
+
def can_open(file: Path) -> bool:
|
|
69
|
+
"""Check if a file can be opened."""
|
|
70
|
+
try:
|
|
71
|
+
with file.open('rb'):
|
|
72
|
+
pass
|
|
73
|
+
except OSError:
|
|
74
|
+
return False
|
|
75
|
+
return True
|
|
76
|
+
|
|
63
77
|
repo = get_repo()
|
|
64
|
-
diff_items = [Path.home() / e.a_path for e in repo.index.diff(None)
|
|
65
|
-
if e.a_path is not None] # pragma: no cover
|
|
78
|
+
diff_items = [Path.home() / e.a_path for e in repo.index.diff(None) if e.a_path is not None]
|
|
66
79
|
items_to_add = [
|
|
67
80
|
*[p for p in diff_items if p.exists()], *[
|
|
68
|
-
x for x in (Path.home() / y
|
|
69
|
-
|
|
81
|
+
x for x in (Path.home() / y for y in repo.untracked_files)
|
|
82
|
+
if can_open(x) and x.is_file() and not is_binary(str(x))
|
|
70
83
|
]
|
|
71
84
|
]
|
|
72
|
-
items_to_remove = [p for p in diff_items if not p.exists()]
|
|
85
|
+
items_to_remove = [p for p in diff_items if not p.exists()]
|
|
73
86
|
if items_to_add:
|
|
74
87
|
format_(items_to_add)
|
|
75
88
|
repo.index.add(items_to_add)
|
|
76
89
|
if items_to_remove:
|
|
77
90
|
repo.index.remove(items_to_remove)
|
|
78
91
|
if items_to_add or items_to_remove or len(repo.index.diff('HEAD')) > 0:
|
|
79
|
-
repo.index.commit(f'Automatic commit @ {datetime.now(tz=
|
|
92
|
+
repo.index.commit(f'Automatic commit @ {datetime.now(tz=timezone.utc).isoformat()}',
|
|
80
93
|
committer=Actor('Auto-commiter', 'hgit@tat.sh'))
|
|
81
94
|
|
|
82
95
|
|
|
@@ -95,7 +108,13 @@ def repo_info() -> RepoInfo:
|
|
|
95
108
|
|
|
96
109
|
|
|
97
110
|
def install_units() -> None:
|
|
98
|
-
"""
|
|
111
|
+
"""
|
|
112
|
+
Install systemd units for automatic committing.
|
|
113
|
+
|
|
114
|
+
Raises
|
|
115
|
+
------
|
|
116
|
+
FileNotFoundError
|
|
117
|
+
"""
|
|
99
118
|
bw = which('bw')
|
|
100
119
|
if not bw:
|
|
101
120
|
raise FileNotFoundError
|
|
@@ -104,8 +123,9 @@ def install_units() -> None:
|
|
|
104
123
|
Description=Home directory VCS commit
|
|
105
124
|
|
|
106
125
|
[Service]
|
|
107
|
-
|
|
126
|
+
Environment=NO_COLOR=1
|
|
108
127
|
ExecStart={bw} auto-commit
|
|
128
|
+
Type=oneshot
|
|
109
129
|
""")
|
|
110
130
|
log.debug('Wrote to `%s`.', service_file)
|
|
111
131
|
timer_file = Path('~/.config/systemd/user/home-vcs.timer').expanduser()
|
|
@@ -136,6 +156,14 @@ def get_git_path() -> Path:
|
|
|
136
156
|
return platformdirs.user_data_path('home-git', roaming=True)
|
|
137
157
|
|
|
138
158
|
|
|
159
|
+
def get_config() -> tomlkit.TOMLDocument | dict[str, Any]:
|
|
160
|
+
"""Get the configuration (TOML file)."""
|
|
161
|
+
config_file = platformdirs.user_config_path('baldwin', roaming=True) / 'config.toml'
|
|
162
|
+
if not config_file.exists():
|
|
163
|
+
return {}
|
|
164
|
+
return tomlkit.loads(config_file.read_text())
|
|
165
|
+
|
|
166
|
+
|
|
139
167
|
def get_repo() -> Repo:
|
|
140
168
|
"""
|
|
141
169
|
Get a :py:class:`git.Repo` object.
|
|
@@ -168,13 +196,18 @@ def format_(filenames: Iterable[Path | str] | None = None,
|
|
|
168
196
|
*(x for x in (Path.home() / y for y in repo.untracked_files)
|
|
169
197
|
if x.is_file() and not is_binary(str(x))))
|
|
170
198
|
if not (filenames := list(filenames)):
|
|
199
|
+
log.debug('No files to format.')
|
|
171
200
|
return
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
201
|
+
if not (prettier := which('prettier')):
|
|
202
|
+
log.debug('Prettier not found in PATH.')
|
|
203
|
+
return
|
|
204
|
+
with resources.path('baldwin.resources', 'prettier.config.json') as default_config_file:
|
|
205
|
+
config_file = get_config().get('baldwin', {
|
|
206
|
+
'prettier_config': str(default_config_file)
|
|
207
|
+
}).get('prettier_config')
|
|
175
208
|
# Detect plugins
|
|
176
|
-
node_modules_path = (Path(prettier).resolve(strict=True).parent /
|
|
177
|
-
'
|
|
209
|
+
node_modules_path = (Path(prettier).resolve(strict=True).parent /
|
|
210
|
+
'../..').resolve(strict=True)
|
|
178
211
|
cmd_prefix = (prettier, '--config', str(config_file), '--write',
|
|
179
212
|
'--no-error-on-unmatched-pattern', '--ignore-unknown', '--log-level',
|
|
180
213
|
log_level, *chain(*(('--plugin', str(fp))
|
|
@@ -1,28 +1,32 @@
|
|
|
1
|
+
"""Commands."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
1
4
|
import logging
|
|
2
5
|
|
|
3
6
|
import click
|
|
4
7
|
|
|
5
8
|
from .lib import (
|
|
6
|
-
auto_commit,
|
|
9
|
+
auto_commit as auto_commit_,
|
|
7
10
|
format_,
|
|
8
|
-
git,
|
|
9
|
-
init,
|
|
10
|
-
install_units,
|
|
11
|
+
git as git_,
|
|
12
|
+
init as init_,
|
|
13
|
+
install_units as install_units_,
|
|
11
14
|
repo_info,
|
|
12
15
|
set_git_env_vars,
|
|
13
16
|
)
|
|
17
|
+
from .utils import setup_logging
|
|
14
18
|
|
|
15
19
|
log = logging.getLogger(__name__)
|
|
16
20
|
|
|
17
|
-
__all__ = ('
|
|
21
|
+
__all__ = ('baldwin', 'git')
|
|
18
22
|
|
|
19
23
|
|
|
20
24
|
@click.group(context_settings={'help_option_names': ('-h', '--help')})
|
|
21
25
|
@click.option('-d', '--debug', help='Enable debug logging.', is_flag=True)
|
|
22
|
-
def
|
|
26
|
+
def baldwin(*, debug: bool = False) -> None:
|
|
23
27
|
"""Manage a home directory with Git."""
|
|
24
28
|
set_git_env_vars()
|
|
25
|
-
|
|
29
|
+
setup_logging(debug=debug)
|
|
26
30
|
|
|
27
31
|
|
|
28
32
|
@click.command(context_settings={
|
|
@@ -30,31 +34,31 @@ def baldwin_main(*, debug: bool = False) -> None:
|
|
|
30
34
|
'ignore_unknown_options': True
|
|
31
35
|
})
|
|
32
36
|
@click.argument('args', nargs=-1, type=click.UNPROCESSED)
|
|
33
|
-
def
|
|
37
|
+
def git(args: tuple[str, ...]) -> None:
|
|
34
38
|
"""Wrap git with git-dir and work-tree passed."""
|
|
35
|
-
|
|
39
|
+
git_(args)
|
|
36
40
|
|
|
37
41
|
|
|
38
42
|
@click.command(context_settings={'help_option_names': ('-h', '--help')})
|
|
39
|
-
def
|
|
43
|
+
def init() -> None:
|
|
40
44
|
"""Start tracking a home directory."""
|
|
41
|
-
|
|
45
|
+
init_()
|
|
42
46
|
|
|
43
47
|
|
|
44
48
|
@click.command(context_settings={'help_option_names': ('-h', '--help')})
|
|
45
|
-
def
|
|
49
|
+
def auto_commit() -> None:
|
|
46
50
|
"""Automated commit of changed and untracked files."""
|
|
47
|
-
|
|
51
|
+
auto_commit_()
|
|
48
52
|
|
|
49
53
|
|
|
50
54
|
@click.command(context_settings={'help_option_names': ('-h', '--help')})
|
|
51
|
-
def
|
|
55
|
+
def format() -> None: # noqa: A001
|
|
52
56
|
"""Format changed and untracked files."""
|
|
53
57
|
format_()
|
|
54
58
|
|
|
55
59
|
|
|
56
60
|
@click.command(context_settings={'help_option_names': ('-h', '--help')})
|
|
57
|
-
def
|
|
61
|
+
def info() -> None:
|
|
58
62
|
"""Get basic information about the repository."""
|
|
59
63
|
data = repo_info()
|
|
60
64
|
click.echo(f'git-dir path: {data.git_dir_path}')
|
|
@@ -62,14 +66,14 @@ def info_main() -> None:
|
|
|
62
66
|
|
|
63
67
|
|
|
64
68
|
@click.command(context_settings={'help_option_names': ('-h', '--help')})
|
|
65
|
-
def
|
|
69
|
+
def install_units() -> None:
|
|
66
70
|
"""Install systemd units for automatic committing."""
|
|
67
|
-
|
|
71
|
+
install_units_()
|
|
68
72
|
|
|
69
73
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
baldwin.add_command(auto_commit)
|
|
75
|
+
baldwin.add_command(format)
|
|
76
|
+
baldwin.add_command(git)
|
|
77
|
+
baldwin.add_command(info)
|
|
78
|
+
baldwin.add_command(init)
|
|
79
|
+
baldwin.add_command(install_units)
|
|
File without changes
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Utilities."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
import logging.config
|
|
6
|
+
|
|
7
|
+
log = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def setup_logging(*,
|
|
11
|
+
debug: bool = False,
|
|
12
|
+
force_color: bool = False,
|
|
13
|
+
no_color: bool = False) -> None: # pragma: no cover
|
|
14
|
+
"""Set up logging configuration."""
|
|
15
|
+
logging.config.dictConfig({
|
|
16
|
+
'disable_existing_loggers': True,
|
|
17
|
+
'root': {
|
|
18
|
+
'level': 'DEBUG' if debug else 'INFO',
|
|
19
|
+
'handlers': ['console'],
|
|
20
|
+
},
|
|
21
|
+
'formatters': {
|
|
22
|
+
'default': {
|
|
23
|
+
'()': 'colorlog.ColoredFormatter',
|
|
24
|
+
'force_color': force_color,
|
|
25
|
+
'format': (
|
|
26
|
+
'%(light_cyan)s%(asctime)s%(reset)s | %(log_color)s%(levelname)-8s%(reset)s | '
|
|
27
|
+
'%(light_green)s%(name)s%(reset)s:%(light_red)s%(funcName)s%(reset)s:'
|
|
28
|
+
'%(blue)s%(lineno)d%(reset)s - %(message)s'),
|
|
29
|
+
'no_color': no_color,
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
'handlers': {
|
|
33
|
+
'console': {
|
|
34
|
+
'class': 'colorlog.StreamHandler',
|
|
35
|
+
'formatter': 'default',
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
'loggers': {
|
|
39
|
+
'baldwin': {
|
|
40
|
+
'level': 'DEBUG' if debug else 'INFO',
|
|
41
|
+
'handlers': ['console'],
|
|
42
|
+
'propagate': False,
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
'version': 1
|
|
46
|
+
})
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
.\" Man page generated from reStructuredText.
|
|
2
|
+
.
|
|
3
|
+
.
|
|
4
|
+
.nr rst2man-indent-level 0
|
|
5
|
+
.
|
|
6
|
+
.de1 rstReportMargin
|
|
7
|
+
\\$1 \\n[an-margin]
|
|
8
|
+
level \\n[rst2man-indent-level]
|
|
9
|
+
level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
|
10
|
+
-
|
|
11
|
+
\\n[rst2man-indent0]
|
|
12
|
+
\\n[rst2man-indent1]
|
|
13
|
+
\\n[rst2man-indent2]
|
|
14
|
+
..
|
|
15
|
+
.de1 INDENT
|
|
16
|
+
.\" .rstReportMargin pre:
|
|
17
|
+
. RS \\$1
|
|
18
|
+
. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
|
|
19
|
+
. nr rst2man-indent-level +1
|
|
20
|
+
.\" .rstReportMargin post:
|
|
21
|
+
..
|
|
22
|
+
.de UNINDENT
|
|
23
|
+
. RE
|
|
24
|
+
.\" indent \\n[an-margin]
|
|
25
|
+
.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
|
26
|
+
.nr rst2man-indent-level -1
|
|
27
|
+
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
|
28
|
+
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
|
29
|
+
..
|
|
30
|
+
.TH "BALDWIN" "1" "Apr 12, 2025" "0.0.7" "baldwin"
|
|
31
|
+
.SH NAME
|
|
32
|
+
baldwin \- baldwin v0.0.7
|
|
33
|
+
.sp
|
|
34
|
+
This is a conversion of my simple scripts to version my home directory with very specific excludes
|
|
35
|
+
and formatting every file upon commit so that readable diffs can be generated.
|
|
36
|
+
.SH COMMANDS
|
|
37
|
+
.SS bw
|
|
38
|
+
.sp
|
|
39
|
+
Manage a home directory with Git.
|
|
40
|
+
.INDENT 0.0
|
|
41
|
+
.INDENT 3.5
|
|
42
|
+
.sp
|
|
43
|
+
.EX
|
|
44
|
+
bw [OPTIONS] COMMAND [ARGS]...
|
|
45
|
+
.EE
|
|
46
|
+
.UNINDENT
|
|
47
|
+
.UNINDENT
|
|
48
|
+
.sp
|
|
49
|
+
Options
|
|
50
|
+
.INDENT 0.0
|
|
51
|
+
.TP
|
|
52
|
+
.B \-d, \-\-debug
|
|
53
|
+
Enable debug logging.
|
|
54
|
+
.UNINDENT
|
|
55
|
+
.SS auto\-commit
|
|
56
|
+
.sp
|
|
57
|
+
Automated commit of changed and untracked files.
|
|
58
|
+
.INDENT 0.0
|
|
59
|
+
.INDENT 3.5
|
|
60
|
+
.sp
|
|
61
|
+
.EX
|
|
62
|
+
bw auto\-commit [OPTIONS]
|
|
63
|
+
.EE
|
|
64
|
+
.UNINDENT
|
|
65
|
+
.UNINDENT
|
|
66
|
+
.SS format
|
|
67
|
+
.sp
|
|
68
|
+
Format changed and untracked files.
|
|
69
|
+
.INDENT 0.0
|
|
70
|
+
.INDENT 3.5
|
|
71
|
+
.sp
|
|
72
|
+
.EX
|
|
73
|
+
bw format [OPTIONS]
|
|
74
|
+
.EE
|
|
75
|
+
.UNINDENT
|
|
76
|
+
.UNINDENT
|
|
77
|
+
.SS git
|
|
78
|
+
.sp
|
|
79
|
+
Wrap git with git\-dir and work\-tree passed.
|
|
80
|
+
.INDENT 0.0
|
|
81
|
+
.INDENT 3.5
|
|
82
|
+
.sp
|
|
83
|
+
.EX
|
|
84
|
+
bw git [OPTIONS] [ARGS]...
|
|
85
|
+
.EE
|
|
86
|
+
.UNINDENT
|
|
87
|
+
.UNINDENT
|
|
88
|
+
.sp
|
|
89
|
+
Arguments
|
|
90
|
+
.INDENT 0.0
|
|
91
|
+
.TP
|
|
92
|
+
.B ARGS
|
|
93
|
+
Optional argument(s)
|
|
94
|
+
.UNINDENT
|
|
95
|
+
.SS info
|
|
96
|
+
.sp
|
|
97
|
+
Get basic information about the repository.
|
|
98
|
+
.INDENT 0.0
|
|
99
|
+
.INDENT 3.5
|
|
100
|
+
.sp
|
|
101
|
+
.EX
|
|
102
|
+
bw info [OPTIONS]
|
|
103
|
+
.EE
|
|
104
|
+
.UNINDENT
|
|
105
|
+
.UNINDENT
|
|
106
|
+
.SS init
|
|
107
|
+
.sp
|
|
108
|
+
Start tracking a home directory.
|
|
109
|
+
.INDENT 0.0
|
|
110
|
+
.INDENT 3.5
|
|
111
|
+
.sp
|
|
112
|
+
.EX
|
|
113
|
+
bw init [OPTIONS]
|
|
114
|
+
.EE
|
|
115
|
+
.UNINDENT
|
|
116
|
+
.UNINDENT
|
|
117
|
+
.SS install\-units
|
|
118
|
+
.sp
|
|
119
|
+
Install systemd units for automatic committing.
|
|
120
|
+
.INDENT 0.0
|
|
121
|
+
.INDENT 3.5
|
|
122
|
+
.sp
|
|
123
|
+
.EX
|
|
124
|
+
bw install\-units [OPTIONS]
|
|
125
|
+
.EE
|
|
126
|
+
.UNINDENT
|
|
127
|
+
.UNINDENT
|
|
128
|
+
.SS hgit
|
|
129
|
+
.sp
|
|
130
|
+
Wrap git with git\-dir and work\-tree passed.
|
|
131
|
+
.INDENT 0.0
|
|
132
|
+
.INDENT 3.5
|
|
133
|
+
.sp
|
|
134
|
+
.EX
|
|
135
|
+
hgit [OPTIONS] [ARGS]...
|
|
136
|
+
.EE
|
|
137
|
+
.UNINDENT
|
|
138
|
+
.UNINDENT
|
|
139
|
+
.sp
|
|
140
|
+
Arguments
|
|
141
|
+
.INDENT 0.0
|
|
142
|
+
.TP
|
|
143
|
+
.B ARGS
|
|
144
|
+
Optional argument(s)
|
|
145
|
+
.UNINDENT
|
|
146
|
+
.SH AUTHOR
|
|
147
|
+
Andrew Udvare <audvare@gmail.com>
|
|
148
|
+
.SH COPYRIGHT
|
|
149
|
+
2025
|
|
150
|
+
.\" Generated by docutils manpage writer.
|
|
151
|
+
.
|
|
@@ -8,6 +8,8 @@ classifiers = [
|
|
|
8
8
|
"Intended Audience :: Developers",
|
|
9
9
|
"License :: OSI Approved :: MIT License",
|
|
10
10
|
"Programming Language :: Python",
|
|
11
|
+
"Programming Language :: Python :: 3.10",
|
|
12
|
+
"Programming Language :: Python :: 3.11",
|
|
11
13
|
"Programming Language :: Python :: 3.12",
|
|
12
14
|
"Programming Language :: Python :: 3.13",
|
|
13
15
|
"Typing :: Typed",
|
|
@@ -18,39 +20,51 @@ keywords = ["command line", "file management", "git", "version control"]
|
|
|
18
20
|
license = "MIT"
|
|
19
21
|
name = "baldwin"
|
|
20
22
|
readme = "README.md"
|
|
21
|
-
version = "0.0.
|
|
23
|
+
version = "0.0.9"
|
|
22
24
|
|
|
23
25
|
[[project.authors]]
|
|
24
26
|
email = "audvare@gmail.com"
|
|
25
27
|
name = "Andrew Udvare"
|
|
26
28
|
|
|
29
|
+
[project.scripts]
|
|
30
|
+
bw = "baldwin.main:baldwin"
|
|
31
|
+
hgit = "baldwin.main:git"
|
|
32
|
+
|
|
27
33
|
[project.urls]
|
|
34
|
+
Issues = "https://github.com/Tatsh/baldwin/issues"
|
|
28
35
|
documentation = "https://baldwin.readthedocs.org"
|
|
29
|
-
|
|
36
|
+
homepage = "https://tatsh.github.io/baldwin/"
|
|
30
37
|
repository = "https://github.com/Tatsh/baldwin"
|
|
31
38
|
|
|
32
|
-
[tool]
|
|
33
|
-
|
|
34
39
|
[tool.commitizen]
|
|
35
40
|
tag_format = "v$version"
|
|
36
41
|
version_files = [
|
|
37
|
-
".
|
|
42
|
+
".wiswa.jsonnet",
|
|
38
43
|
"CITATION.cff",
|
|
39
44
|
"README.md",
|
|
40
45
|
"baldwin/__init__.py",
|
|
46
|
+
"docs/index.rst",
|
|
47
|
+
"man/baldwin.1",
|
|
41
48
|
"package.json",
|
|
42
49
|
]
|
|
43
50
|
version_provider = "pep621"
|
|
44
51
|
|
|
45
|
-
[tool.coverage]
|
|
46
|
-
|
|
47
52
|
[tool.coverage.report]
|
|
48
|
-
|
|
53
|
+
exclude_also = ["if TYPE_CHECKING:"]
|
|
54
|
+
omit = ["conftest.py", "tests.py", "tests/test_*.py", "__main__.py"]
|
|
49
55
|
show_missing = true
|
|
50
56
|
|
|
51
57
|
[tool.coverage.run]
|
|
52
58
|
branch = true
|
|
53
|
-
omit = ["
|
|
59
|
+
omit = ["conftest.py", "tests.py", "tests/test_*.py", "__main__.py"]
|
|
60
|
+
|
|
61
|
+
[tool.djlint]
|
|
62
|
+
max_line_length = 100
|
|
63
|
+
no_line_after_yaml = true
|
|
64
|
+
preserve_blank_lines = true
|
|
65
|
+
preserve_leading_space = true
|
|
66
|
+
profile = "jinja"
|
|
67
|
+
use_gitignore = true
|
|
54
68
|
|
|
55
69
|
[tool.doc8]
|
|
56
70
|
max-line-length = 100
|
|
@@ -59,68 +73,85 @@ max-line-length = 100
|
|
|
59
73
|
cache_dir = "~/.cache/mypy"
|
|
60
74
|
explicit_package_bases = true
|
|
61
75
|
platform = "linux"
|
|
62
|
-
python_version = "3.
|
|
76
|
+
python_version = "3.10"
|
|
63
77
|
show_column_numbers = true
|
|
64
78
|
strict = true
|
|
65
79
|
strict_optional = true
|
|
66
80
|
warn_unreachable = true
|
|
67
81
|
|
|
68
82
|
[tool.poetry]
|
|
83
|
+
include = ["man", { format = "sdist", path = "tests" }]
|
|
69
84
|
|
|
70
85
|
[tool.poetry.dependencies]
|
|
71
86
|
binaryornot = "^0.4.4"
|
|
72
87
|
click = "^8.1.8"
|
|
88
|
+
colorlog = "^6.9.0"
|
|
73
89
|
gitpython = "^3.1.44"
|
|
74
90
|
platformdirs = "^4.3.6"
|
|
75
|
-
python = ">=3.
|
|
76
|
-
|
|
77
|
-
|
|
91
|
+
python = ">=3.10,<3.14"
|
|
92
|
+
tomlkit = "^0.13.2"
|
|
93
|
+
typing-extensions = "^4.13.2"
|
|
78
94
|
|
|
79
95
|
[tool.poetry.group.dev]
|
|
80
96
|
optional = true
|
|
81
97
|
|
|
82
98
|
[tool.poetry.group.dev.dependencies]
|
|
83
99
|
binaryornot-stubs = "^0"
|
|
84
|
-
commitizen = "^4.
|
|
85
|
-
|
|
86
|
-
|
|
100
|
+
commitizen = "^4.7.0"
|
|
101
|
+
djlint = "^1.36.4"
|
|
102
|
+
mypy = ">=1.12,<1.16"
|
|
103
|
+
ruff = "^0.11.9"
|
|
87
104
|
yapf = "^0.43.0"
|
|
88
105
|
|
|
89
106
|
[tool.poetry.group.docs]
|
|
90
107
|
optional = true
|
|
91
108
|
|
|
92
109
|
[tool.poetry.group.docs.dependencies]
|
|
110
|
+
autodoc-pydantic = "^2.2.0"
|
|
93
111
|
doc8 = "^1.1.2"
|
|
94
112
|
docutils = "^0.21.2"
|
|
95
113
|
esbonio = "^0.16.5"
|
|
114
|
+
numpydoc = "^1.8.0"
|
|
96
115
|
restructuredtext-lint = "^1.4.0"
|
|
97
|
-
sphinx = "^8.1.3"
|
|
98
116
|
sphinx-click = "^6.0.0"
|
|
99
|
-
|
|
117
|
+
sphinx-datatables = "^0.2.1"
|
|
118
|
+
sphinx-hoverxref = "^1.4.2"
|
|
119
|
+
sphinx-immaterial = "^0.13.5"
|
|
120
|
+
|
|
121
|
+
[tool.poetry.group.docs.dependencies.enum-tools]
|
|
122
|
+
extras = ["sphinx"]
|
|
123
|
+
version = "^0.12.0"
|
|
124
|
+
|
|
125
|
+
[[tool.poetry.group.docs.dependencies.sphinx]]
|
|
126
|
+
python = ">=3.11"
|
|
127
|
+
version = "^8.2.0"
|
|
128
|
+
|
|
129
|
+
[[tool.poetry.group.docs.dependencies.sphinx]]
|
|
130
|
+
python = "<3.11"
|
|
131
|
+
version = "^7.2.5"
|
|
100
132
|
|
|
101
133
|
[tool.poetry.group.tests]
|
|
102
134
|
optional = true
|
|
103
135
|
|
|
104
136
|
[tool.poetry.group.tests.dependencies]
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
pytest = "^
|
|
108
|
-
pytest-cov = "^5.0.0"
|
|
137
|
+
mock = "^5.2.0"
|
|
138
|
+
pytest = "^8.3.5"
|
|
139
|
+
pytest-cov = "^6.1.1"
|
|
109
140
|
pytest-mock = "^3.14.0"
|
|
110
141
|
|
|
142
|
+
[tool.poetry.group.tests.dependencies.coveralls]
|
|
143
|
+
python = "<3.13"
|
|
144
|
+
version = "^4.0.1"
|
|
145
|
+
|
|
111
146
|
[[tool.poetry.packages]]
|
|
112
147
|
include = "baldwin"
|
|
113
148
|
|
|
114
|
-
[tool.poetry.scripts]
|
|
115
|
-
bw = "baldwin.main:baldwin_main"
|
|
116
|
-
hgit = "baldwin.main:git_main"
|
|
117
|
-
|
|
118
149
|
[tool.pyright]
|
|
119
150
|
deprecateTypingAliases = true
|
|
120
151
|
enableExperimentalFeatures = true
|
|
121
152
|
include = ["./baldwin", "./tests"]
|
|
122
153
|
pythonPlatform = "Linux"
|
|
123
|
-
pythonVersion = "3.
|
|
154
|
+
pythonVersion = "3.10"
|
|
124
155
|
reportCallInDefaultInitializer = "warning"
|
|
125
156
|
reportImplicitOverride = "warning"
|
|
126
157
|
reportImportCycles = "error"
|
|
@@ -131,21 +162,18 @@ reportUnnecessaryTypeIgnoreComment = "none"
|
|
|
131
162
|
typeCheckingMode = "off"
|
|
132
163
|
useLibraryCodeForTypes = false
|
|
133
164
|
|
|
134
|
-
[tool.pytest]
|
|
135
|
-
|
|
136
165
|
[tool.pytest.ini_options]
|
|
137
166
|
mock_use_standalone_module = true
|
|
138
167
|
norecursedirs = ["node_modules"]
|
|
139
|
-
python_files = ["test_*.py", "*_tests.py"]
|
|
168
|
+
python_files = ["tests.py", "test_*.py", "*_tests.py"]
|
|
140
169
|
testpaths = ["tests"]
|
|
141
170
|
|
|
142
171
|
[tool.ruff]
|
|
143
172
|
cache-dir = "~/.cache/ruff"
|
|
144
|
-
extend-exclude = []
|
|
145
173
|
force-exclude = true
|
|
146
174
|
line-length = 100
|
|
147
175
|
namespace-packages = ["docs", "tests"]
|
|
148
|
-
target-version = "
|
|
176
|
+
target-version = "py310"
|
|
149
177
|
unsafe-fixes = true
|
|
150
178
|
|
|
151
179
|
[tool.ruff.lint]
|
|
@@ -163,6 +191,7 @@ extend-select = [
|
|
|
163
191
|
"CPY",
|
|
164
192
|
"D",
|
|
165
193
|
"DJ",
|
|
194
|
+
"DOC",
|
|
166
195
|
"DTZ",
|
|
167
196
|
"E",
|
|
168
197
|
"EM",
|
|
@@ -170,6 +199,7 @@ extend-select = [
|
|
|
170
199
|
"EXE",
|
|
171
200
|
"F",
|
|
172
201
|
"FA",
|
|
202
|
+
"FAST",
|
|
173
203
|
"FBT",
|
|
174
204
|
"FIX",
|
|
175
205
|
"FLY",
|
|
@@ -201,11 +231,12 @@ extend-select = [
|
|
|
201
231
|
"SLOT",
|
|
202
232
|
"T10",
|
|
203
233
|
"T20",
|
|
204
|
-
"
|
|
234
|
+
"TC",
|
|
205
235
|
"TD",
|
|
206
236
|
"TID",
|
|
207
237
|
"TRY",
|
|
208
238
|
"UP",
|
|
239
|
+
"W",
|
|
209
240
|
"YTT",
|
|
210
241
|
]
|
|
211
242
|
ignore = [
|
|
@@ -217,17 +248,11 @@ ignore = [
|
|
|
217
248
|
"C901",
|
|
218
249
|
"COM812",
|
|
219
250
|
"CPY001",
|
|
220
|
-
"
|
|
221
|
-
"D101",
|
|
222
|
-
"D102",
|
|
223
|
-
"D103",
|
|
224
|
-
"D104",
|
|
225
|
-
"D105",
|
|
226
|
-
"D106",
|
|
227
|
-
"D107",
|
|
251
|
+
"D201",
|
|
228
252
|
"D203",
|
|
229
253
|
"D204",
|
|
230
254
|
"D212",
|
|
255
|
+
"DOC201",
|
|
231
256
|
"EM101",
|
|
232
257
|
"N818",
|
|
233
258
|
"PLR0912",
|
|
@@ -243,6 +268,8 @@ ignore = [
|
|
|
243
268
|
"TD002",
|
|
244
269
|
"TD003",
|
|
245
270
|
"TD004",
|
|
271
|
+
"UP046",
|
|
272
|
+
"UP047",
|
|
246
273
|
]
|
|
247
274
|
preview = true
|
|
248
275
|
|
|
@@ -254,6 +281,8 @@ multiline-quotes = "double"
|
|
|
254
281
|
case-sensitive = true
|
|
255
282
|
combine-as-imports = true
|
|
256
283
|
from-first = true
|
|
284
|
+
required-imports = ["from __future__ import annotations"]
|
|
285
|
+
section-order = ["future", "standard-library", "third-party", "local-folder"]
|
|
257
286
|
|
|
258
287
|
[tool.ruff.lint.pep8-naming]
|
|
259
288
|
extend-ignore-names = ["test_*"]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Configuration for Pytest."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import NoReturn
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
from click.testing import CliRunner
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
if os.getenv('_PYTEST_RAISE', '0') != '0': # pragma no cover
|
|
11
|
+
|
|
12
|
+
@pytest.hookimpl(tryfirst=True)
|
|
13
|
+
def pytest_exception_interact(call: pytest.CallInfo[None]) -> NoReturn:
|
|
14
|
+
assert call.excinfo is not None
|
|
15
|
+
raise call.excinfo.value
|
|
16
|
+
|
|
17
|
+
@pytest.hookimpl(tryfirst=True)
|
|
18
|
+
def pytest_internalerror(excinfo: pytest.ExceptionInfo[BaseException]) -> NoReturn:
|
|
19
|
+
raise excinfo.value
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@pytest.fixture
|
|
23
|
+
def runner() -> CliRunner:
|
|
24
|
+
return CliRunner()
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from baldwin.main import baldwin
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from click.testing import CliRunner
|
|
9
|
+
from pytest_mock import MockerFixture
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_commit(runner: CliRunner, mocker: MockerFixture) -> None:
|
|
13
|
+
path = mocker.patch('baldwin.lib.Path')
|
|
14
|
+
mocker.patch('baldwin.lib.platformdirs.user_data_path')
|
|
15
|
+
mocker.patch('baldwin.lib.resources')
|
|
16
|
+
which = mocker.patch('baldwin.lib.which')
|
|
17
|
+
which.return_value = None # Disable format
|
|
18
|
+
repo = mocker.patch('baldwin.lib.Repo')
|
|
19
|
+
repo.return_value.untracked_files = ['untracked1']
|
|
20
|
+
changed_file = mocker.MagicMock()
|
|
21
|
+
changed_file.a_path = 'changed1'
|
|
22
|
+
deleted_file = mocker.MagicMock()
|
|
23
|
+
deleted_file.a_path = 'deleted1'
|
|
24
|
+
repo.return_value.index.diff.return_value = [changed_file, deleted_file]
|
|
25
|
+
path.home.return_value.__truediv__.return_value.exists.side_effect = [True, False, True, False]
|
|
26
|
+
runner.invoke(baldwin, ('auto-commit',))
|
|
27
|
+
assert repo.return_value.index.add.called
|
|
28
|
+
assert repo.return_value.index.remove.called
|
|
29
|
+
assert repo.return_value.index.commit.called
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_commit_no_files(runner: CliRunner, mocker: MockerFixture) -> None:
|
|
33
|
+
mocker.patch('baldwin.lib.Path')
|
|
34
|
+
mocker.patch('baldwin.lib.platformdirs.user_data_path')
|
|
35
|
+
mocker.patch('baldwin.lib.resources')
|
|
36
|
+
which = mocker.patch('baldwin.lib.which')
|
|
37
|
+
which.return_value = None # Disable format
|
|
38
|
+
repo = mocker.patch('baldwin.lib.Repo')
|
|
39
|
+
repo.return_value.untracked_files = []
|
|
40
|
+
repo.return_value.index.diff.return_value = []
|
|
41
|
+
runner.invoke(baldwin, ('auto-commit',))
|
|
42
|
+
assert not repo.return_value.index.add.called
|
|
43
|
+
assert not repo.return_value.index.remove.called
|
|
44
|
+
assert not repo.return_value.index.commit.called
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_commit_no_add(runner: CliRunner, mocker: MockerFixture) -> None:
|
|
48
|
+
path = mocker.patch('baldwin.lib.Path')
|
|
49
|
+
mocker.patch('baldwin.lib.platformdirs.user_data_path')
|
|
50
|
+
mocker.patch('baldwin.lib.resources')
|
|
51
|
+
which = mocker.patch('baldwin.lib.which')
|
|
52
|
+
which.return_value = None # Disable format
|
|
53
|
+
repo = mocker.patch('baldwin.lib.Repo')
|
|
54
|
+
repo.return_value.untracked_files = []
|
|
55
|
+
deleted_file = mocker.MagicMock()
|
|
56
|
+
deleted_file.a_path = 'deleted1'
|
|
57
|
+
repo.return_value.index.diff.return_value = [deleted_file]
|
|
58
|
+
path.home.return_value.__truediv__.return_value.exists.return_value = False
|
|
59
|
+
runner.invoke(baldwin, ('auto-commit',))
|
|
60
|
+
assert not repo.return_value.index.add.called
|
|
61
|
+
assert repo.return_value.index.remove.called
|
|
62
|
+
assert repo.return_value.index.commit.called
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_commit_no_delete(runner: CliRunner, mocker: MockerFixture) -> None:
|
|
66
|
+
mocker.patch('baldwin.lib.Path')
|
|
67
|
+
mocker.patch('baldwin.lib.platformdirs.user_data_path')
|
|
68
|
+
mocker.patch('baldwin.lib.resources')
|
|
69
|
+
which = mocker.patch('baldwin.lib.which')
|
|
70
|
+
which.return_value = None # Disable format
|
|
71
|
+
repo = mocker.patch('baldwin.lib.Repo')
|
|
72
|
+
repo.return_value.untracked_files = ['untracked1']
|
|
73
|
+
changed_file = mocker.MagicMock()
|
|
74
|
+
changed_file.a_path = 'changed1'
|
|
75
|
+
repo.return_value.index.diff.return_value = [changed_file]
|
|
76
|
+
runner.invoke(baldwin, ('auto-commit',))
|
|
77
|
+
assert repo.return_value.index.add.called
|
|
78
|
+
assert not repo.return_value.index.remove.called
|
|
79
|
+
assert repo.return_value.index.commit.called
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_commit_ignore_unreadable_files(runner: CliRunner, mocker: MockerFixture) -> None:
|
|
83
|
+
path = mocker.patch('baldwin.lib.Path')
|
|
84
|
+
mocker.patch('baldwin.lib.platformdirs.user_data_path')
|
|
85
|
+
mocker.patch('baldwin.lib.resources')
|
|
86
|
+
which = mocker.patch('baldwin.lib.which')
|
|
87
|
+
which.return_value = None # Disable format
|
|
88
|
+
repo = mocker.patch('baldwin.lib.Repo')
|
|
89
|
+
repo.return_value.untracked_files = ['untracked1']
|
|
90
|
+
changed_file = mocker.MagicMock()
|
|
91
|
+
changed_file.a_path = 'changed1'
|
|
92
|
+
deleted_file = mocker.MagicMock()
|
|
93
|
+
deleted_file.a_path = 'deleted1'
|
|
94
|
+
repo.return_value.index.diff.return_value = [changed_file, deleted_file]
|
|
95
|
+
path.home.return_value.__truediv__.return_value.exists.side_effect = [True, False, True, False]
|
|
96
|
+
path.home.return_value.__truediv__.return_value.is_file.side_effect = [True, False, True]
|
|
97
|
+
path.home.return_value.__truediv__.return_value.open.side_effect = PermissionError
|
|
98
|
+
runner.invoke(baldwin, ('auto-commit',))
|
|
99
|
+
assert len(repo.return_value.index.add.call_args[0][0]) == 1
|
|
100
|
+
assert repo.return_value.index.remove.called
|
|
101
|
+
assert repo.return_value.index.commit.called
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from baldwin.main import baldwin
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from click.testing import CliRunner
|
|
9
|
+
from pytest_mock import MockerFixture
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_format_no_prettier(runner: CliRunner, mocker: MockerFixture) -> None:
|
|
13
|
+
path = mocker.patch('baldwin.lib.Path')
|
|
14
|
+
mocker.patch('baldwin.lib.platformdirs.user_data_path')
|
|
15
|
+
mocker.patch('baldwin.lib.resources')
|
|
16
|
+
run = mocker.patch('baldwin.lib.sp.run')
|
|
17
|
+
which = mocker.patch('baldwin.lib.which')
|
|
18
|
+
which.return_value = None
|
|
19
|
+
repo = mocker.patch('baldwin.lib.Repo')
|
|
20
|
+
repo.untracked_files = ['untracked1']
|
|
21
|
+
changed_file = mocker.MagicMock()
|
|
22
|
+
changed_file.a_path = 'changed1'
|
|
23
|
+
deleted_file = mocker.MagicMock()
|
|
24
|
+
deleted_file.a_path = 'deleted1'
|
|
25
|
+
repo.return_value.index.diff.return_value = [changed_file, deleted_file]
|
|
26
|
+
path.home.return_value.__truediv__.return_value.exists.side_effect = [True, False, True, False]
|
|
27
|
+
runner.invoke(baldwin, ('format',))
|
|
28
|
+
assert not run.called
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_format_no_files(runner: CliRunner, mocker: MockerFixture) -> None:
|
|
32
|
+
mocker.patch('baldwin.lib.Path')
|
|
33
|
+
mocker.patch('baldwin.lib.platformdirs.user_data_path')
|
|
34
|
+
mocker.patch('baldwin.lib.resources')
|
|
35
|
+
run = mocker.patch('baldwin.lib.sp.run')
|
|
36
|
+
which = mocker.patch('baldwin.lib.which')
|
|
37
|
+
repo = mocker.patch('baldwin.lib.Repo')
|
|
38
|
+
repo.untracked_files = []
|
|
39
|
+
repo.return_value.index.diff.return_value = []
|
|
40
|
+
runner.invoke(baldwin, ('format',))
|
|
41
|
+
assert not run.called
|
|
42
|
+
assert not which.called
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_format(runner: CliRunner, mocker: MockerFixture) -> None:
|
|
46
|
+
path = mocker.patch('baldwin.lib.Path')
|
|
47
|
+
mocker.patch('baldwin.lib.platformdirs.user_data_path')
|
|
48
|
+
mocker.patch('baldwin.lib.platformdirs.user_config_path'
|
|
49
|
+
).return_value.__truediv__.return_value.exists.return_value = False
|
|
50
|
+
mocker.patch('baldwin.lib.resources')
|
|
51
|
+
run = mocker.patch('baldwin.lib.sp.run')
|
|
52
|
+
which = mocker.patch('baldwin.lib.which')
|
|
53
|
+
which.return_value = '/bin/prettier'
|
|
54
|
+
repo = mocker.patch('baldwin.lib.Repo')
|
|
55
|
+
repo.untracked_files = ['untracked1']
|
|
56
|
+
changed_file = mocker.MagicMock()
|
|
57
|
+
changed_file.a_path = 'changed1'
|
|
58
|
+
deleted_file = mocker.MagicMock()
|
|
59
|
+
deleted_file.a_path = 'deleted1'
|
|
60
|
+
repo.return_value.index.diff.return_value = [changed_file, deleted_file]
|
|
61
|
+
path.home.return_value.__truediv__.return_value.exists.side_effect = [True, False, True, False]
|
|
62
|
+
runner.invoke(baldwin, ('format',))
|
|
63
|
+
assert run.called
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_format_config_file_exists(runner: CliRunner, mocker: MockerFixture) -> None:
|
|
67
|
+
path = mocker.patch('baldwin.lib.Path')
|
|
68
|
+
mocker.patch('baldwin.lib.platformdirs.user_data_path')
|
|
69
|
+
mocker.patch('baldwin.lib.platformdirs.user_config_path'
|
|
70
|
+
).return_value.__truediv__.return_value.exists.return_value = True
|
|
71
|
+
mocker.patch('baldwin.lib.resources')
|
|
72
|
+
mocker.patch('baldwin.lib.tomlkit.loads').return_value = {}
|
|
73
|
+
run = mocker.patch('baldwin.lib.sp.run')
|
|
74
|
+
which = mocker.patch('baldwin.lib.which')
|
|
75
|
+
which.return_value = '/bin/prettier'
|
|
76
|
+
repo = mocker.patch('baldwin.lib.Repo')
|
|
77
|
+
repo.untracked_files = ['untracked1']
|
|
78
|
+
changed_file = mocker.MagicMock()
|
|
79
|
+
changed_file.a_path = 'changed1'
|
|
80
|
+
deleted_file = mocker.MagicMock()
|
|
81
|
+
deleted_file.a_path = 'deleted1'
|
|
82
|
+
repo.return_value.index.diff.return_value = [changed_file, deleted_file]
|
|
83
|
+
path.home.return_value.__truediv__.return_value.exists.side_effect = [True, False, True, False]
|
|
84
|
+
runner.invoke(baldwin, ('format',))
|
|
85
|
+
assert run.called
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from baldwin.lib import get_git_path
|
|
7
|
+
from baldwin.main import baldwin, git
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from click.testing import CliRunner
|
|
11
|
+
from pytest_mock import MockerFixture
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_hgit_wrapper(runner: CliRunner, mocker: MockerFixture) -> None:
|
|
15
|
+
run = mocker.patch('baldwin.lib.sp.run')
|
|
16
|
+
runner.invoke(git, ('status',))
|
|
17
|
+
run.assert_called_once_with(
|
|
18
|
+
('git', f'--git-dir={get_git_path()}', f'--work-tree={Path.home()}', 'status'), check=False)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_bw_git_wrapper(runner: CliRunner, mocker: MockerFixture) -> None:
|
|
22
|
+
run = mocker.patch('baldwin.lib.sp.run')
|
|
23
|
+
runner.invoke(baldwin, ('git', 'status'))
|
|
24
|
+
run.assert_called_once_with(
|
|
25
|
+
('git', f'--git-dir={get_git_path()}', f'--work-tree={Path.home()}', 'status'), check=False)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
from baldwin.main import baldwin
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from click.testing import CliRunner
|
|
10
|
+
from pytest_mock import MockerFixture
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_init_returns_if_exists(runner: CliRunner, mocker: MockerFixture) -> None:
|
|
14
|
+
mocker.patch('baldwin.lib.platformdirs.user_data_path').return_value.exists.return_value = True
|
|
15
|
+
mocker.patch('baldwin.lib.Path')
|
|
16
|
+
run = runner.invoke(baldwin, ('info',))
|
|
17
|
+
lines = run.stdout.splitlines()
|
|
18
|
+
assert re.match(r'^git-dir path: <MagicMock.*>$', lines[0])
|
|
19
|
+
assert re.match(r'^work-tree path: <MagicMock.*>$', lines[1])
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from baldwin.main import baldwin
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from click.testing import CliRunner
|
|
9
|
+
from pytest_mock import MockerFixture
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_init_returns_if_exists(runner: CliRunner, mocker: MockerFixture) -> None:
|
|
13
|
+
mocker.patch('baldwin.lib.platformdirs.user_data_path').return_value.exists.return_value = True
|
|
14
|
+
repo = mocker.patch('baldwin.lib.Repo')
|
|
15
|
+
runner.invoke(baldwin, ('init',))
|
|
16
|
+
assert not repo.called
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_init(runner: CliRunner, mocker: MockerFixture) -> None:
|
|
20
|
+
path = mocker.patch('baldwin.lib.Path')
|
|
21
|
+
mocker.patch('baldwin.lib.platformdirs.user_data_path').return_value.exists.return_value = False
|
|
22
|
+
repo = mocker.patch('baldwin.lib.Repo')
|
|
23
|
+
mocker.patch('baldwin.lib.resources')
|
|
24
|
+
which = mocker.patch('baldwin.lib.which')
|
|
25
|
+
which.side_effect = ['jq', 'prettier']
|
|
26
|
+
runner.invoke(baldwin, ('init',))
|
|
27
|
+
assert repo.init.called
|
|
28
|
+
assert repo.init.return_value.index.add.called
|
|
29
|
+
path.home.return_value.__truediv__.assert_any_call('.gitattributes')
|
|
30
|
+
path.home.return_value.__truediv__.assert_any_call('.gitignore')
|
|
31
|
+
assert path.home.return_value.__truediv__.return_value.write_text.call_count == 2
|
|
32
|
+
which.assert_any_call('jq')
|
|
33
|
+
which.assert_any_call('prettier')
|
|
34
|
+
repo.init.return_value.git.execute.assert_any_call(('git', 'config', 'commit.gpgsign', 'false'))
|
|
35
|
+
repo.init.return_value.git.execute.assert_any_call(
|
|
36
|
+
('git', 'config', 'diff.json.textconv', '"jq" -MS .'))
|
|
37
|
+
repo.init.return_value.git.execute.assert_any_call(
|
|
38
|
+
('git', 'config', 'diff.xml.textconv',
|
|
39
|
+
'"prettier" --no-editorconfig --parser xml --xml-whitespace-sensitivity ignore'))
|
|
40
|
+
repo.init.return_value.git.execute.assert_any_call(
|
|
41
|
+
('git', 'config', 'diff.yaml.textconv', '"prettier" --no-editorconfig --parser yaml'))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_init_no_tools(runner: CliRunner, mocker: MockerFixture) -> None:
|
|
45
|
+
path = mocker.patch('baldwin.lib.Path')
|
|
46
|
+
mocker.patch('baldwin.lib.platformdirs.user_data_path').return_value.exists.return_value = False
|
|
47
|
+
repo = mocker.patch('baldwin.lib.Repo')
|
|
48
|
+
mocker.patch('baldwin.lib.resources')
|
|
49
|
+
which = mocker.patch('baldwin.lib.which')
|
|
50
|
+
which.return_value = False
|
|
51
|
+
runner.invoke(baldwin, ('init',))
|
|
52
|
+
assert repo.init.called
|
|
53
|
+
path.home.return_value.__truediv__.assert_any_call('.gitattributes')
|
|
54
|
+
path.home.return_value.__truediv__.assert_any_call('.gitignore')
|
|
55
|
+
assert path.home.return_value.__truediv__.return_value.write_text.call_count == 2
|
|
56
|
+
which.assert_any_call('jq')
|
|
57
|
+
which.assert_any_call('prettier')
|
|
58
|
+
repo.init.return_value.git.execute.assert_any_call(('git', 'config', 'commit.gpgsign', 'false'))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_init_no_xml_plugin(runner: CliRunner, mocker: MockerFixture) -> None:
|
|
62
|
+
path = mocker.patch('baldwin.lib.Path')
|
|
63
|
+
mocker.patch('baldwin.lib.platformdirs.user_data_path').return_value.exists.return_value = False
|
|
64
|
+
repo = mocker.patch('baldwin.lib.Repo')
|
|
65
|
+
mocker.patch('baldwin.lib.resources')
|
|
66
|
+
which = mocker.patch('baldwin.lib.which')
|
|
67
|
+
which.side_effect = ['jq', 'prettier']
|
|
68
|
+
path.return_value.resolve.return_value.parent.__truediv__.return_value.__truediv__.return_value.resolve.return_value.__truediv__.return_value.exists.return_value = False # noqa: E501
|
|
69
|
+
runner.invoke(baldwin, ('init',))
|
|
70
|
+
assert repo.init.called
|
|
71
|
+
assert repo.init.return_value.index.add.called
|
|
72
|
+
path.home.return_value.__truediv__.assert_any_call('.gitattributes')
|
|
73
|
+
path.home.return_value.__truediv__.assert_any_call('.gitignore')
|
|
74
|
+
assert path.home.return_value.__truediv__.return_value.write_text.call_count == 2
|
|
75
|
+
which.assert_any_call('jq')
|
|
76
|
+
which.assert_any_call('prettier')
|
|
77
|
+
repo.init.return_value.git.execute.assert_any_call(('git', 'config', 'commit.gpgsign', 'false'))
|
|
78
|
+
repo.init.return_value.git.execute.assert_any_call(
|
|
79
|
+
('git', 'config', 'diff.json.textconv', '"jq" -MS .'))
|
|
80
|
+
repo.init.return_value.git.execute.assert_any_call(
|
|
81
|
+
('git', 'config', 'diff.yaml.textconv', '"prettier" --no-editorconfig --parser yaml'))
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from baldwin.main import baldwin
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from click.testing import CliRunner
|
|
9
|
+
from pytest_mock import MockerFixture
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_install_units_no_bw(runner: CliRunner, mocker: MockerFixture) -> None:
|
|
13
|
+
mocker.patch('baldwin.lib.which').return_value = None
|
|
14
|
+
run = runner.invoke(baldwin, ('install-units',))
|
|
15
|
+
assert run.exit_code != 0
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_install_units(runner: CliRunner, mocker: MockerFixture) -> None:
|
|
19
|
+
path = mocker.patch('baldwin.lib.Path')
|
|
20
|
+
mocker.patch('baldwin.lib.sp.run')
|
|
21
|
+
runner.invoke(baldwin, ('install-units',))
|
|
22
|
+
service_content = path.return_value.expanduser.return_value.write_text.mock_calls[0].args[0]
|
|
23
|
+
assert 'Type=oneshot' in service_content
|
|
24
|
+
assert 'ExecStart=' in service_content
|
|
25
|
+
timer_content = path.return_value.expanduser.return_value.write_text.mock_calls[1].args[0]
|
|
26
|
+
assert 'OnCalendar=' in timer_content
|
|
27
|
+
assert 'WantedBy=timers.target' in timer_content
|
baldwin-0.0.7/LICENSE.txt
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
The MIT License (MIT)
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2023 baldwin authors
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in
|
|
13
|
-
all copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
-
THE SOFTWARE.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|