mirror of
https://github.com/Xevion/dotfiles.git
synced 2026-01-30 22:24:06 -06:00
feat: introduce meta-configs pattern and relocate fonts config
- Move fonts.toml from deployed location to meta/ directory - Update install-fonts.ts to read from meta/ and support extras array - Add comprehensive documentation explaining meta-configs pattern - Add ZedMono NF font and update Zed editor keybindings/settings
This commit is contained in:
@@ -0,0 +1,98 @@
|
|||||||
|
# Meta-Configs Pattern
|
||||||
|
|
||||||
|
This repository uses a **meta-config pattern** for configuration files that drive imperative actions during `chezmoi apply`, rather than being deployed directly to the filesystem.
|
||||||
|
|
||||||
|
## What are Meta-Configs?
|
||||||
|
|
||||||
|
Meta-configs are configuration files that:
|
||||||
|
|
||||||
|
1. **Are NOT deployed** to the target system
|
||||||
|
2. **Drive scripts** that perform imperative actions (downloads, installations, modifications)
|
||||||
|
3. **Live in `meta/`** at the repository root (outside `home/`)
|
||||||
|
4. **Trigger `run_onchange_*` scripts** when their content changes
|
||||||
|
|
||||||
|
This pattern separates "what to configure" from "configuration files that get deployed."
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
chezmoi/
|
||||||
|
├── meta/ # Meta-configs (NOT deployed)
|
||||||
|
│ ├── fonts.toml # Drives font installation
|
||||||
|
│ └── ... # Future meta-configs
|
||||||
|
├── home/ # Deployed to $HOME
|
||||||
|
│ ├── .config/
|
||||||
|
│ ├── .local/
|
||||||
|
│ └── ...
|
||||||
|
└── docs/
|
||||||
|
└── meta-configs.md # This file
|
||||||
|
```
|
||||||
|
|
||||||
|
## Current Meta-Configs
|
||||||
|
|
||||||
|
### `meta/fonts.toml`
|
||||||
|
|
||||||
|
Drives the `install-fonts.ts` script to download and install fonts.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[ui]
|
||||||
|
primary = "Inter"
|
||||||
|
fallback = "Noto Sans"
|
||||||
|
|
||||||
|
[mono]
|
||||||
|
primary = "Geist Mono"
|
||||||
|
fallback = "JetBrains Mono"
|
||||||
|
|
||||||
|
[extras]
|
||||||
|
# Fonts to install without category assignment
|
||||||
|
fonts = ["ZedMono NF"]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Triggered by:** `run_onchange_after_install-fonts.sh.tmpl`
|
||||||
|
|
||||||
|
**Script:** `~/.local/bin/install-fonts.ts`
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
1. Edit a meta-config in `meta/` (e.g., `meta/fonts.toml`)
|
||||||
|
2. Run `chezmoi apply`
|
||||||
|
3. Chezmoi detects the file changed via `{{ include "../meta/fonts.toml" | sha256sum }}`
|
||||||
|
4. The corresponding `run_onchange_*` script executes
|
||||||
|
5. The script reads the meta-config and performs imperative actions
|
||||||
|
|
||||||
|
## Why This Pattern?
|
||||||
|
|
||||||
|
### Problem
|
||||||
|
|
||||||
|
Some configuration requires imperative actions:
|
||||||
|
- Downloading files from the internet
|
||||||
|
- Installing packages
|
||||||
|
- Modifying system state
|
||||||
|
|
||||||
|
These don't fit the declarative "deploy this file" model of chezmoi.
|
||||||
|
|
||||||
|
### Solution
|
||||||
|
|
||||||
|
Meta-configs provide:
|
||||||
|
- **Centralized configuration** - Easy to edit, version-controlled
|
||||||
|
- **Imperative execution** - Scripts perform the actual work
|
||||||
|
- **Change detection** - `run_onchange_*` only runs when config changes
|
||||||
|
- **Clear separation** - Meta-configs are clearly not "files to deploy"
|
||||||
|
|
||||||
|
## Adding a New Meta-Config
|
||||||
|
|
||||||
|
1. Create `meta/<name>.toml` with your configuration schema
|
||||||
|
2. Create a script in `home/dot_local/bin/` to process the config
|
||||||
|
3. Create `home/run_onchange_after_<name>.sh.tmpl` that:
|
||||||
|
- Includes a hash comment: `# hash: {{ include "../meta/<name>.toml" | sha256sum }}`
|
||||||
|
- Calls your processing script
|
||||||
|
4. Document the meta-config in this file
|
||||||
|
|
||||||
|
## Comparison with `.managed/`
|
||||||
|
|
||||||
|
| Aspect | `meta/` | `.managed/` |
|
||||||
|
| ------------ | ---------------------------------- | --------------------------------------- |
|
||||||
|
| Deployed | No | Yes (via symlinks) |
|
||||||
|
| Purpose | Drive imperative scripts | Source of truth for app configs |
|
||||||
|
| Consumed by | Chezmoi scripts | Applications (via symlinks) |
|
||||||
|
| Location | Repo root | Inside `home/` |
|
||||||
@@ -10,24 +10,38 @@
|
|||||||
"context": "Workspace",
|
"context": "Workspace",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
// "shift shift": "file_finder::Toggle"
|
// "shift shift": "file_finder::Toggle"
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && vim_mode == insert",
|
"context": "Editor && vim_mode == insert",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
// "j k": "vim::NormalBefore"
|
"j k": "vim::NormalBefore",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor",
|
"context": "Editor",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"alt-pageup": "editor::HalfPageUp"
|
"alt-pageup": "editor::HalfPageUp",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor",
|
"context": "Editor",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"alt-pagedown": "editor::HalfPageDown"
|
"alt-pagedown": "editor::HalfPageDown",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"context": "Workspace",
|
||||||
|
"bindings": {
|
||||||
|
"ctrl-tab": "pane::ActivateNextItem",
|
||||||
|
"ctrl-shift-tab": "pane::ActivatePreviousItem",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "Editor",
|
||||||
|
"bindings": {
|
||||||
|
"ctrl-alt-left": "pane::SwapItemLeft",
|
||||||
|
"ctrl-alt-right": "pane::SwapItemRight",
|
||||||
|
},
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -7,12 +7,19 @@
|
|||||||
// custom settings, run `zed: open default settings` from the
|
// custom settings, run `zed: open default settings` from the
|
||||||
// command palette (cmd-shift-p / ctrl-shift-p)
|
// command palette (cmd-shift-p / ctrl-shift-p)
|
||||||
{
|
{
|
||||||
"base_keymap": "Cursor",
|
"vim_mode": false,
|
||||||
|
"icon_theme": "Material Icon Theme",
|
||||||
|
"use_system_path_prompts": false,
|
||||||
|
"base_keymap": "VSCode",
|
||||||
"ui_font_size": 16,
|
"ui_font_size": 16,
|
||||||
"buffer_font_size": 15,
|
"buffer_font_size": 15,
|
||||||
|
"auto_signature_help": false,
|
||||||
"theme": {
|
"theme": {
|
||||||
"mode": "dark",
|
"mode": "light",
|
||||||
"light": "One Light",
|
"light": "One Light",
|
||||||
"dark": "Gruvbox Dark Hard"
|
"dark": "Min Dark (Blurred)"
|
||||||
|
},
|
||||||
|
"terminal": {
|
||||||
|
"font_family": "ZedMono Nerd Font"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
# Font Configuration for Chezmoi
|
|
||||||
# This file defines which fonts to install and configure.
|
|
||||||
# Fonts are sourced from Google Fonts automatically.
|
|
||||||
#
|
|
||||||
# To add a font: Just type its name - fuzzy matching will help if you misspell.
|
|
||||||
# To swap fonts: Change the primary, run `chezmoi apply`, done!
|
|
||||||
#
|
|
||||||
# Run `install-fonts.ts` manually to see available fonts or troubleshoot.
|
|
||||||
|
|
||||||
[ui]
|
|
||||||
# Sans-serif fonts for user interface elements
|
|
||||||
primary = "Inter"
|
|
||||||
fallback = "Noto Sans"
|
|
||||||
|
|
||||||
[serif]
|
|
||||||
# Serif fonts for documents and reading
|
|
||||||
primary = "Source Serif 4"
|
|
||||||
fallback = "Noto Serif"
|
|
||||||
|
|
||||||
[mono]
|
|
||||||
# Monospace fonts for code and terminals
|
|
||||||
primary = "Geist Mono"
|
|
||||||
fallback = "JetBrains Mono"
|
|
||||||
|
|
||||||
[emoji]
|
|
||||||
# Emoji font for unicode emoji support
|
|
||||||
primary = "Noto Color Emoji"
|
|
||||||
|
|
||||||
# Optional: Uncomment to install accessibility-focused fonts
|
|
||||||
# [accessibility]
|
|
||||||
# primary = "Atkinson Hyperlegible"
|
|
||||||
@@ -49,11 +49,14 @@ interface FontDetails extends GoogleFont {
|
|||||||
variants: FontVariant[];
|
variants: FontVariant[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface FontCategoryConfig {
|
||||||
|
primary: string;
|
||||||
|
fallback?: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface FontConfig {
|
interface FontConfig {
|
||||||
[category: string]: {
|
categories: Record<string, FontCategoryConfig>;
|
||||||
primary: string;
|
extras: string[];
|
||||||
fallback?: string;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FuseResult<T> {
|
interface FuseResult<T> {
|
||||||
@@ -67,7 +70,8 @@ interface FuseResult<T> {
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
const FONTS_DIR = join(homedir(), ".local", "share", "fonts");
|
const FONTS_DIR = join(homedir(), ".local", "share", "fonts");
|
||||||
const CONFIG_PATH = join(homedir(), ".config", "fontconfig", "fonts.toml");
|
// Meta-config location: chezmoi source dir, not deployed to filesystem
|
||||||
|
const CONFIG_PATH = join(homedir(), ".local", "share", "chezmoi", "meta", "fonts.toml");
|
||||||
const API_BASE = "https://gwfh.mranftl.com/api";
|
const API_BASE = "https://gwfh.mranftl.com/api";
|
||||||
const CACHE_FILE = join(homedir(), ".cache", "font-catalog.json");
|
const CACHE_FILE = join(homedir(), ".cache", "font-catalog.json");
|
||||||
const CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
const CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
||||||
@@ -81,6 +85,10 @@ interface GitHubFontConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const GITHUB_FONTS: Record<string, GitHubFontConfig> = {
|
const GITHUB_FONTS: Record<string, GitHubFontConfig> = {
|
||||||
|
"ZedMono NF": {
|
||||||
|
repo: "ryanoasis/nerd-fonts",
|
||||||
|
assetPattern: /^ZedMono\.zip$/,
|
||||||
|
},
|
||||||
Iosevka: {
|
Iosevka: {
|
||||||
repo: "be5invis/Iosevka",
|
repo: "be5invis/Iosevka",
|
||||||
assetPattern: /^PkgTTF-Iosevka-[\d.]+\.zip$/,
|
assetPattern: /^PkgTTF-Iosevka-[\d.]+\.zip$/,
|
||||||
@@ -517,7 +525,7 @@ async function loadConfig(): Promise<FontConfig> {
|
|||||||
const content = await Bun.file(CONFIG_PATH).text();
|
const content = await Bun.file(CONFIG_PATH).text();
|
||||||
|
|
||||||
// Simple TOML parser for our specific format
|
// Simple TOML parser for our specific format
|
||||||
const config: FontConfig = {};
|
const config: FontConfig = { categories: {}, extras: [] };
|
||||||
let currentSection = "";
|
let currentSection = "";
|
||||||
|
|
||||||
for (const line of content.split("\n")) {
|
for (const line of content.split("\n")) {
|
||||||
@@ -530,16 +538,33 @@ async function loadConfig(): Promise<FontConfig> {
|
|||||||
const sectionMatch = trimmed.match(/^\[(\w+)\]$/);
|
const sectionMatch = trimmed.match(/^\[(\w+)\]$/);
|
||||||
if (sectionMatch) {
|
if (sectionMatch) {
|
||||||
currentSection = sectionMatch[1];
|
currentSection = sectionMatch[1];
|
||||||
config[currentSection] = { primary: "" };
|
if (currentSection !== "extras") {
|
||||||
|
config.categories[currentSection] = { primary: "" };
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Key-value pair
|
// Handle [extras] section - array of fonts
|
||||||
|
if (currentSection === "extras") {
|
||||||
|
// Match: fonts = ["Font1", "Font2"]
|
||||||
|
const arrayMatch = trimmed.match(/^fonts\s*=\s*\[(.*)\]$/);
|
||||||
|
if (arrayMatch) {
|
||||||
|
const arrayContent = arrayMatch[1];
|
||||||
|
// Parse quoted strings from array
|
||||||
|
const fontMatches = arrayContent.matchAll(/"([^"]+)"/g);
|
||||||
|
for (const match of fontMatches) {
|
||||||
|
config.extras.push(match[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key-value pair for category sections
|
||||||
const kvMatch = trimmed.match(/^(\w+)\s*=\s*"([^"]+)"$/);
|
const kvMatch = trimmed.match(/^(\w+)\s*=\s*"([^"]+)"$/);
|
||||||
if (kvMatch && currentSection) {
|
if (kvMatch && currentSection && currentSection !== "extras") {
|
||||||
const [, key, value] = kvMatch;
|
const [, key, value] = kvMatch;
|
||||||
if (key === "primary" || key === "fallback") {
|
if (key === "primary" || key === "fallback") {
|
||||||
config[currentSection][key] = value;
|
config.categories[currentSection][key] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -630,13 +655,16 @@ async function installFromConfig(): Promise<void> {
|
|||||||
mkdirSync(FONTS_DIR, { recursive: true });
|
mkdirSync(FONTS_DIR, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect all fonts to install
|
// Collect all fonts to install from categories
|
||||||
const fontsToInstall: string[] = [];
|
const fontsToInstall: string[] = [];
|
||||||
for (const category of Object.values(config)) {
|
for (const category of Object.values(config.categories)) {
|
||||||
if (category.primary) fontsToInstall.push(category.primary);
|
if (category.primary) fontsToInstall.push(category.primary);
|
||||||
if (category.fallback) fontsToInstall.push(category.fallback);
|
if (category.fallback) fontsToInstall.push(category.fallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add extras
|
||||||
|
fontsToInstall.push(...config.extras);
|
||||||
|
|
||||||
// Remove duplicates
|
// Remove duplicates
|
||||||
const uniqueFonts = [...new Set(fontsToInstall)];
|
const uniqueFonts = [...new Set(fontsToInstall)];
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
{{ if eq .chezmoi.os "linux" -}}
|
{{ if eq .chezmoi.os "linux" -}}
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Font Installer Hook
|
# Font Installer Hook
|
||||||
# Runs automatically when fonts.toml changes
|
# Runs automatically when meta/fonts.toml changes
|
||||||
#
|
#
|
||||||
# fonts.toml hash: {{ include "dot_config/fontconfig/fonts.toml" | sha256sum }}
|
# fonts.toml hash: {{ include "../meta/fonts.toml" | sha256sum }}
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# Font Installation Meta-Config
|
||||||
|
#
|
||||||
|
# This file drives the install-fonts.ts script.
|
||||||
|
# It is NOT deployed to the filesystem.
|
||||||
|
#
|
||||||
|
# Fonts are sourced from:
|
||||||
|
# - Google Fonts (via google-webfonts-helper API)
|
||||||
|
# - GitHub releases (for fonts like Iosevka, ZedMono NF)
|
||||||
|
#
|
||||||
|
# Run `install-fonts.ts --help` for manual usage.
|
||||||
|
# Run `install-fonts.ts --search <query>` to find fonts.
|
||||||
|
|
||||||
|
[ui]
|
||||||
|
# Sans-serif fonts for user interface elements
|
||||||
|
primary = "Inter"
|
||||||
|
fallback = "Noto Sans"
|
||||||
|
|
||||||
|
[serif]
|
||||||
|
# Serif fonts for documents and reading
|
||||||
|
primary = "Source Serif 4"
|
||||||
|
fallback = "Noto Serif"
|
||||||
|
|
||||||
|
[mono]
|
||||||
|
# Monospace fonts for code and terminals
|
||||||
|
primary = "Geist Mono"
|
||||||
|
fallback = "JetBrains Mono"
|
||||||
|
|
||||||
|
[emoji]
|
||||||
|
# Emoji font for unicode emoji support
|
||||||
|
primary = "Noto Color Emoji"
|
||||||
|
|
||||||
|
[extras]
|
||||||
|
# Additional fonts to install without category assignment
|
||||||
|
# Useful for fonts needed by specific applications
|
||||||
|
fonts = ["ZedMono NF"]
|
||||||
Reference in New Issue
Block a user