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.

@@ -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.7
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.12,<4
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
+ [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit)](https://github.com/pre-commit/pre-commit)
28
35
  [![QA](https://github.com/Tatsh/baldwin/actions/workflows/qa.yml/badge.svg)](https://github.com/Tatsh/baldwin/actions/workflows/qa.yml)
29
36
  [![Tests](https://github.com/Tatsh/baldwin/actions/workflows/tests.yml/badge.svg)](https://github.com/Tatsh/baldwin/actions/workflows/tests.yml)
30
37
  [![Coverage Status](https://coveralls.io/repos/github/Tatsh/baldwin/badge.svg?branch=master)](https://coveralls.io/github/Tatsh/baldwin?branch=master)
@@ -32,7 +39,9 @@ Description-Content-Type: text/markdown
32
39
  ![PyPI - Version](https://img.shields.io/pypi/v/baldwin)
33
40
  ![GitHub tag (with filter)](https://img.shields.io/github/v/tag/Tatsh/baldwin)
34
41
  ![GitHub](https://img.shields.io/github/license/Tatsh/baldwin)
35
- ![GitHub commits since latest release (by SemVer including pre-releases)](https://img.shields.io/github/commits-since/Tatsh/baldwin/v0.0.7/master)
42
+ ![GitHub commits since latest release (by SemVer including pre-releases)](https://img.shields.io/github/commits-since/Tatsh/baldwin/v0.0.9/master)
43
+
44
+ [![@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)
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
+ [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit)](https://github.com/pre-commit/pre-commit)
3
4
  [![QA](https://github.com/Tatsh/baldwin/actions/workflows/qa.yml/badge.svg)](https://github.com/Tatsh/baldwin/actions/workflows/qa.yml)
4
5
  [![Tests](https://github.com/Tatsh/baldwin/actions/workflows/tests.yml/badge.svg)](https://github.com/Tatsh/baldwin/actions/workflows/tests.yml)
5
6
  [![Coverage Status](https://coveralls.io/repos/github/Tatsh/baldwin/badge.svg?branch=master)](https://coveralls.io/github/Tatsh/baldwin?branch=master)
@@ -7,7 +8,9 @@
7
8
  ![PyPI - Version](https://img.shields.io/pypi/v/baldwin)
8
9
  ![GitHub tag (with filter)](https://img.shields.io/github/v/tag/Tatsh/baldwin)
9
10
  ![GitHub](https://img.shields.io/github/license/Tatsh/baldwin)
10
- ![GitHub commits since latest release (by SemVer including pre-releases)](https://img.shields.io/github/commits-since/Tatsh/baldwin/v0.0.7/master)
11
+ ![GitHub commits since latest release (by SemVer including pre-releases)](https://img.shields.io/github/commits-since/Tatsh/baldwin/v0.0.9/master)
12
+
13
+ [![@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)
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.
@@ -0,0 +1,4 @@
1
+ """baldwin module."""
2
+ from __future__ import annotations
3
+
4
+ __version__ = '0.0.9'
@@ -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()
@@ -1,13 +1,14 @@
1
1
  """Baldwin library."""
2
- from collections.abc import Iterable
2
+ from __future__ import annotations
3
+
3
4
  from dataclasses import dataclass
4
- from datetime import UTC, datetime
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
- for y in repo.untracked_files) if x.is_file() and not is_binary(str(x))
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()] # pragma: no cover
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=UTC).isoformat()}',
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
- """Install systemd units for automatic committing."""
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
- Type=oneshot
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
- with resources.path('baldwin.resources', 'prettier.config.json') as config_file:
173
- if not (prettier := which('prettier')):
174
- return
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
- '..').resolve(strict=True)
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__ = ('baldwin_main', 'git_main')
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 baldwin_main(*, debug: bool = False) -> None:
26
+ def baldwin(*, debug: bool = False) -> None:
23
27
  """Manage a home directory with Git."""
24
28
  set_git_env_vars()
25
- logging.basicConfig(level=logging.DEBUG if debug else logging.ERROR)
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 git_main(args: tuple[str, ...]) -> None:
37
+ def git(args: tuple[str, ...]) -> None:
34
38
  """Wrap git with git-dir and work-tree passed."""
35
- git(args)
39
+ git_(args)
36
40
 
37
41
 
38
42
  @click.command(context_settings={'help_option_names': ('-h', '--help')})
39
- def init_main() -> None:
43
+ def init() -> None:
40
44
  """Start tracking a home directory."""
41
- init()
45
+ init_()
42
46
 
43
47
 
44
48
  @click.command(context_settings={'help_option_names': ('-h', '--help')})
45
- def auto_commit_main() -> None:
49
+ def auto_commit() -> None:
46
50
  """Automated commit of changed and untracked files."""
47
- auto_commit()
51
+ auto_commit_()
48
52
 
49
53
 
50
54
  @click.command(context_settings={'help_option_names': ('-h', '--help')})
51
- def format_main() -> None:
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 info_main() -> None:
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 install_units_main() -> None:
69
+ def install_units() -> None:
66
70
  """Install systemd units for automatic committing."""
67
- install_units()
71
+ install_units_()
68
72
 
69
73
 
70
- baldwin_main.add_command(auto_commit_main, 'auto-commit')
71
- baldwin_main.add_command(format_main, 'format')
72
- baldwin_main.add_command(git_main, 'git')
73
- baldwin_main.add_command(info_main, 'info')
74
- baldwin_main.add_command(init_main, 'init')
75
- baldwin_main.add_command(install_units_main, 'install-units')
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.7"
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
- issues = "https://github.com/Tatsh/baldwin/issues"
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
- ".project.jsonnet",
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
- omit = ["tests/conftest.py", "tests/test_*.py"]
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 = ["tests/conftest.py", "tests/test_*.py"]
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.12"
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.12,<4"
76
-
77
- [tool.poetry.group]
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.0.0"
85
- mypy = "^1.13.0"
86
- ruff = "^0.8.0"
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
- tomlkit = "^0.13.2"
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
- coveralls = "^3.3.1"
106
- mock = "^5.1.0"
107
- pytest = "^8.3.3"
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.12"
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 = "py312"
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
- "TCH",
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
- "D100",
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,10 @@
1
+ [tool]
2
+
3
+ [tool.ruff]
4
+ extend = "../pyproject.toml"
5
+
6
+ [tool.ruff.lint]
7
+ extend-ignore = ["ARG001", "ARG002", "D100", "D103", "INP001", "PLC0415", "PLR2004", "S105", "S106"]
8
+
9
+ [tool.ruff.lint.pep8-naming]
10
+ extend-ignore-names = ["test_*"]
@@ -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.
@@ -1,2 +0,0 @@
1
- """Manage a home directory with Git."""
2
- __version__ = '0.0.7'