macsetup 0.1.0__py3-none-any.whl

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.
macsetup/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """Declarative provisioning for macOS: packages and dotfiles, with a preview."""
@@ -0,0 +1,17 @@
1
+ # Managed by macsetup. Edits here are overwritten on the next `macsetup apply`;
2
+ # the source is files/git/gitconfig.j2 in the manifests repo.
3
+ #
4
+ # Rendered on {{ machine }}.
5
+
6
+ [user]
7
+ name = {{ git_name }}
8
+ email = {{ git_email }}
9
+
10
+ [init]
11
+ defaultBranch = main
12
+
13
+ # Refuse to invent an identity. Without this, an unset user.email makes git
14
+ # silently guess you@<hostname>.local and stamp it on the commit -- a wrong
15
+ # address you only discover later, in a published history.
16
+ [user]
17
+ useConfigOnly = true
@@ -0,0 +1,6 @@
1
+ # Sources every fragment macsetup has installed. Add a fragment by adding it to
2
+ # a module manifest -- this block never needs to change.
3
+ for _f in "$HOME"/.config/macsetup/zsh/*.zsh(N); do
4
+ [ -r "$_f" ] && source "$_f"
5
+ done
6
+ unset _f
@@ -0,0 +1,10 @@
1
+ # HOMEBREW_PREFIX, without shelling out to `brew --prefix` on every startup.
2
+ # `brew shellenv` would set this, but it costs a subprocess and PATH is already
3
+ # handled by /etc/paths.d, so just resolve the two possible locations.
4
+ if [[ -z ${HOMEBREW_PREFIX} ]]; then
5
+ if [[ -x /opt/homebrew/bin/brew ]]; then
6
+ export HOMEBREW_PREFIX=/opt/homebrew # Apple Silicon
7
+ elif [[ -x /usr/local/bin/brew ]]; then
8
+ export HOMEBREW_PREFIX=/usr/local # Intel
9
+ fi
10
+ fi
@@ -0,0 +1,22 @@
1
+ # zsh completion. Must run before any fragment that calls compdef -- tool
2
+ # completion scripts fail with "command not found: compdef" otherwise.
3
+ #
4
+ # Homebrew drops completions in share/zsh/site-functions (_brew, _git, _gh,
5
+ # _docker, _uv, _uvx, _wezterm, _zed, _aws), so adding it to fpath is all that
6
+ # is needed; no per-tool `eval` at startup.
7
+ if [[ -n ${HOMEBREW_PREFIX} && -d ${HOMEBREW_PREFIX}/share/zsh/site-functions ]]; then
8
+ fpath=("${HOMEBREW_PREFIX}/share/zsh/site-functions" $fpath)
9
+ fi
10
+
11
+ autoload -Uz compinit
12
+
13
+ # compinit's security audit is slow, so run it in full at most once a day and
14
+ # use the cached dump otherwise. -i skips the prompt about Homebrew's
15
+ # group-writable directories, which are expected rather than suspicious.
16
+ _ms_zcompdump="${ZDOTDIR:-$HOME}/.zcompdump"
17
+ if [[ -n ${_ms_zcompdump}(#qN.mh+24) ]]; then
18
+ compinit -i -d "${_ms_zcompdump}"
19
+ else
20
+ compinit -C -i -d "${_ms_zcompdump}"
21
+ fi
22
+ unset _ms_zcompdump
@@ -0,0 +1,18 @@
1
+ # The two `cli` tools that need shell wiring rather than just a binary on PATH.
2
+ #
3
+ # Installed as 90-cli.zsh so zoxide initialises after every other fragment:
4
+ # zoxide's own doctor check wants its init at the end of the shell config, and
5
+ # it says so on stderr when it is not. The gap to 40-p10k leaves room for
6
+ # fragments that have to load earlier.
7
+
8
+ # `always` rather than the default `auto`, which drops the icons as soon as the
9
+ # output is not a terminal -- `ls | less` should still show them. The glyphs come
10
+ # from the Nerd Font, which is why the cli module requires the fonts module.
11
+ alias ls="eza --icons=always"
12
+
13
+ # zoxide tracks visited directories and jumps to a match on a partial name.
14
+ # Aliasing cd to z keeps `cd some/real/path` working, since z falls back to a
15
+ # plain cd when the argument resolves to a real directory. `zi` still opens the
16
+ # interactive picker.
17
+ eval "$(zoxide init zsh)"
18
+ alias cd="z"
@@ -0,0 +1,8 @@
1
+ # Node — nvm, installed via Homebrew. HOMEBREW_PREFIX comes from
2
+ # 00-homebrew.zsh; the literal fallback keeps this fragment usable standalone.
3
+ export NVM_DIR="$HOME/.nvm"
4
+
5
+ _ms_nvm="${HOMEBREW_PREFIX:-/opt/homebrew}/opt/nvm"
6
+ [ -s "$_ms_nvm/nvm.sh" ] && \. "$_ms_nvm/nvm.sh"
7
+ [ -s "$_ms_nvm/etc/bash_completion.d/nvm" ] && \. "$_ms_nvm/etc/bash_completion.d/nvm"
8
+ unset _ms_nvm
@@ -0,0 +1,12 @@
1
+ # Python — uv only. pyenv and poetry were dropped on 2026-07-26.
2
+ #
3
+ # No completion setup here: Homebrew already installs _uv and _uvx into
4
+ # site-functions, which 10-completion.zsh puts on fpath. Calling
5
+ # `uv generate-shell-completion` at startup would duplicate that and cost two
6
+ # subprocesses per shell.
7
+
8
+ # `uv tool install` puts shims here.
9
+ export PATH="$HOME/.local/bin:$PATH"
10
+
11
+ # Prefer uv's managed interpreters over whatever the system ships.
12
+ export UV_PYTHON_PREFERENCE=managed
@@ -0,0 +1,16 @@
1
+ id: cli
2
+ name: CLI tools
3
+ description: Terminal utilities
4
+ # eza renders Nerd Font glyphs, so the font is a hard dependency rather than
5
+ # something the wizard could leave unticked. Anything with hard dependencies
6
+ # belongs in its own module for exactly this reason.
7
+ requires: [fonts]
8
+ packages:
9
+ - tree
10
+ - ripgrep
11
+ - eza
12
+ - zoxide
13
+ files:
14
+ # 90 so zoxide's init is the last fragment to run; see the file.
15
+ - copy: zsh/cli.zsh
16
+ to: ~/.config/macsetup/zsh/90-cli.zsh
@@ -0,0 +1,7 @@
1
+ id: core
2
+ name: Core
3
+ description: Installed on every machine, no questions asked
4
+ always: true
5
+ packages:
6
+ - git
7
+ - gh
@@ -0,0 +1,16 @@
1
+ id: editors
2
+ name: Editors & IDEs
3
+ description: Pick the ones this machine needs
4
+ # `select: pick` turns this into a per-package menu in the wizard, with
5
+ # `default:` pre-ticked.
6
+ #
7
+ # Picks gate packages, not files: a module's `files` are applied whenever the
8
+ # module is selected, whichever packages you tick. So anything that ships
9
+ # config cannot live as a pick -- its config would land on machines that
10
+ # unticked it. Give those their own module instead.
11
+ select: pick
12
+ default: [visual-studio-code]
13
+ packages:
14
+ - {name: visual-studio-code, cask: true}
15
+ - {name: zed, cask: true}
16
+ - {name: jetbrains-toolbox, cask: true}
@@ -0,0 +1,5 @@
1
+ id: fonts
2
+ name: Fonts
3
+ description: Nerd Fonts, pulled in by anything that renders glyphs
4
+ packages:
5
+ - {name: font-meslo-lg-nerd-font, cask: true}
@@ -0,0 +1,15 @@
1
+ id: git-identity
2
+ name: Git identity
3
+ description: A rendered gitconfig, as a demonstration of `render:`
4
+ # Rendered rather than copied: identity is the one part that may differ per
5
+ # machine. Override git_name / git_email in the [vars] table of machine.toml;
6
+ # the catalog defaults are in manifests/vars.yaml.
7
+ #
8
+ # The destination is deliberately inert. This is the catalog that ships inside
9
+ # the wheel, so `macsetup init` on a machine that has not written its own
10
+ # manifests yet will run it -- and an example that overwrites a real
11
+ # ~/.gitconfig on first run is a trap, not a demonstration. Point this at
12
+ # ~/.gitconfig in your own catalog, once you mean it.
13
+ files:
14
+ - render: git/gitconfig.j2
15
+ to: ~/.config/macsetup/example-gitconfig
@@ -0,0 +1,8 @@
1
+ id: lang-node
2
+ name: Node
3
+ description: nvm, so a project can pin its own version
4
+ packages:
5
+ - nvm
6
+ files:
7
+ - copy: zsh/node.zsh
8
+ to: ~/.config/macsetup/zsh/30-node.zsh
@@ -0,0 +1,8 @@
1
+ id: lang-python
2
+ name: Python
3
+ description: uv, and nothing else -- it supplies its own interpreters
4
+ packages:
5
+ - uv
6
+ files:
7
+ - copy: zsh/python.zsh
8
+ to: ~/.config/macsetup/zsh/20-python.zsh
@@ -0,0 +1,18 @@
1
+ id: shell
2
+ name: Shell
3
+ description: The managed ~/.zshrc block, Homebrew prefix and zsh completion
4
+ always: true
5
+ # No packages: this module exists purely to place files. `apply --only shell`
6
+ # therefore never invokes brew, which makes it the safest module to try
7
+ # against a sandboxed HOME.
8
+ files:
9
+ # One managed block in a file macsetup does not own. Lines outside the
10
+ # markers are left alone; re-applying rewrites only between them.
11
+ - block: shell/zshrc-block.zsh
12
+ into: ~/.zshrc
13
+ # Numbered so they load before the tool fragments: completion must be
14
+ # initialised before anything calls compdef.
15
+ - copy: zsh/00-homebrew.zsh
16
+ to: ~/.config/macsetup/zsh/00-homebrew.zsh
17
+ - copy: zsh/10-completion.zsh
18
+ to: ~/.config/macsetup/zsh/10-completion.zsh
@@ -0,0 +1,26 @@
1
+ # Profiles bundle modules. Modules marked `always: true` are folded in
2
+ # automatically and do not need listing here.
3
+ #
4
+ # These are an example. Replace them with the way you actually use your
5
+ # machines -- `brew bundle dump` on each one and diffing the results is a
6
+ # good way to turn guesswork into data.
7
+ profiles:
8
+ work:
9
+ - cli
10
+ - git-identity
11
+ - lang-python
12
+ - lang-node
13
+ - editors
14
+
15
+ personal:
16
+ - cli
17
+ - git-identity
18
+ - lang-python
19
+ - editors
20
+
21
+ # A loaner or travel machine: enough to be useful in a terminal, nothing else.
22
+ minimal:
23
+ - cli
24
+
25
+ full:
26
+ - "*"
@@ -0,0 +1,10 @@
1
+ # Default values for `render:` template variables. The engine ships none of
2
+ # its own -- identity is data, so it lives here with the manifests that use
3
+ # it. A machine overrides any of these in the [vars] table of
4
+ # ~/.config/macsetup/machine.toml.
5
+ #
6
+ # Rendering uses StrictUndefined: a template referencing a variable with no
7
+ # value is a hard error, not a silently empty setting.
8
+ vars:
9
+ git_name: Your Name
10
+ git_email: you@example.com
macsetup/brewfile.py ADDED
@@ -0,0 +1,59 @@
1
+ """Brewfile generation and the `brew bundle` driver.
2
+
3
+ macsetup does not install anything itself. It decides *what* should be
4
+ installed and hands a generated Brewfile to Homebrew's bundler, which already
5
+ knows about retries, cask quarantine, sudo prompts and upgrade semantics.
6
+
7
+ Generated files are written to a predictable cache dir and deliberately left
8
+ behind after a run, so a failure can be inspected.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from collections.abc import Iterable
14
+ from pathlib import Path
15
+
16
+ from .manifest import Package
17
+ from .runner import Runner
18
+
19
+ CACHE_DIR = Path.home() / ".cache" / "macsetup" / "brewfiles"
20
+
21
+
22
+ def render(packages: Iterable[Package], *, header: str | None = None) -> str:
23
+ lines: list[str] = []
24
+ if header:
25
+ lines += [f"# {header}", "# generated by macsetup -- edits will be overwritten", ""]
26
+ # Taps and formulae before casks; brew bundle is order-insensitive but this
27
+ # reads far better when you open the file to debug something.
28
+ packages = list(packages)
29
+ lines += [p.brewfile_line() for p in packages if not p.cask and p.mas is None]
30
+ lines += [p.brewfile_line() for p in packages if p.cask]
31
+ lines += [p.brewfile_line() for p in packages if p.mas is not None]
32
+ return "\n".join(lines) + "\n"
33
+
34
+
35
+ def write(module_id: str, packages: Iterable[Package], *, cache_dir: Path | None = None) -> Path:
36
+ directory = cache_dir or CACHE_DIR
37
+ directory.mkdir(parents=True, exist_ok=True)
38
+ path = directory / f"{module_id}.Brewfile"
39
+ path.write_text(render(packages, header=f"module: {module_id}"), encoding="utf-8")
40
+ return path
41
+
42
+
43
+ def install(runner: Runner, brewfile: Path, *, upgrade: bool = False, jobs: int | None = None):
44
+ """Run `brew bundle install` against a generated Brewfile.
45
+
46
+ Homebrew upgrades outdated dependencies by default, which is wrong for
47
+ convergence -- adding one package should not turn into a 40 minute upgrade
48
+ of everything. So `--no-upgrade` unless explicitly asked.
49
+ """
50
+ cmd = ["brew", "bundle", "install", f"--file={brewfile}"]
51
+ if not upgrade:
52
+ cmd.append("--no-upgrade")
53
+ if jobs:
54
+ cmd.append(f"--jobs={jobs}")
55
+ return runner.run(cmd, capture=False)
56
+
57
+
58
+ def check(runner: Runner, brewfile: Path):
59
+ return runner.run(["brew", "bundle", "check", f"--file={brewfile}"])