Skip to content

Commit 756648c

Browse files
Merge pull request #188 from StrangeRanger/dev
2 parents 3798b23 + fcadea8 commit 756648c

File tree

9 files changed

+212
-246
lines changed

9 files changed

+212
-246
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ The documentation includes:
4242

4343
To build and preview the documentation locally, you'll need:
4444

45-
- **Python**: Version 3.9 or higher
45+
- **Python**: Version 3.10 or higher
4646
- **[uv](https://github.com/astral-sh/uv#installation)**: For dependency management
4747

4848
### Download and Setup

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "custom-unix-terminal"
33
version = "2"
44
description = "A project that uses mkdocs to host documentation for customizations of my Unix terminal."
55
readme = "README.md"
6-
requires-python = ">=3.9"
6+
requires-python = ">=3.10"
77
dependencies = [
88
"mkdocs-git-revision-date-localized-plugin==1.4.7",
99
"mkdocs-material==9.6.16",

update_repo.py

Lines changed: 84 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,34 @@
11
#!/usr/bin/env python3
2-
#
3-
# Automates the process of updating zsh and neovim configuration files in the 'includes'
4-
# directory. This is done by reading my 'dotfiles' in the 'submodules/dotfiles'
5-
# submodule and applying the necessary changes to the 'includes' directory.
6-
#
7-
# NOTE:
8-
# - This script does not require the initialization of a virtual environment. The
9-
# Pipfiles are only required when deploying the MkDocs site.
10-
# - This script has some hard-coded values that may make it fragile in some cases.
11-
# I've attempted to do my best an indicate where these values are, but it's always a
12-
# good idea to keep an eye on this script when making changes to the configuration.
13-
#
14-
########################################################################################
15-
# [ Imports ]###########################################################################
2+
"""Configuration file update automation script.
3+
4+
This script automates the process of updating Neovim and zsh configuration files in the
5+
`includes` directory by extracting relevant sections from dotfiles stored in the
6+
`submodules/dotfiles` submodule.
7+
8+
The script handles two main types of configuration updates:
9+
- Neovim configuration files with selective content extraction
10+
- Zsh configuration files with snippet-based processing
11+
12+
Notes:
13+
- This script is, unfortunately, fragile by nature. Changes to upstream marker text
14+
or chezmoi template layout can silently break extractions. When this occurs,
15+
adjust the markers in the constants module or update the `chezmoi_edge_case`
16+
function as needed.
17+
- No external dependencies are required; a virtual environment is not necessary.
18+
19+
Example:
20+
Run the script directly to update all configuration files:
1621
22+
```bash
23+
$ python3 update_repo.py
24+
```
25+
"""
1726

18-
from utils.file_utils import read_file, write_file
27+
# [ Imports ]###########################################################################
28+
29+
from utils.file_utils import read_file, read_lines, write_file
1930
from utils.constants import (
20-
CHEZMOI_STATEMENTS,
31+
CHEZMOI_DELIMITERS,
2132
NEOVIM_CONFIG_PATHS,
2233
ZSH_CONFIG_PATHS,
2334
NEOVIM_MARKERS,
@@ -31,15 +42,26 @@
3142
# [ Functions ]#########################################################################
3243

3344

34-
def neovim_config():
35-
"""Updates the neovim configuration files."""
36-
for operation, paths in NEOVIM_CONFIG_PATHS.items():
37-
data: list[str] | str = read_file(
38-
paths["from"], read_lines=(operation == "init_vim_no_plug")
39-
)
45+
def neovim_config() -> None:
46+
"""Process and write Neovim config file variants to the `includes` directory.
47+
48+
Handles two types of Neovim config processing:
49+
1. `init_vim_no_plug`: Extracts content between designated section markers,
50+
excluding plugin-related configurations.
51+
2. Other variants: Copies the entire source file without modification.
52+
53+
The function iterates through all Neovim config paths defined in
54+
`NEOVIM_CONFIG_PATHS` and processes each according to its operation type.
4055
56+
Note:
57+
The `init_vim_no_plug` operation relies on `NEOVIM_MARKERS` to identify
58+
content boundaries. The end marker line itself is excluded from output.
59+
"""
60+
for operation, paths in NEOVIM_CONFIG_PATHS.items():
4161
if operation == "init_vim_no_plug":
62+
data: list[str] = read_lines(paths["from"])
4263
filtered_data: list[str] = []
64+
4365
for current_line in data:
4466
if NEOVIM_MARKERS.start_marker in current_line:
4567
NEOVIM_MARKERS.is_within_section = True
@@ -53,22 +75,30 @@ def neovim_config():
5375
filtered_data.append(current_line)
5476
write_file(paths["to"], "".join(filtered_data))
5577
else:
78+
data: str = read_file(paths["from"])
5679
write_file(paths["to"], data)
5780

5881

59-
def chezmoi_edge_case(current_line, data, line_number):
60-
"""Handles edge cases when chezmoi statements are encountered. These are hard coded
61-
strings that need to be processed in a very specific way. This means that changes to
62-
the configuration files can easily break or negate the functionality of this
63-
method. Keep a close eye when making changes to the configuration files.
82+
def chezmoi_edge_case(current_line: str, data: list[str], line_number: int) -> int:
83+
"""Calculate the number of lines to skip for chezmoi template actions.
84+
85+
Handles special cases in chezmoi template processing by analyzing the current line
86+
and subsequent lines to determine how many lines should be skipped during zsh
87+
configuration processing.
88+
89+
Note:
90+
This function's logic is tightly coupled to the current structure of the
91+
dotfiles. Changes to the upstream chezmoi template layout may require updates
92+
to the pattern matching logic.
6493
6594
Args:
66-
current_line (str): The line to process.
67-
data (list[str]): The data from the file.
68-
line_number (int): The current line number.
95+
current_line: The current line being processed, which contains a chezmoi
96+
template delimiter.
97+
data: Complete list of lines from the source zsh configuration file.
98+
line_number: Zero-based index of the current line within the data list.
6999
70100
Returns:
71-
int: The number of lines to skip.
101+
Number of lines to skip (minimum 1).
72102
"""
73103
if "data.isGUIEnvironment" in current_line:
74104
if (
@@ -85,24 +115,37 @@ def chezmoi_edge_case(current_line, data, line_number):
85115
return 1
86116

87117

88-
def zsh_config():
89-
"""Updates the zsh configuration files."""
118+
def zsh_config() -> None:
119+
"""Process and write zsh config file variants and snippets to the `includes`
120+
directory.
121+
122+
Handles two types of processing:
123+
1. Full file operations: Copies entire source file, filtering chezmoi template
124+
actions.
125+
2. Snippet operations: Extracts specific sections and wraps with MkDocs section
126+
markers.
127+
128+
For snippet operations, extracts alias and `LS_COLORS` sections based on predefined
129+
markers and optionally appends hard-coded content.
130+
131+
Note:
132+
Includes debug output for CI/CD troubleshooting.
133+
"""
90134
for file_operation, file_paths in ZSH_CONFIG_PATHS.items():
91-
data: list[str] | str = read_file(file_paths["from"], read_lines=True)
135+
data: list[str] = read_lines(file_paths["from"])
92136
output_data: list[str] = []
93137
line_number = 0
94138

95139
while line_number < len(data):
96140
current_line = data[line_number]
97141

98-
## DEBUG: The below lines help with debugging, especially when running in a
99-
## CI/CD environment.
142+
## DEBUG: The below lines help with debugging...
100143
print(f"Processing line {line_number + 1} of {file_paths['from']}")
101144
print(f"Line: {current_line}")
102145

103-
if any(marker in current_line for marker in CHEZMOI_STATEMENTS):
104-
skip_line = chezmoi_edge_case(current_line, data, line_number)
105-
line_number += skip_line
146+
if any(marker in current_line for marker in CHEZMOI_DELIMITERS):
147+
skip_line_count = chezmoi_edge_case(current_line, data, line_number)
148+
line_number += skip_line_count
106149
continue
107150

108151
if not file_operation.endswith("snippet"):
@@ -143,8 +186,8 @@ def zsh_config():
143186
write_file(file_paths["to"], "".join(output_data))
144187

145188

146-
def main():
147-
"""Main function."""
189+
def main() -> None:
190+
"""Execute all configuration file update routines."""
148191
neovim_config()
149192
zsh_config()
150193

utils/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
__version__ = "1.0.5"
1+
__version__ = "1.1.0"
22
__author__ = "Hunter T. (StrangeRanger)"
33
__license__ = "MIT"

utils/constants.py

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,26 @@
1+
"""Configuration constants for update_repo.py.
2+
3+
This module defines all the constants used by the update_repo.py script for processing
4+
configuration files from the dotfiles submodule. It includes:
5+
6+
- File path mappings for Neovim and zsh configuration variants
7+
- Section markers for extracting specific content from configuration files
8+
- Template delimiters for identifying chezmoi template blocks
9+
- MkDocs content markers for generated documentation snippets
10+
11+
The constants are organized by their functional purpose and are designed to be easily
12+
modified when upstream dotfile structures change.
13+
"""
14+
115
from pathlib import Path
2-
from utils.marker_config import Markers
16+
from typing import Final
17+
from utils.marker_config import SectionMarkers
318

4-
CHEZMOI_STATEMENTS = ["{{ ", "{{- "]
19+
# Chezmoi template delimiters to used to identify template blocks from my dotfiles.
20+
CHEZMOI_DELIMITERS: Final[list[str]] = ["{{ ", "{{- "]
521

6-
NEOVIM_CONFIG_PATHS = {
22+
# Maps Neovim config variants to their source and destination paths.
23+
NEOVIM_CONFIG_PATHS: Final[dict[str, dict[str, Path]]] = {
724
"init_lua": {
825
"from": Path("submodules/dotfiles/private_dot_config/nvim/second_init.lua"),
926
"to": Path("includes/neovim-init-files/neovim-init-lua.lua"),
@@ -18,39 +35,41 @@
1835
},
1936
}
2037

21-
ZSH_CONFIG_PATHS = {
38+
# Maps zsh config variants to their source and destination paths.
39+
ZSH_CONFIG_PATHS: Final[dict[str, dict[str, Path]]] = {
2240
"zshrc_linux": {
23-
"from": Path("submodules/dotfiles/.zshrc_linux.tmpl"),
41+
"from": Path("submodules/dotfiles/.chezmoitemplates/.zshrc_linux.tmpl"),
2442
"to": Path("includes/zshrc-files/zshrc-linux.zsh"),
2543
},
2644
"zshrc_linux_snippet": {
27-
"from": Path("submodules/dotfiles/.zshrc_linux.tmpl"),
45+
"from": Path("submodules/dotfiles/.chezmoitemplates/.zshrc_linux.tmpl"),
2846
"to": Path("includes/zshrc-files/zshrc-linux-snippet.zsh"),
2947
},
3048
"zshrc_macos": {
31-
"from": Path("submodules/dotfiles/.zshrc_darwin.tmpl"),
49+
"from": Path("submodules/dotfiles/.chezmoitemplates/.zshrc_darwin.tmpl"),
3250
"to": Path("includes/zshrc-files/zshrc-macos.zsh"),
3351
},
3452
"zshrc_macos_snippet": {
35-
"from": Path("submodules/dotfiles/.zshrc_darwin.tmpl"),
53+
"from": Path("submodules/dotfiles/.chezmoitemplates/.zshrc_darwin.tmpl"),
3654
"to": Path("includes/zshrc-files/zshrc-macos-snippet.zsh"),
3755
},
3856
}
3957

40-
NEOVIM_MARKERS = Markers(
58+
# Section markers for the general configuration section in ``init.vim`` for snippet
59+
# extraction.
60+
NEOVIM_MARKERS: Final[SectionMarkers] = SectionMarkers(
4161
start_marker='""""[ General Configurations ]',
4262
end_marker='""""[ vim-plug Plugin Configurations ]',
4363
)
4464

45-
ZSH_ALIAS_MARKERS = Markers(
65+
# Section markers for the aliases portion of ``.zshrc`` for snippet extraction.
66+
ZSH_ALIAS_MARKERS: Final[SectionMarkers] = SectionMarkers(
4667
start_marker="####[ Aliases ]",
4768
end_marker="####[ Environmental Variables ]",
4869
)
4970

50-
# NOTE: This is an "estimated" value, that relies on the position of comments and other
51-
# commands that may not always be present. Compared to the previous markers, this one
52-
# is more likely to be wrong, if the `.zshrc` related files are modified.
53-
ZSH_LS_COLORS_MARKERS = Markers(
71+
# Section markers for extracting/injecting the LS_COLORS related lines.
72+
ZSH_LS_COLORS_MARKERS: Final[SectionMarkers] = SectionMarkers(
5473
start_marker="# Modifies the colors",
5574
end_marker="## Set default",
5675
hard_coded_inclusion=(
@@ -59,12 +78,14 @@
5978
),
6079
)
6180

62-
MKDOCS_USER_CONFIG_MARKERS = Markers(
81+
# Section markers used in generated MkDocs content for the user configuration snippet.
82+
MKDOCS_USER_CONFIG_MARKERS: Final[SectionMarkers] = SectionMarkers(
6383
start_marker="# --8<-- [start:user_config]\n",
6484
end_marker="# --8<-- [end:user_config]\n",
6585
)
6686

67-
MKDOCS_LS_COLORS_MARKERS = Markers(
87+
# Section markers used in generated MkDocs content for the LS_COLORS snippet.
88+
MKDOCS_LS_COLORS_MARKERS: Final[SectionMarkers] = SectionMarkers(
6889
start_marker="# --8<-- [start:ls_colors]\n",
6990
end_marker="# --8<-- [end:ls_colors]\n",
7091
)

utils/file_utils.py

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,49 @@
1+
"""File I/O utilities for the repository update automation system.
2+
3+
This module provides simple, consistent wrappers around pathlib operations for
4+
reading and writing text files. These utilities are used throughout the project
5+
to handle configuration file processing with proper encoding support.
6+
7+
All functions accept both string paths and pathlib.Path objects for flexibility.
8+
"""
9+
110
from pathlib import Path
11+
from typing import List
12+
13+
14+
def read_file(file_path: str | Path, encoding: str = "utf-8") -> str:
15+
"""Return the full text contents of ``file_path``.
16+
17+
Args:
18+
file_path: Filesystem path or path-like string.
19+
encoding: Text encoding used to decode the file (default UTF-8).
20+
21+
Returns:
22+
The decoded file contents as a single string.
23+
"""
24+
return Path(file_path).read_text(encoding=encoding)
225

326

4-
def read_file(file_path, read_lines=False):
5-
"""Reads the file and returns the data.
27+
def read_lines(file_path: str | Path, encoding: str = "utf-8") -> List[str]:
28+
"""Return file contents as a list of lines, preserving newline characters.
629
730
Args:
8-
file_path (Path): The path to the file.
9-
read_lines (bool, optional): Whether to read the file line by line. Defaults to
10-
False.
31+
file_path: Filesystem path or path-like string.
32+
encoding: Text encoding used to decode the file (default UTF-8).
1133
1234
Returns:
13-
list[str] | str: The data from the file.
35+
List of lines including their terminating newlines where present.
1436
"""
15-
with open(file_path, "r") as file:
16-
return file.readlines() if read_lines else file.read()
37+
text = Path(file_path).read_text(encoding=encoding)
38+
return text.splitlines(keepends=True)
1739

1840

19-
def write_file(file_path, data):
20-
"""Writes the data to the file.
41+
def write_file(file_path: str | Path, data: str, encoding: str = "utf-8") -> None:
42+
"""Write text to ``file_path``, creating the file if it doesn't exist.
2143
2244
Args:
23-
file_path (Path): The path to the file.
24-
data (str): The data to write.
45+
file_path: Destination path; parent directories must already exist.
46+
data: Text to write.
47+
encoding: Text encoding used to encode the file (default UTF-8).
2548
"""
26-
with open(file_path, "w") as file:
27-
file.write(data)
49+
Path(file_path).write_text(data, encoding=encoding)

0 commit comments

Comments
 (0)