baldwin 0.0.1__tar.gz → 0.0.10__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,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.
@@ -0,0 +1,162 @@
1
+ Metadata-Version: 2.4
2
+ Name: baldwin
3
+ Version: 0.0.10
4
+ Summary: Simple tracking of your home directory with easy-to-read diffs.
5
+ License-Expression: MIT
6
+ License-File: LICENSE.txt
7
+ Keywords: command line,file management,git,version control
8
+ Author: Andrew Udvare
9
+ Author-email: audvare@gmail.com
10
+ Requires-Python: >=3.10,<4.0
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Typing :: Typed
20
+ Requires-Dist: bascom (>=0.0.4,<0.0.5)
21
+ Requires-Dist: binaryornot (>=0.4.4,<0.5.0)
22
+ Requires-Dist: click (>=8.3.0,<9.0.0)
23
+ Requires-Dist: gitpython (>=3.1.45,<4.0.0)
24
+ Requires-Dist: platformdirs (>=4.5.0,<5.0.0)
25
+ Requires-Dist: tomlkit (>=0.13.3,<0.14.0)
26
+ Requires-Dist: typing-extensions (>=4.15.0,<5.0.0)
27
+ Project-URL: Documentation, https://baldwin.readthedocs.org
28
+ Project-URL: Homepage, https://tatsh.github.io/baldwin/
29
+ Project-URL: Issues, https://github.com/Tatsh/baldwin/issues
30
+ Project-URL: Repository, https://github.com/Tatsh/baldwin
31
+ Description-Content-Type: text/markdown
32
+
33
+ # Baldwin
34
+
35
+ [![Python versions](https://img.shields.io/pypi/pyversions/baldwin.svg?color=blue&logo=python&logoColor=white)](https://www.python.org/)
36
+ [![PyPI - Version](https://img.shields.io/pypi/v/baldwin)](https://pypi.org/project/baldwin/)
37
+ [![GitHub tag (with filter)](https://img.shields.io/github/v/tag/Tatsh/baldwin)](https://github.com/Tatsh/baldwin/tags)
38
+ [![License](https://img.shields.io/github/license/Tatsh/baldwin)](https://github.com/Tatsh/baldwin/blob/master/LICENSE.txt)
39
+ [![GitHub commits since latest release (by SemVer including pre-releases)](https://img.shields.io/github/commits-since/Tatsh/baldwin/v0.0.10/master)](https://github.com/Tatsh/baldwin/compare/v0.0.10...master)
40
+ [![CodeQL](https://github.com/Tatsh/baldwin/actions/workflows/codeql.yml/badge.svg)](https://github.com/Tatsh/baldwin/actions/workflows/codeql.yml)
41
+ [![QA](https://github.com/Tatsh/baldwin/actions/workflows/qa.yml/badge.svg)](https://github.com/Tatsh/baldwin/actions/workflows/qa.yml)
42
+ [![Tests](https://github.com/Tatsh/baldwin/actions/workflows/tests.yml/badge.svg)](https://github.com/Tatsh/baldwin/actions/workflows/tests.yml)
43
+ [![Coverage Status](https://coveralls.io/repos/github/Tatsh/baldwin/badge.svg?branch=master)](https://coveralls.io/github/Tatsh/baldwin?branch=master)
44
+ [![Documentation Status](https://readthedocs.org/projects/baldwin/badge/?version=latest)](https://baldwin.readthedocs.org/?badge=latest)
45
+ [![mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/)
46
+ [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit)
47
+ [![pydocstyle](https://img.shields.io/badge/pydocstyle-enabled-AD4CD3)](http://www.pydocstyle.org/en/stable/)
48
+ [![pytest](https://img.shields.io/badge/pytest-zz?logo=Pytest&labelColor=black&color=black)](https://docs.pytest.org/en/stable/)
49
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
50
+ [![Downloads](https://static.pepy.tech/badge/baldwin/month)](https://pepy.tech/project/baldwin)
51
+ [![Stargazers](https://img.shields.io/github/stars/Tatsh/baldwin?logo=github&style=flat)](https://github.com/Tatsh/baldwin/stargazers)
52
+
53
+ [![@Tatsh](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fpublic.api.bsky.app%2Fxrpc%2Fapp.bsky.actor.getProfile%2F%3Factor%3Ddid%3Aplc%3Auq42idtvuccnmtl57nsucz72%26query%3D%24.followersCount%26style%3Dsocial%26logo%3Dbluesky%26label%3DFollow%2520%40Tatsh&query=%24.followersCount&style=social&logo=bluesky&label=Follow%20%40Tatsh)](https://bsky.app/profile/Tatsh.bsky.social)
54
+ [![Mastodon Follow](https://img.shields.io/mastodon/follow/109370961877277568?domain=hostux.social&style=social)](https://hostux.social/@Tatsh)
55
+
56
+ This is a conversion of my simple scripts to version my home directory with very specific excludes
57
+ and formatting every file upon commit so that readable diffs can be generated.
58
+
59
+ ## Installation
60
+
61
+ ### Pip
62
+
63
+ ```shell
64
+ pip install baldwin
65
+ ```
66
+
67
+ ## Usage
68
+
69
+ ```plain
70
+ $ bw -h
71
+ Usage: bw [OPTIONS] COMMAND [ARGS]...
72
+
73
+ Manage a home directory with Git.
74
+
75
+ Options:
76
+ -d, --debug Enable debug logging.
77
+ -h, --help Show this message and exit.
78
+
79
+ Commands:
80
+ auto-commit
81
+ format
82
+ git
83
+ info
84
+ init
85
+ install-units
86
+ ```
87
+
88
+ In addition to the `bw` command, `hgit` is a shortcut for `bw git`.
89
+
90
+ ### Start a new repository
91
+
92
+ ```shell
93
+ bw init
94
+ ```
95
+
96
+ Find out where the Git directory is by running `bw info`. This can be done even if `init` has not
97
+ been run.
98
+
99
+ ### Automation
100
+
101
+ #### systemd
102
+
103
+ ```shell
104
+ bw install-units
105
+ ```
106
+
107
+ This will install a timer that will automatically make a new commit every 6 hours. It does not push.
108
+
109
+ Keep in mind that systemd units require a full path to the executable, so you must keep the unit
110
+ up-to-date if you move where you install this package. Simply run `bw install-units` again.
111
+
112
+ Note that user systemd units only run while logged in.
113
+
114
+ To disable and remove the units, use the following commands:
115
+
116
+ ```shell
117
+ systemctl disable --now home-vcs.timer
118
+ rm ~/.config/systemd/user/home-vcs.*
119
+ ```
120
+
121
+ ### Pushing
122
+
123
+ To push, use either of the following:
124
+
125
+ - `bw git push`
126
+ - `hgit push`
127
+
128
+ The above also demonstrates that `bw git`/`hgit` are just frontends to `git` with the correct
129
+ environment applied.
130
+
131
+ ## Formatting
132
+
133
+ If Prettier is installed, it will be used to format files. The configuration used comes with this
134
+ package. Having consistent formatting allows for nice diffs to be generated.
135
+
136
+ If you have initialised a repository without having `prettier` or `jq` in `PATH`, you need to run the
137
+ following commands to enable readable diffs:
138
+
139
+ ```shell
140
+ hgit config diff.json.textconv 'jq -MS .'
141
+ hgit config diff.json.cachetextconv true
142
+ hgit config diff.yaml.textconv 'prettier --no-editorconfig --parser yaml'
143
+ hgit config diff.yaml.cachetextconv true
144
+ ```
145
+
146
+ If you have the XML plugin installed:
147
+
148
+ ```shell
149
+ hgit config diff.xml.textconv 'prettier --no-editorconfig --parser xml --xml-whitespace-sensitivity ignore'
150
+ hgit config diff.xml.cachetextconv true
151
+ ```
152
+
153
+ ## Binary files
154
+
155
+ Any file that is untracked and detected to be binary will not be added. Use `hgit add` to add a
156
+ binary file manually.
157
+
158
+ ## Other details
159
+
160
+ Default `.gitignore` and `.gitattributes` files are installed on initialisation. They are never
161
+ modified by this tool. Please customise as necessary.
162
+
@@ -0,0 +1,129 @@
1
+ # Baldwin
2
+
3
+ [![Python versions](https://img.shields.io/pypi/pyversions/baldwin.svg?color=blue&logo=python&logoColor=white)](https://www.python.org/)
4
+ [![PyPI - Version](https://img.shields.io/pypi/v/baldwin)](https://pypi.org/project/baldwin/)
5
+ [![GitHub tag (with filter)](https://img.shields.io/github/v/tag/Tatsh/baldwin)](https://github.com/Tatsh/baldwin/tags)
6
+ [![License](https://img.shields.io/github/license/Tatsh/baldwin)](https://github.com/Tatsh/baldwin/blob/master/LICENSE.txt)
7
+ [![GitHub commits since latest release (by SemVer including pre-releases)](https://img.shields.io/github/commits-since/Tatsh/baldwin/v0.0.10/master)](https://github.com/Tatsh/baldwin/compare/v0.0.10...master)
8
+ [![CodeQL](https://github.com/Tatsh/baldwin/actions/workflows/codeql.yml/badge.svg)](https://github.com/Tatsh/baldwin/actions/workflows/codeql.yml)
9
+ [![QA](https://github.com/Tatsh/baldwin/actions/workflows/qa.yml/badge.svg)](https://github.com/Tatsh/baldwin/actions/workflows/qa.yml)
10
+ [![Tests](https://github.com/Tatsh/baldwin/actions/workflows/tests.yml/badge.svg)](https://github.com/Tatsh/baldwin/actions/workflows/tests.yml)
11
+ [![Coverage Status](https://coveralls.io/repos/github/Tatsh/baldwin/badge.svg?branch=master)](https://coveralls.io/github/Tatsh/baldwin?branch=master)
12
+ [![Documentation Status](https://readthedocs.org/projects/baldwin/badge/?version=latest)](https://baldwin.readthedocs.org/?badge=latest)
13
+ [![mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/)
14
+ [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit)
15
+ [![pydocstyle](https://img.shields.io/badge/pydocstyle-enabled-AD4CD3)](http://www.pydocstyle.org/en/stable/)
16
+ [![pytest](https://img.shields.io/badge/pytest-zz?logo=Pytest&labelColor=black&color=black)](https://docs.pytest.org/en/stable/)
17
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
18
+ [![Downloads](https://static.pepy.tech/badge/baldwin/month)](https://pepy.tech/project/baldwin)
19
+ [![Stargazers](https://img.shields.io/github/stars/Tatsh/baldwin?logo=github&style=flat)](https://github.com/Tatsh/baldwin/stargazers)
20
+
21
+ [![@Tatsh](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fpublic.api.bsky.app%2Fxrpc%2Fapp.bsky.actor.getProfile%2F%3Factor%3Ddid%3Aplc%3Auq42idtvuccnmtl57nsucz72%26query%3D%24.followersCount%26style%3Dsocial%26logo%3Dbluesky%26label%3DFollow%2520%40Tatsh&query=%24.followersCount&style=social&logo=bluesky&label=Follow%20%40Tatsh)](https://bsky.app/profile/Tatsh.bsky.social)
22
+ [![Mastodon Follow](https://img.shields.io/mastodon/follow/109370961877277568?domain=hostux.social&style=social)](https://hostux.social/@Tatsh)
23
+
24
+ This is a conversion of my simple scripts to version my home directory with very specific excludes
25
+ and formatting every file upon commit so that readable diffs can be generated.
26
+
27
+ ## Installation
28
+
29
+ ### Pip
30
+
31
+ ```shell
32
+ pip install baldwin
33
+ ```
34
+
35
+ ## Usage
36
+
37
+ ```plain
38
+ $ bw -h
39
+ Usage: bw [OPTIONS] COMMAND [ARGS]...
40
+
41
+ Manage a home directory with Git.
42
+
43
+ Options:
44
+ -d, --debug Enable debug logging.
45
+ -h, --help Show this message and exit.
46
+
47
+ Commands:
48
+ auto-commit
49
+ format
50
+ git
51
+ info
52
+ init
53
+ install-units
54
+ ```
55
+
56
+ In addition to the `bw` command, `hgit` is a shortcut for `bw git`.
57
+
58
+ ### Start a new repository
59
+
60
+ ```shell
61
+ bw init
62
+ ```
63
+
64
+ Find out where the Git directory is by running `bw info`. This can be done even if `init` has not
65
+ been run.
66
+
67
+ ### Automation
68
+
69
+ #### systemd
70
+
71
+ ```shell
72
+ bw install-units
73
+ ```
74
+
75
+ This will install a timer that will automatically make a new commit every 6 hours. It does not push.
76
+
77
+ Keep in mind that systemd units require a full path to the executable, so you must keep the unit
78
+ up-to-date if you move where you install this package. Simply run `bw install-units` again.
79
+
80
+ Note that user systemd units only run while logged in.
81
+
82
+ To disable and remove the units, use the following commands:
83
+
84
+ ```shell
85
+ systemctl disable --now home-vcs.timer
86
+ rm ~/.config/systemd/user/home-vcs.*
87
+ ```
88
+
89
+ ### Pushing
90
+
91
+ To push, use either of the following:
92
+
93
+ - `bw git push`
94
+ - `hgit push`
95
+
96
+ The above also demonstrates that `bw git`/`hgit` are just frontends to `git` with the correct
97
+ environment applied.
98
+
99
+ ## Formatting
100
+
101
+ If Prettier is installed, it will be used to format files. The configuration used comes with this
102
+ package. Having consistent formatting allows for nice diffs to be generated.
103
+
104
+ If you have initialised a repository without having `prettier` or `jq` in `PATH`, you need to run the
105
+ following commands to enable readable diffs:
106
+
107
+ ```shell
108
+ hgit config diff.json.textconv 'jq -MS .'
109
+ hgit config diff.json.cachetextconv true
110
+ hgit config diff.yaml.textconv 'prettier --no-editorconfig --parser yaml'
111
+ hgit config diff.yaml.cachetextconv true
112
+ ```
113
+
114
+ If you have the XML plugin installed:
115
+
116
+ ```shell
117
+ hgit config diff.xml.textconv 'prettier --no-editorconfig --parser xml --xml-whitespace-sensitivity ignore'
118
+ hgit config diff.xml.cachetextconv true
119
+ ```
120
+
121
+ ## Binary files
122
+
123
+ Any file that is untracked and detected to be binary will not be added. Use `hgit add` to add a
124
+ binary file manually.
125
+
126
+ ## Other details
127
+
128
+ Default `.gitignore` and `.gitattributes` files are installed on initialisation. They are never
129
+ modified by this tool. Please customise as necessary.
@@ -0,0 +1,4 @@
1
+ """baldwin module."""
2
+ from __future__ import annotations
3
+
4
+ __version__ = '0.0.10'
@@ -0,0 +1,6 @@
1
+ """Main entry point for the Baldwin CLI."""
2
+ from __future__ import annotations
3
+
4
+ from .main import baldwin as main
5
+
6
+ main()
@@ -0,0 +1,230 @@
1
+ """Baldwin library."""
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass
5
+ from datetime import datetime, timezone
6
+ from importlib import resources
7
+ from itertools import chain
8
+ from pathlib import Path
9
+ from shlex import quote
10
+ from shutil import which
11
+ from typing import TYPE_CHECKING, Literal, cast
12
+ import logging
13
+ import os
14
+ import subprocess as sp
15
+
16
+ from binaryornot.check import is_binary
17
+ from git import Actor, Repo
18
+ import platformdirs
19
+ import tomlkit
20
+
21
+ if TYPE_CHECKING:
22
+ from collections.abc import Iterable
23
+
24
+ from .typing import BaldwinConfigContainer
25
+
26
+ log = logging.getLogger(__name__)
27
+
28
+
29
+ def git(args: Iterable[str]) -> None:
30
+ """Front-end to git with git-dir and work-tree passed."""
31
+ # Pass these arguments because of the hgit shortcut
32
+ cmd = ('git', f'--git-dir={get_git_path()}', f'--work-tree={Path.home()}', *args)
33
+ log.debug('Running: %s', ' '.join(quote(x) for x in cmd))
34
+ sp.run(cmd, check=False) # do not use env= because env vars controlling colour will be lost
35
+
36
+
37
+ def init() -> None:
38
+ """
39
+ Start tracking a home directory.
40
+
41
+ Does nothing if the Git directory already exists.
42
+ """
43
+ if (git_path := get_git_path()).exists():
44
+ return
45
+ repo = Repo.init(git_path, expand_vars=False)
46
+ repo.git.execute(('git', 'config', 'commit.gpgsign', 'false'))
47
+ gitattributes = Path.home() / '.gitattributes'
48
+ gitattributes.write_text(resources.read_text('baldwin.resources', 'default_gitattributes.txt'))
49
+ gitignore = Path.home() / '.gitignore'
50
+ gitignore.write_text(resources.read_text('baldwin.resources', 'default_gitignore.txt'))
51
+ repo.index.add([gitattributes, gitignore])
52
+ if jq := which('jq'):
53
+ repo.git.execute(('git', 'config', 'diff.json.textconv', f'"{jq}" -MS .'))
54
+ repo.git.execute(('git', 'config', 'diff.json.cachetextconv', 'true'))
55
+ if (prettier := which('prettier')):
56
+ node_modules_path = (Path(prettier).resolve(strict=True).parent / '..' /
57
+ '..').resolve(strict=True)
58
+ if (node_modules_path / '@prettier/plugin-xml/src/plugin.js').exists():
59
+ repo.git.execute((
60
+ 'git', 'config', 'diff.xml.textconv',
61
+ f'"{prettier}" --no-editorconfig --parser xml --xml-whitespace-sensitivity ignore'))
62
+ repo.git.execute(('git', 'config', 'diff.xml.cachetextconv', 'true'))
63
+ repo.git.execute(('git', 'config', 'diff.yaml.textconv',
64
+ f'"{prettier}" --no-editorconfig --parser yaml'))
65
+ repo.git.execute(('git', 'config', 'diff.yaml.cachetextconv', 'true'))
66
+
67
+
68
+ def auto_commit() -> None:
69
+ """Automated commit of changed and untracked files."""
70
+ def can_open(file: Path) -> bool:
71
+ """Check if a file can be opened."""
72
+ try:
73
+ with file.open('rb'):
74
+ pass
75
+ except OSError:
76
+ return False
77
+ return True
78
+
79
+ repo = get_repo()
80
+ diff_items = [Path.home() / e.a_path for e in repo.index.diff(None) if e.a_path is not None]
81
+ items_to_add = [
82
+ *[p for p in diff_items if p.exists()], *[
83
+ x for x in (Path.home() / y for y in repo.untracked_files)
84
+ if can_open(x) and x.is_file() and not is_binary(str(x))
85
+ ]
86
+ ]
87
+ items_to_remove = [p for p in diff_items if not p.exists()]
88
+ if items_to_add:
89
+ format_(items_to_add)
90
+ repo.index.add(items_to_add)
91
+ if items_to_remove:
92
+ repo.index.remove(items_to_remove)
93
+ if items_to_add or items_to_remove or len(repo.index.diff('HEAD')) > 0:
94
+ repo.index.commit(f'Automatic commit @ {datetime.now(tz=timezone.utc).isoformat()}',
95
+ committer=Actor('Auto-commiter', 'hgit@tat.sh'))
96
+
97
+
98
+ @dataclass
99
+ class RepoInfo:
100
+ """General repository information."""
101
+ git_dir_path: Path
102
+ """Git directory."""
103
+ work_tree_path: Path
104
+ """Work tree."""
105
+
106
+
107
+ def repo_info() -> RepoInfo:
108
+ """Get general repository information."""
109
+ return RepoInfo(git_dir_path=get_git_path(), work_tree_path=Path.home())
110
+
111
+
112
+ def install_units() -> None:
113
+ """
114
+ Install systemd units for automatic committing.
115
+
116
+ Raises
117
+ ------
118
+ FileNotFoundError
119
+ """
120
+ bw = which('bw')
121
+ if not bw:
122
+ raise FileNotFoundError
123
+ service_file = Path('~/.config/systemd/user/home-vcs.service').expanduser()
124
+ service_file.write_text(f"""[Unit]
125
+ Description=Home directory VCS commit
126
+
127
+ [Service]
128
+ Environment=NO_COLOR=1
129
+ ExecStart={bw} auto-commit
130
+ Type=oneshot
131
+ """)
132
+ log.debug('Wrote to `%s`.', service_file)
133
+ timer_file = Path('~/.config/systemd/user/home-vcs.timer').expanduser()
134
+ timer_file.write_text("""[Unit]
135
+ Description=Hexahourly trigger for Home directory VCS
136
+
137
+ [Timer]
138
+ OnCalendar=0/6:0:00
139
+
140
+ [Install]
141
+ WantedBy=timers.target
142
+ """)
143
+ log.debug('Wrote to `%s`.', timer_file)
144
+ cmd: tuple[str, ...] = ('systemctl', '--user', 'enable', '--now', 'home-vcs.timer')
145
+ log.debug('Running: %s', ' '.join(quote(x) for x in cmd))
146
+ sp.run(cmd, check=True)
147
+ cmd = ('systemctl', '--user', 'daemon-reload')
148
+ log.debug('Running: %s', ' '.join(quote(x) for x in cmd))
149
+ sp.run(('systemctl', '--user', 'daemon-reload'), check=True)
150
+
151
+
152
+ def get_git_path() -> Path:
153
+ """
154
+ Get the Git directory (``GIT_DIR``).
155
+
156
+ This path is platform-specific. On Windows, the Roaming AppData directory will be used.
157
+ """
158
+ return platformdirs.user_data_path('home-git', roaming=True)
159
+
160
+
161
+ def get_config() -> BaldwinConfigContainer:
162
+ """Get the configuration (TOML file)."""
163
+ config_file = platformdirs.user_config_path('baldwin', roaming=True) / 'config.toml'
164
+ if not config_file.exists():
165
+ return {}
166
+ return cast('BaldwinConfigContainer', tomlkit.loads(config_file.read_text()).unwrap())
167
+
168
+
169
+ def get_repo() -> Repo:
170
+ """
171
+ Get a :py:class:`git.Repo` object.
172
+
173
+ Also disables GPG signing for the repository.
174
+ """
175
+ repo = Repo(get_git_path(), expand_vars=False)
176
+ repo.git.execute(('git', 'config', 'commit.gpgsign', 'false'))
177
+ return repo
178
+
179
+
180
+ def format_(filenames: Iterable[Path | str] | None = None,
181
+ log_level: Literal['silent', 'error', 'warn', 'log', 'debug'] = 'error') -> None:
182
+ """
183
+ Format untracked and modified files in the repository.
184
+
185
+ Does nothing if Prettier is not in ``PATH``.
186
+
187
+ The following plugins will be detected and enabled if found:
188
+
189
+ * @prettier/plugin-xml
190
+ * prettier-plugin-ini
191
+ * prettier-plugin-sort-json
192
+ * prettier-plugin-toml
193
+ """
194
+ if filenames is None:
195
+ repo = get_repo()
196
+ filenames = (*(Path.home() / d.a_path
197
+ for d in repo.index.diff(None) if d.a_path is not None),
198
+ *(x for x in (Path.home() / y for y in repo.untracked_files)
199
+ if x.is_file() and not is_binary(str(x))))
200
+ if not (filenames := list(filenames)):
201
+ log.debug('No files to format.')
202
+ return
203
+ if not (prettier := which('prettier')):
204
+ log.debug('Prettier not found in PATH.')
205
+ return
206
+ with resources.path('baldwin.resources', 'prettier.config.json') as default_config_file:
207
+ config_file = get_config().get('baldwin', {
208
+ 'prettier_config': str(default_config_file)
209
+ }).get('prettier_config')
210
+ # Detect plugins
211
+ node_modules_path = (Path(prettier).resolve(strict=True).parent /
212
+ '../..').resolve(strict=True)
213
+ cmd_prefix = (prettier, '--config', str(config_file), '--write',
214
+ '--no-error-on-unmatched-pattern', '--ignore-unknown', '--log-level',
215
+ log_level, *chain(*(('--plugin', str(fp))
216
+ for module in ('@prettier/plugin-xml/src/plugin.js',
217
+ 'prettier-plugin-ini/src/plugin.js',
218
+ 'prettier-plugin-sort-json/dist/index.js',
219
+ 'prettier-plugin-toml/lib/index.cjs')
220
+ if (fp := (node_modules_path / module)).exists())))
221
+ for filename in filenames:
222
+ cmd = (*cmd_prefix, str(filename))
223
+ log.debug('Running: %s', ' '.join(quote(x) for x in cmd))
224
+ sp.run(cmd, check=False)
225
+
226
+
227
+ def set_git_env_vars() -> None:
228
+ """Set environment variables for Git."""
229
+ os.environ['GIT_DIR'] = str(get_git_path())
230
+ os.environ['GIT_WORK_TREE'] = str(Path.home())
@@ -0,0 +1,82 @@
1
+ """Commands."""
2
+ from __future__ import annotations
3
+
4
+ import logging
5
+
6
+ from bascom import setup_logging
7
+ import click
8
+
9
+ from .lib import (
10
+ auto_commit as auto_commit_,
11
+ format_,
12
+ git as git_,
13
+ init as init_,
14
+ install_units as install_units_,
15
+ repo_info,
16
+ set_git_env_vars,
17
+ )
18
+
19
+ log = logging.getLogger(__name__)
20
+
21
+ __all__ = ('baldwin', 'git')
22
+
23
+
24
+ @click.group(context_settings={'help_option_names': ('-h', '--help')})
25
+ @click.option('-d', '--debug', help='Enable debug logging.', is_flag=True)
26
+ def baldwin(*, debug: bool = False) -> None:
27
+ """Manage a home directory with Git."""
28
+ set_git_env_vars()
29
+ setup_logging(debug=debug, loggers={'baldwin': {
30
+ 'handlers': ('console',),
31
+ 'propagate': False,
32
+ }})
33
+
34
+
35
+ @click.command(context_settings={
36
+ 'help_option_names': ('-h', '--help'),
37
+ 'ignore_unknown_options': True
38
+ })
39
+ @click.argument('args', nargs=-1, type=click.UNPROCESSED)
40
+ def git(args: tuple[str, ...]) -> None:
41
+ """Wrap git with git-dir and work-tree passed."""
42
+ git_(args)
43
+
44
+
45
+ @click.command(context_settings={'help_option_names': ('-h', '--help')})
46
+ def init() -> None:
47
+ """Start tracking a home directory."""
48
+ init_()
49
+
50
+
51
+ @click.command(context_settings={'help_option_names': ('-h', '--help')})
52
+ def auto_commit() -> None:
53
+ """Automated commit of changed and untracked files."""
54
+ auto_commit_()
55
+
56
+
57
+ @click.command(context_settings={'help_option_names': ('-h', '--help')})
58
+ def format() -> None: # noqa: A001
59
+ """Format changed and untracked files."""
60
+ format_()
61
+
62
+
63
+ @click.command(context_settings={'help_option_names': ('-h', '--help')})
64
+ def info() -> None:
65
+ """Get basic information about the repository."""
66
+ data = repo_info()
67
+ click.echo(f'git-dir path: {data.git_dir_path}')
68
+ click.echo(f'work-tree path: {data.work_tree_path}')
69
+
70
+
71
+ @click.command(context_settings={'help_option_names': ('-h', '--help')})
72
+ def install_units() -> None:
73
+ """Install systemd units for automatic committing."""
74
+ install_units_()
75
+
76
+
77
+ baldwin.add_command(auto_commit)
78
+ baldwin.add_command(format)
79
+ baldwin.add_command(git)
80
+ baldwin.add_command(info)
81
+ baldwin.add_command(init)
82
+ baldwin.add_command(install_units)
File without changes
@@ -0,0 +1,15 @@
1
+ """Typing helpers."""
2
+ from __future__ import annotations
3
+
4
+ from typing import TypedDict
5
+
6
+ from typing_extensions import NotRequired
7
+
8
+
9
+ class _BaldwinBaldwin(TypedDict):
10
+ prettier_config: str
11
+
12
+
13
+ class BaldwinConfigContainer(TypedDict):
14
+ """Container for Baldwin configuration."""
15
+ baldwin: NotRequired[_BaldwinBaldwin]