theamify-cli 1.0.1

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,151 @@
1
+ #!/usr/bin/env bash
2
+ # -----------------------------------------------------------------------------
3
+ # lib/colors.sh - ANSI Colors, Print Functions & UI Components
4
+ # -----------------------------------------------------------------------------
5
+
6
+ RESET=$'\033[0m'; BOLD=$'\033[1m'; DIM=$'\033[2m'
7
+ ITALIC=$'\033[3m'; UNDERLINE=$'\033[4m'
8
+
9
+ BLACK=$'\033[0;30m'; RED=$'\033[0;31m'; GREEN=$'\033[0;32m'
10
+ YELLOW=$'\033[0;33m';BLUE=$'\033[0;34m'; MAGENTA=$'\033[0;35m'
11
+ CYAN=$'\033[0;36m'; WHITE=$'\033[0;37m'
12
+
13
+ BOLD_BLACK=$'\033[1;30m'; BOLD_RED=$'\033[1;31m'; BOLD_GREEN=$'\033[1;32m'
14
+ BOLD_YELLOW=$'\033[1;33m'; BOLD_BLUE=$'\033[1;34m'; BOLD_MAGENTA=$'\033[1;35m'
15
+ BOLD_CYAN=$'\033[1;36m'; BOLD_WHITE=$'\033[1;37m'
16
+
17
+ BRIGHT_RED=$'\033[0;91m'; BRIGHT_GREEN=$'\033[0;92m'; BRIGHT_YELLOW=$'\033[0;93m'
18
+ BRIGHT_BLUE=$'\033[0;94m'; BRIGHT_MAGENTA=$'\033[0;95m';BRIGHT_CYAN=$'\033[0;96m'
19
+ BRIGHT_WHITE=$'\033[0;97m'
20
+
21
+ BG_BLACK=$'\033[40m'; BG_RED=$'\033[41m'; BG_GREEN=$'\033[42m'
22
+ BG_YELLOW=$'\033[43m'; BG_BLUE=$'\033[44m'; BG_MAGENTA=$'\033[45m'
23
+ BG_CYAN=$'\033[46m'; BG_WHITE=$'\033[47m'
24
+
25
+ C_TITLE="${BOLD_CYAN}"
26
+ C_SUBTITLE="${BOLD_MAGENTA}"
27
+ C_SECTION="${BOLD_YELLOW}"
28
+ C_SUCCESS="${BOLD_GREEN}"
29
+ C_ERROR="${BOLD_RED}"
30
+ C_WARNING="${YELLOW}"
31
+ C_INFO="${CYAN}"
32
+ C_DIM="${DIM}${WHITE}"
33
+ C_HIGHLIGHT="${BOLD_WHITE}"
34
+ C_PROMPT="${BOLD_BLUE}"
35
+ C_ACTIVE="${BOLD_GREEN}"
36
+ C_INACTIVE="${DIM}${WHITE}"
37
+ C_STEP="${BRIGHT_CYAN}"
38
+ C_CMD="${DIM}${GREEN}"
39
+ C_LINK="${UNDERLINE}${BRIGHT_BLUE}"
40
+ C_TAG="${BRIGHT_MAGENTA}"
41
+ C_NUM="${BOLD_YELLOW}"
42
+ C_BORDER="${BOLD_BLUE}"
43
+
44
+ if [[ -n "${NO_COLOR:-}" ]] || [[ ! -t 1 ]]; then
45
+ RESET="" BOLD="" DIM="" ITALIC="" UNDERLINE=""
46
+ BLACK="" RED="" GREEN="" YELLOW="" BLUE="" MAGENTA="" CYAN="" WHITE=""
47
+ BOLD_BLACK="" BOLD_RED="" BOLD_GREEN="" BOLD_YELLOW="" BOLD_BLUE=""
48
+ BOLD_MAGENTA="" BOLD_CYAN="" BOLD_WHITE=""
49
+ BRIGHT_RED="" BRIGHT_GREEN="" BRIGHT_YELLOW="" BRIGHT_BLUE=""
50
+ BRIGHT_MAGENTA="" BRIGHT_CYAN="" BRIGHT_WHITE=""
51
+ BG_BLACK="" BG_RED="" BG_GREEN="" BG_YELLOW="" BG_BLUE=""
52
+ BG_MAGENTA="" BG_CYAN="" BG_WHITE=""
53
+ C_TITLE="" C_SUBTITLE="" C_SECTION="" C_SUCCESS="" C_ERROR=""
54
+ C_WARNING="" C_INFO="" C_DIM="" C_HIGHLIGHT="" C_PROMPT=""
55
+ C_ACTIVE="" C_INACTIVE="" C_STEP="" C_CMD="" C_LINK=""
56
+ C_TAG="" C_NUM="" C_BORDER=""
57
+ fi
58
+
59
+ print_title() { echo -e "${C_TITLE}${*}${RESET}"; }
60
+ print_section() { echo -e "\n${C_SECTION}== ${*}${RESET}"; }
61
+ print_success() { echo -e "${C_SUCCESS}[OK]${RESET} ${*}"; }
62
+ print_error() { echo -e "${C_ERROR}[ERR]${RESET} ${*}" >&2; }
63
+ print_warning() { echo -e "${C_WARNING}[WARN]${RESET} ${*}"; }
64
+ print_info() { echo -e "${C_INFO}[INFO]${RESET} ${*}"; }
65
+ print_step() { echo -e "${C_STEP}->${RESET} ${*}"; }
66
+ print_dim() { echo -e "${C_DIM}${*}${RESET}"; }
67
+ print_cmd() { echo -e "${C_CMD} \$ ${*}${RESET}"; }
68
+ print_link() { echo -e "${C_LINK}${*}${RESET}"; }
69
+ print_bold() { echo -e "${BOLD_WHITE}${*}${RESET}"; }
70
+
71
+ _term_width() {
72
+ local w; w="$(tput cols 2>/dev/null || echo 80)"
73
+ (( w > 100 )) && w=100
74
+ echo "${w}"
75
+ }
76
+
77
+ print_rule() {
78
+ local char="${1:--}"
79
+ local w; w="$(_term_width)"
80
+ printf '%s' "${C_BORDER}"
81
+ printf '%*s' "${w}" '' | tr ' ' "${char}"
82
+ printf '%s\n' "${RESET}"
83
+ }
84
+
85
+ print_box_title() {
86
+ local title="${1}"
87
+ local w; w="$(_term_width)"
88
+ local inner=$(( w - 4 ))
89
+ local tlen="${#title}"
90
+ local lpad=$(( (inner - tlen) / 2 ))
91
+ (( lpad < 0 )) && lpad=0
92
+ local rpad=$(( inner - tlen - lpad ))
93
+ (( rpad < 0 )) && rpad=0
94
+
95
+ print_rule "-"
96
+ printf '%s|%s %s%*s%s%*s %s|%s\n' \
97
+ "${C_BORDER}" "${RESET}" "${C_TITLE}" \
98
+ "${lpad}" "" "${title}" "${rpad}" "" "${C_BORDER}" "${RESET}"
99
+ print_rule "-"
100
+ }
101
+
102
+ print_kv() {
103
+ local key="${1}" val="${2}"
104
+ printf " ${BOLD_WHITE}%-20s${RESET} %b\n" "${key}:" "${val}"
105
+ }
106
+
107
+ print_status() {
108
+ case "${1:-}" in
109
+ active) printf '%s[ACTIVE]%s' "${C_ACTIVE}" "${RESET}" ;;
110
+ cached) printf '%s[CACHED]%s' "${C_INFO}" "${RESET}" ;;
111
+ remote) printf '%s[REMOTE]%s' "${C_DIM}" "${RESET}" ;;
112
+ error) printf '%s[ERROR]%s' "${C_ERROR}" "${RESET}" ;;
113
+ *) printf '%s[UNKNOWN]%s' "${C_DIM}" "${RESET}" ;;
114
+ esac
115
+ }
116
+
117
+ print_banner() {
118
+ print_rule "="
119
+ echo -e " ${C_TITLE}${BOLD}theamify${RESET} ${C_DIM}v${VERSION:-1.0.0}${RESET} ${C_DIM}- GRUB Theme Manager${RESET}"
120
+ echo -e " ${C_DIM}by Don Artkins${RESET}"
121
+ print_rule "="
122
+ echo
123
+ }
124
+
125
+ SPINNER_FRAMES=('|' '/' '-' '\')
126
+ SPINNER_PID=""
127
+
128
+ spinner_start() {
129
+ local msg="${1:-Working}"
130
+ (
131
+ local i=0
132
+ while true; do
133
+ # >&2 is mandatory: get_repo() and other functions run this while
134
+ # their own stdout is being captured via $(...). Anything the
135
+ # spinner writes to fd1 lands inside that captured string.
136
+ printf '\r %s%s%s %s...' "${C_STEP}" "${SPINNER_FRAMES[$((i % 4))]}" "${RESET}" "${msg}" >&2
137
+ sleep 0.1
138
+ i=$(( i + 1 ))
139
+ done
140
+ ) &
141
+ SPINNER_PID=$!
142
+ disown "${SPINNER_PID}" 2>/dev/null || true
143
+ }
144
+
145
+ spinner_stop() {
146
+ if [[ -n "${SPINNER_PID:-}" ]]; then
147
+ kill "${SPINNER_PID}" 2>/dev/null || true
148
+ SPINNER_PID=""
149
+ printf '\r\033[K' >&2
150
+ fi
151
+ }
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env bash
2
+ # -----------------------------------------------------------------------------
3
+ # lib/grub.sh - GRUB Detection & Management
4
+ # -----------------------------------------------------------------------------
5
+
6
+ # -- Detect GRUB themes directory ---------------------------------------------
7
+ grub_detect_dir() {
8
+ local candidates=(
9
+ "/boot/grub/themes"
10
+ "/boot/grub2/themes"
11
+ "/usr/share/grub/themes"
12
+ )
13
+ for d in "${candidates[@]}"; do
14
+ [[ -d "${d}" ]] && echo "${d}" && return 0
15
+ done
16
+ echo "/boot/grub/themes" # default fallback - will be created on apply
17
+ }
18
+
19
+ # -- Detect GRUB update command ------------------------------------------------
20
+ grub_detect_update_cmd() {
21
+ if command -v update-grub &>/dev/null; then
22
+ echo "update-grub"
23
+ elif command -v grub-mkconfig &>/dev/null; then
24
+ echo "grub-mkconfig -o /boot/grub/grub.cfg"
25
+ elif command -v grub2-mkconfig &>/dev/null; then
26
+ if command -v zypper &>/dev/null; then
27
+ echo "grub2-mkconfig -o /boot/grub2/grub.cfg"
28
+ elif command -v dnf &>/dev/null; then
29
+ echo "grub2-mkconfig -o /boot/efi/EFI/fedora/grub.cfg"
30
+ else
31
+ echo "grub2-mkconfig -o /boot/grub2/grub.cfg"
32
+ fi
33
+ else
34
+ return 1
35
+ fi
36
+ }
37
+
38
+ # -- Get name of currently active GRUB theme ----------------------------------
39
+ grub_get_active_theme() {
40
+ local cfg="${GRUB_CFG:-/etc/default/grub}"
41
+ [[ ! -f "${cfg}" ]] && echo "" && return 0
42
+
43
+ local line
44
+ line="$(grep '^GRUB_THEME=' "${cfg}" 2>/dev/null | tail -1 || true)"
45
+ [[ -z "${line}" ]] && echo "" && return 0
46
+
47
+ local path
48
+ path="$(echo "${line}" | cut -d= -f2- | tr -d '"' | tr -d "'")"
49
+ # The theme folder is the parent dir of theme.txt
50
+ dirname "${path}" 2>/dev/null | xargs basename 2>/dev/null || echo ""
51
+ }
52
+
53
+ # -- Backup GRUB config --------------------------------------------------------
54
+ grub_backup() {
55
+ local cfg="${GRUB_CFG:-/etc/default/grub}"
56
+ local bak="${cfg}.bak.$(date +%Y%m%d_%H%M%S)"
57
+ cp -a "${cfg}" "${bak}"
58
+ print_dim " Backed up: ${bak}"
59
+ }
60
+
61
+ # ==============================================================================
62
+ # Apply theme to GRUB (4-stage process)
63
+ # ==============================================================================
64
+ grub_apply_theme() {
65
+ local theme_name="${1}"
66
+ local cached="${THEMES_CACHE_DIR}/${theme_name}"
67
+ local grub_dir
68
+ grub_dir="$(grub_detect_dir)"
69
+ local cfg="${GRUB_CFG:-/etc/default/grub}"
70
+
71
+ echo
72
+ print_box_title " Applying Theme: ${theme_name} "
73
+
74
+ # -- Stage 1 : Verify cached theme -----------------------------------------
75
+ print_section "Stage 1 of 4 - Verify"
76
+
77
+ if [[ ! -d "${cached}" ]]; then
78
+ print_error "Theme '${theme_name}' is not cached."
79
+ print_step "Run: theamify get ${theme_name}"
80
+ exit 1
81
+ fi
82
+
83
+ local theme_txt
84
+ theme_txt="$(find_theme_txt "${cached}")" || {
85
+ print_error "No theme.txt found in cache for '${theme_name}'."
86
+ print_warning "This theme may require its own install script."
87
+ print_step "Check the repo: theamify open ${theme_name}"
88
+ exit 1
89
+ }
90
+ print_success "theme.txt found: ${theme_txt}"
91
+
92
+ # -- Stage 2 : Install theme files -----------------------------------------
93
+ print_section "Stage 2 of 4 - Install Files"
94
+
95
+ local dest="${grub_dir}/${theme_name}"
96
+ mkdir -p "${grub_dir}"
97
+ [[ -d "${dest}" ]] && rm -rf "${dest}"
98
+
99
+ print_step "Copying to: ${dest}"
100
+ cp -r "${cached}" "${dest}"
101
+
102
+ local dest_txt
103
+ dest_txt="$(find_theme_txt "${dest}")" || {
104
+ print_error "theme.txt not found after copying. Something went wrong."
105
+ exit 1
106
+ }
107
+ print_success "Theme files installed."
108
+
109
+ # -- Stage 3 : Configure /etc/default/grub --------------------------------
110
+ print_section "Stage 3 of 4 - Configure GRUB"
111
+
112
+ grub_backup
113
+
114
+ # Remove any old conflicting options
115
+ sed -i '/^GRUB_THEME=/d' "${cfg}"
116
+ sed -i '/^GRUB_TERMINAL_OUTPUT=/d' "${cfg}"
117
+ sed -i '/^GRUB_TIMEOUT_STYLE=/d' "${cfg}"
118
+ sed -i '/^GRUB_GFXMODE=/d' "${cfg}"
119
+
120
+ # Set timeout style to show menu
121
+ echo 'GRUB_TIMEOUT_STYLE="menu"' >> "${cfg}"
122
+ echo 'GRUB_GFXMODE="auto"' >> "${cfg}"
123
+ # Set timeout if not already defined
124
+ grep -q '^GRUB_TIMEOUT=' "${cfg}" || echo 'GRUB_TIMEOUT="30"' >> "${cfg}"
125
+ # Point GRUB at theme
126
+ echo "GRUB_THEME=\"${dest_txt}\"" >> "${cfg}"
127
+
128
+ print_success "GRUB config updated."
129
+ print_kv "GRUB_THEME" "${dest_txt}"
130
+
131
+ # -- Stage 4 : Rebuild GRUB config ----------------------------------------
132
+ print_section "Stage 4 of 4 - Rebuild GRUB"
133
+
134
+ local update_cmd
135
+ update_cmd="$(grub_detect_update_cmd)" || {
136
+ print_error "No GRUB update command found."
137
+ print_step "Run manually: update-grub OR grub-mkconfig -o /boot/grub/grub.cfg"
138
+ exit 1
139
+ }
140
+
141
+ print_step "Running: ${update_cmd}"
142
+ if eval "${update_cmd}"; then
143
+ print_success "GRUB rebuilt successfully."
144
+ else
145
+ print_error "GRUB rebuild failed. Check your GRUB setup."
146
+ exit 1
147
+ fi
148
+
149
+ # -- Done ------------------------------------------------------------------
150
+ echo
151
+ print_rule "="
152
+ echo -e "\n ${C_SUCCESS}${BOLD}Theme '${theme_name}' is now active!${RESET}"
153
+ echo
154
+ print_step "Next steps:"
155
+ echo -e " ${C_DIM}1.${RESET} Reboot -> ${C_CMD}sudo reboot${RESET}"
156
+ echo -e " ${C_DIM}2.${RESET} Switch -> ${C_CMD}sudo theamify use <name>${RESET}"
157
+ echo -e " ${C_DIM}3.${RESET} Browse -> ${C_CMD}theamify list${RESET}"
158
+ echo
159
+ }
160
+
161
+ # -- Remove theme from GRUB (does NOT remove cache) ---------------------------
162
+ grub_remove_theme() {
163
+ local theme_name="${1}"
164
+ local grub_dir
165
+ grub_dir="$(grub_detect_dir)"
166
+ local dest="${grub_dir}/${theme_name}"
167
+ local cfg="${GRUB_CFG:-/etc/default/grub}"
168
+
169
+ [[ -d "${dest}" ]] && rm -rf "${dest}" && \
170
+ print_success "Removed GRUB theme dir: ${dest}"
171
+
172
+ if grep -q "${theme_name}" "${cfg}" 2>/dev/null; then
173
+ sed -i "/GRUB_THEME=.*${theme_name}.*/d" "${cfg}"
174
+ print_success "Removed theme from GRUB config."
175
+ local update_cmd
176
+ update_cmd="$(grub_detect_update_cmd)" && eval "${update_cmd}" || \
177
+ print_warning "GRUB rebuild had issues. Run update-grub manually."
178
+ fi
179
+ }
@@ -0,0 +1,283 @@
1
+ #!/usr/bin/env bash
2
+ # -----------------------------------------------------------------------------
3
+ # lib/themes.sh - Theme Registry CRUD & Download Logic
4
+ # -----------------------------------------------------------------------------
5
+
6
+ parse_conf() {
7
+ grep -v '^[[:space:]]*$' "${THEMES_CONF}" 2>/dev/null \
8
+ | grep -v '^#' \
9
+ || true
10
+ }
11
+
12
+ theme_get_entry() {
13
+ local name="${1}"
14
+ local line
15
+ line="$(parse_conf | grep "^${name}|" | head -1 || true)"
16
+ [[ -z "${line}" ]] && return 1
17
+ echo "${line}"
18
+ }
19
+
20
+ theme_generate_subdir() {
21
+ local name="${1}" repo_path="${2}" gen_args="${3}"
22
+
23
+ local gen_script="${repo_path}/generate.sh"
24
+ if [[ ! -f "${gen_script}" ]]; then
25
+ print_error "Registry entry '${name}' uses 'generate:' but no"
26
+ print_error "generate.sh was found at: ${gen_script}"
27
+ return 1
28
+ fi
29
+
30
+ local build_dir="${REPO_CACHE_DIR}/.build/${name}"
31
+ rm -rf "${build_dir}"
32
+ mkdir -p "${build_dir}"
33
+
34
+ print_step "Running: generate.sh -d <build_dir> ${gen_args}" >&2
35
+ # shellcheck disable=SC2086
36
+ if ! bash "${gen_script}" -d "${build_dir}" ${gen_args} >&2; then
37
+ print_error "generate.sh failed for '${name}'."
38
+ print_step "Reproduce manually: cd ${repo_path} && ./generate.sh -d <dir> ${gen_args}" >&2
39
+ return 1
40
+ fi
41
+
42
+ local -a produced=()
43
+ while IFS= read -r -d '' d; do
44
+ produced+=("${d}")
45
+ done < <(find "${build_dir}" -mindepth 1 -maxdepth 1 -type d -print0)
46
+
47
+ if (( ${#produced[@]} == 0 )); then
48
+ print_error "generate.sh ran but produced no output directory in: ${build_dir}"
49
+ return 1
50
+ fi
51
+ if (( ${#produced[@]} > 1 )); then
52
+ print_warning "generate.sh produced ${#produced[@]} directories; using: $(basename "${produced[0]}")" >&2
53
+ fi
54
+
55
+ echo "${produced[0]}"
56
+ }
57
+
58
+ theme_download() {
59
+ local name="${1}"
60
+ local force="${2:-}"
61
+
62
+ local line
63
+ line="$(theme_get_entry "${name}")" || {
64
+ print_error "Theme '${name}' not found in registry."
65
+ print_info "Run 'theamify list' to see all themes."
66
+ return 1
67
+ }
68
+
69
+ IFS='|' read -r t_name t_url t_sub t_desc t_source t_tags <<< "${line}"
70
+ local dest="${THEMES_CACHE_DIR}/${t_name}"
71
+
72
+ if [[ -d "${dest}" && "${force}" != "--force" ]]; then
73
+ print_info "Theme '${t_name}' is already cached."
74
+ print_step "Re-download with: theamify update ${t_name}"
75
+ return 0
76
+ fi
77
+
78
+ print_section "Downloading: ${t_name}"
79
+ echo -e " ${C_DIM}${t_desc}${RESET}"
80
+ echo -e " ${C_LINK}${t_url}${RESET}"
81
+ echo
82
+
83
+ check_deps_required
84
+
85
+ print_step "[1/4] Fetching repository..."
86
+ local force_clone="false"
87
+ [[ "${force}" == "--force" ]] && force_clone="true"
88
+
89
+ local repo_path
90
+ repo_path="$(get_repo "${t_url}" "${force_clone}")" || return 1
91
+ print_success "Repository ready."
92
+
93
+ print_step "[2/4] Locating theme files..."
94
+
95
+ local src_path
96
+ if [[ "${t_sub}" == generate:* ]]; then
97
+ src_path="$(theme_generate_subdir "${t_name}" "${repo_path}" "${t_sub#generate:}")" || return 1
98
+ elif [[ "${t_sub}" == "." ]]; then
99
+ src_path="${repo_path}"
100
+ else
101
+ src_path="${repo_path}/${t_sub}"
102
+ if [[ ! -d "${src_path}" ]]; then
103
+ local found_dir
104
+ found_dir="$(find "${repo_path}" -maxdepth 4 -type d \
105
+ -iname "$(basename "${t_sub}")" 2>/dev/null | head -1 || true)"
106
+ if [[ -n "${found_dir}" ]]; then
107
+ print_warning "Subdir '${t_sub}' not found exactly; using: ${found_dir}"
108
+ src_path="${found_dir}"
109
+ else
110
+ print_error "Theme subdir '${t_sub}' not found in repo."
111
+ echo
112
+ print_dim " Repo contents:"
113
+ ls "${repo_path}" 2>/dev/null | head -20 | while read -r f; do
114
+ print_dim " ${f}"
115
+ done
116
+ return 1
117
+ fi
118
+ fi
119
+ fi
120
+ print_success "Source path: ${src_path}"
121
+
122
+ print_step "[3/4] Caching theme..."
123
+ [[ -d "${dest}" ]] && rm -rf "${dest}"
124
+ mkdir -p "${dest}"
125
+ cp -r "${src_path}/." "${dest}/"
126
+
127
+ rm -rf "${dest}/.git" "${dest}/.github" 2>/dev/null || true
128
+
129
+ local preview_img
130
+ preview_img="$(find "${dest}" -maxdepth 3 \
131
+ \( -name "*.png" -o -name "*.jpg" -o -name "*.jpeg" \) \
132
+ -not -path "*/.git/*" 2>/dev/null | sort | head -1 || true)"
133
+ if [[ -n "${preview_img}" ]]; then
134
+ mkdir -p "${dest}/.preview"
135
+ cp "${preview_img}" "${dest}/.preview/" 2>/dev/null || true
136
+ fi
137
+
138
+ print_success "Cached: ${dest}"
139
+
140
+ print_step "[4/4] Validating..."
141
+ local theme_txt
142
+ theme_txt="$(find_theme_txt "${dest}")" || {
143
+ print_warning "No theme.txt found in '${t_name}'."
144
+ print_dim " This usually means the repo has no static theme folder and"
145
+ print_dim " no compatible generate.sh - SUBDIR may need fixing, or the"
146
+ print_dim " repo needs a 'generate:<args>' entry (see CONTRIBUTING.md)."
147
+ print_dim " Check the repo README for instructions:"
148
+ print_dim " ${t_url}"
149
+ print_dim " Cache is saved at: ${dest}"
150
+ echo
151
+ return 1
152
+ }
153
+
154
+ print_success "Valid theme - theme.txt: ${theme_txt}"
155
+ echo
156
+ echo -e " ${C_DIM}Apply with:${RESET} ${C_CMD}sudo theamify use ${t_name}${RESET}"
157
+ echo -e " ${C_DIM}Preview at:${RESET} ${C_CMD}theamify info ${t_name}${RESET}"
158
+ echo
159
+ }
160
+
161
+ theme_remove_cache() {
162
+ local name="${1}"
163
+
164
+ theme_get_entry "${name}" &>/dev/null || {
165
+ print_error "Theme '${name}' not found in registry."
166
+ return 1
167
+ }
168
+
169
+ local dest="${THEMES_CACHE_DIR}/${name}"
170
+ if [[ ! -d "${dest}" ]]; then
171
+ print_info "Theme '${name}' is not currently cached."
172
+ return 0
173
+ fi
174
+
175
+ local active
176
+ active="$(grub_get_active_theme)"
177
+ if [[ "${name}" == "${active}" ]]; then
178
+ print_warning "Theme '${name}' is currently active in GRUB."
179
+ confirm "Remove cache anyway?" || { print_info "Cancelled."; return 0; }
180
+ else
181
+ confirm "Remove local cache for '${name}'?" || { print_info "Cancelled."; return 0; }
182
+ fi
183
+
184
+ rm -rf "${dest}"
185
+ print_success "Cache removed for '${name}'."
186
+ print_dim " Registry entry kept. Re-download: theamify get ${name}"
187
+ }
188
+
189
+ theme_add_to_registry() {
190
+ local url="${1}"
191
+
192
+ print_section "Add New Theme"
193
+ echo -e " ${C_DIM}URL: ${url}${RESET}"
194
+
195
+ if ! echo "${url}" | grep -qE '^https?://(github|gitlab)\.com/'; then
196
+ print_error "URL must be a GitHub or GitLab repository URL."
197
+ return 1
198
+ fi
199
+
200
+ local default_name
201
+ default_name="$(basename "${url%.git}")"
202
+
203
+ echo
204
+ echo -ne " ${C_PROMPT}Theme name${RESET} ${C_DIM}[${default_name}]${RESET}: "
205
+ read -r name
206
+ name="${name:-${default_name}}"
207
+
208
+ if parse_conf | grep -q "^${name}|"; then
209
+ print_error "A theme named '${name}' already exists."
210
+ return 1
211
+ fi
212
+
213
+ echo -ne " ${C_PROMPT}Subdir within repo${RESET} ${C_DIM}[.]${RESET} ${C_DIM}(or 'generate:<args>' if the repo builds via its own generate.sh)${RESET}: "
214
+ read -r subdir
215
+ subdir="${subdir:-.}"
216
+
217
+ echo -ne " ${C_PROMPT}Description${RESET}: "
218
+ read -r desc
219
+ desc="${desc:-Custom GRUB theme}"
220
+
221
+ echo -ne " ${C_PROMPT}Source page URL${RESET} ${C_DIM}[${url}]${RESET}: "
222
+ read -r source
223
+ source="${source:-${url}}"
224
+
225
+ echo -ne " ${C_PROMPT}Tags${RESET} ${C_DIM}(comma-separated)${RESET}: "
226
+ read -r tags
227
+ tags="${tags:-custom}"
228
+
229
+ local entry="${name}|${url}|${subdir}|${desc}|${source}|${tags}"
230
+
231
+ echo
232
+ print_section "Preview Entry"
233
+ echo -e " ${C_DIM}${entry}${RESET}"
234
+ echo
235
+
236
+ if confirm "Add '${name}' to registry?"; then
237
+ echo "${entry}" >> "${THEMES_CONF}"
238
+ print_success "Theme '${name}' added to registry!"
239
+ echo -e " ${C_DIM}Download:${RESET} ${C_CMD}theamify get ${name}${RESET}"
240
+ echo -e " ${C_DIM}Apply:${RESET} ${C_CMD}sudo theamify use ${name}${RESET}"
241
+ else
242
+ print_info "Cancelled."
243
+ fi
244
+ echo
245
+ }
246
+
247
+ theme_delete_from_registry() {
248
+ local name="${1}"
249
+
250
+ local line
251
+ line="$(theme_get_entry "${name}")" || {
252
+ print_error "Theme '${name}' not found in registry."
253
+ return 1
254
+ }
255
+
256
+ local active
257
+ active="$(grub_get_active_theme)"
258
+ if [[ "${name}" == "${active}" ]]; then
259
+ print_error "Cannot delete the currently active GRUB theme '${name}'."
260
+ print_step "Switch to another theme first: sudo theamify use <other>"
261
+ return 1
262
+ fi
263
+
264
+ echo -e " ${C_WARNING}Entry to delete:${RESET}"
265
+ echo -e " ${C_DIM}${line}${RESET}"
266
+ echo
267
+
268
+ confirm "Permanently delete '${name}' from registry?" || {
269
+ print_info "Cancelled."
270
+ return 0
271
+ }
272
+
273
+ local dest="${THEMES_CACHE_DIR}/${name}"
274
+ [[ -d "${dest}" ]] && rm -rf "${dest}" && print_dim " Cache removed: ${dest}"
275
+
276
+ local tmp
277
+ tmp="$(mktemp)"
278
+ grep -v "^${name}|" "${THEMES_CONF}" > "${tmp}"
279
+ mv "${tmp}" "${THEMES_CONF}"
280
+
281
+ print_success "Theme '${name}' deleted from registry."
282
+ echo
283
+ }