From 264e6490a0c40eefa7f50f266c361f05215f4e4a Mon Sep 17 00:00:00 2001 From: rootiest Date: Mon, 3 Aug 2026 16:52:16 -0400 Subject: [PATCH] feat: initial commit --- .gitignore | 15 +++ __pycache__/config.cpython-314.pyc | Bin 0 -> 3408 bytes __pycache__/gui.cpython-314.pyc | Bin 0 -> 9351 bytes __pycache__/svg_parser.cpython-314.pyc | Bin 0 -> 3796 bytes breeze-icons | 1 + config.py | 45 ++++++++ gui.py | 141 +++++++++++++++++++++++++ icon-color-tool | 3 + main.py | 89 ++++++++++++++++ requirements.txt | 1 + svg_parser.py | 56 ++++++++++ 11 files changed, 351 insertions(+) create mode 100644 .gitignore create mode 100644 __pycache__/config.cpython-314.pyc create mode 100644 __pycache__/gui.cpython-314.pyc create mode 100644 __pycache__/svg_parser.cpython-314.pyc create mode 160000 breeze-icons create mode 100644 config.py create mode 100644 gui.py create mode 100755 icon-color-tool create mode 100644 main.py create mode 100644 requirements.txt create mode 100644 svg_parser.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2345db7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ + +# ──────────────── Added by agents-init ────────────────── +# agents-init --agents +AGENTS/ +/AGENTS.md +/CLAUDE.md +# ──────────────────────────────────────────────────────── + +# ──────────────── Added by agents-init ────────────────── +# agents-init --plugins +docs/superpowers +docs/plans +docs/specs +docs/devlogs +# ──────────────────────────────────────────────────────── diff --git a/__pycache__/config.cpython-314.pyc b/__pycache__/config.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0df73e55deda1210b0b0e91faca08bab203f0727 GIT binary patch literal 3408 zcmbtWO>7&-6`uX$F1ZpZQ7&1?l(@2G8;mV7vXsiL=|MWOXn@K<4=zw3*BA&;^hTKsD6AEvKy%4Wt{WK0sc&Yvl&l(U zkvwu|X6NU<*?Hgh-kkMzdl1N#>!lAuE`1)q6S43%|U5P!r7VA0d+z+^wMsshu08oMjy&g zFGO4xs~eW674PJ+UbMKJW?Fm+PQdbL_e$wvwrpq^^I$i^Se|>?T*_8tkj*L9o3TZG zS&Ly^H}jfd#!6WKK+Bj$>{T-{dr`EsVtK(rTSqFJ$Jh&cM0kt|D5_|$w{Nq5qknU7 zW3aA_{^k596Mvp)jm3Ay;-4-x#?(gdyVdJ^vhSg%As?>~Mn9LMbw2t6mZA*VQTxac zIWp?9VQ5rQ61~q}_$@+N=nJ7=GK-AL9Jnz?0Kk4t)>yK=(-b)ZOfz4h?1O%~Kd7mYFQ)tR!j8Wi zq#OX0;9HVR7l8jfS7MVW@&5vhQ#qBNXoD;K3b<6k0o_F=#gy4HmqO$1t(UkYmmr65 zM3SttqcBokkE8@435I zm$t@Q?r_r`u8ruOm+-IKC^5d&^{hu@;uA(#v*)(zJ4TN^U zRs!%R+CYQZ9dY35KrX8g$jx=4Gpr(LI3ms}N4v8+H$zu<;TnME$&J2^pB7Rm72ztX6{f z&x}^iEShG?_(?1V3hu$q=>+ZJu@mT#rx|vz1=aw3oLOr-# zFR!$u%5xD7joWYq+Z`c*qHrbA^Ls{hfVcy`EC2OnJi%5cz8{Y2b0m#XNdl<s*Q!@&yeK5fx5PHf^Tq z9t?8pfHVkVXF{3iZeR59+c0<>9^-FN09Adx>y@?2`h&Fx^}*4G?|gOYOVPI{^>1Bh zO2fOp;HvMd-f*LLsCs?ZAKLoO=l*lm8-JGrEqP!^9%#wor*e2t3fJVObnYvf1IJr# zrRi2`m-hk#n}v@Gwb9?{t1?M~Ly$lF1~+3HvDzEk^9}!K%m3Dn|EG6AyK#9V}wVZFw8ULeug~HQ12J$htH7rpFGRNzL7*m`NmJu>A$>wCP-EN E8|<-#^8f$< literal 0 HcmV?d00001 diff --git a/__pycache__/gui.cpython-314.pyc b/__pycache__/gui.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dca7e7f0f88c326ef2cbdcd540b37eca6999bfdd GIT binary patch literal 9351 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 literal 0 HcmV?d00001 diff --git a/__pycache__/svg_parser.cpython-314.pyc b/__pycache__/svg_parser.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0b8ed1ade6d5eed56c898f996481e68c262fb16c GIT binary patch literal 3796 zcmeHK-BTOa6~8O3q!n6WLW~5KY`zl08YFN`VkgDK6zqn8u|{^4P7U6!R|~8WTKVpk ziE%yQtzyO#$C>G1HxGukGuUrUr!#3D`yZf+R`NETcBXmhTMC_~ee1bbp9se7Lmztw z&YpYExnJjZKCb#2y$uMOuaLU=hJestXs6ux60;WuW))pWQEmbe`n64P@7jp19krlN z6t(+NlpnGcwTV5-btB2~SK6JHIE#-cWI-(oipZdhkOBMn z5#ggYpbok^aYlKduBZd35Oo4|M_r^LhKMIBz;14<@yhM_F(YB%h=NTGtEJPho#;L6 z2nl;~=&r1VX|FPy z^_*o{3`LHV`RaG5#I}Q_yU{cIiX%O__;|9(WmWdXH> zc>%~O`m#0n=zE_wX88NwfCw5rmf64og$ z#=TY=j{g9mAox^-TCuKOuXtL@R&yd2d;3-4L|n{Pj;547W^Y2@<$M(Eb~ zi`S3whstebPrqTCd=()#IsxmYc<{{1?WnKO|1`ukmFY;bubdq=$(@9D?fS^<$#?Yl zxh^%0F?d$V159;6Thh9ic%(Oc@*Qb-fK3=#IX@HbWf6IW>4dH+p_S3l_Xk67&Gtz% z=E<46K$suB4NPx0$md2w7peU2>HatGNz;sntRdT_>%%!h_C!S3HWOBQAaeHYc7rdLf^!!`z=8jE#aqbt&lQWOcY@GV^?TuSo z%bUwjZ~biLw}De%oXZ}Y$c%q9`8PKb{r}n(P~bhzq=5bARq5Nuc(vq(Fn|!f2J`zb zncu(P6k)^r$y*?cgH{myzMFdfelhFCxc+0+Ver8JIv;{&#ha=tls&JV?gurdzC|do zb*-8KXDG0&MHF^8PiN&2?(ftwbCT5!7-}{(i@ycPmv2h@%P? zpm}gQ1q=8O3ahsTdE1NR6g7#Pmb}d0F?C4Gsiaa`GS1=yQyW8Z>g16f4{PviRH!^Is>=jNM* zX5V_>!@fuA@0z-x}Q#*nLqj{k%E41Z>_N>spBXs^oJ% zo1fhLdGl7=PurfJ$#tIoRrK@O&t`MJu?$p=uJsQeez>vxx%iow^Pi(75Nrz2Iw=6r8JXPxSnEyqV>jlxz55AL&)pMJPrgb3-|G^dGkU?R_NF`x)t0E zLQxeuelMIqctd)2_!sv6!R8)*!%CH#XI1WhnG;~w`gT|iD@RFftif4dm_0yFW2CN? zR|5M(Jkj^+GuzdRl8eO?3nAPIQ;HSGo-$IH-kyaB9t5ri4U11FL&7$HVzRUpCN}3xqP`_oqPj z!Nl6c{YlD9u1(&*^!#Q2^CL(94-{7~zO>svI0fhNLe>Tvc4-94lgSo)#{}DvUx3szy zGdF!TRvmC!66sqysVr&mmTr*cB_p0rP+pYfTWKXxk{prcIUSoMp(nMZA#A=aclPE-2Wk};#zmX=I32VM2%j{Vc*HxlwiG(b}Zw+tFAWWkf=zK8) z(UgcOq4~``3FMzY0vqkxoDSb^lgsfQx7+Lm_5|k`<#wGeN6R/dev/null; then + kitten icat --clear --transfer-mode=memory --stdin=no --scale-up --place="${DIM}@${FZF_PREVIEW_LEFT:-0}x${FZF_PREVIEW_TOP:-0}" "$FILE" > /dev/tty + elif command -v wezterm >/dev/null; then + wezterm imgcat "$FILE" + elif command -v chafa >/dev/null; then + chafa -s "$DIM" "$FILE" + else + cat "$FILE" + fi + ''' + env = os.environ.copy() + env['FZF_PREVIEW_SCRIPT'] = preview_script + + find_proc = subprocess.Popen( + ["find", ".", "-type", "f", "-name", "*.svg"], + cwd=breeze_path, + stdout=subprocess.PIPE, + ) + fzf_proc = subprocess.Popen( + ["fzf", "--preview", "bash -c \"$FZF_PREVIEW_SCRIPT\" _ {}"], + stdin=find_proc.stdout, + stdout=subprocess.PIPE, + text=True, + cwd=breeze_path, + env=env + ) + find_proc.stdout.close() + out, _ = fzf_proc.communicate() + if fzf_proc.returncode == 0 and out.strip(): + return (Path(breeze_path) / out.strip()).resolve() + return None + except Exception as e: + print(f"Error running fzf: {e}") + return None + + +def main(): + breeze_path = load_config() + + icon_path = None + if len(sys.argv) > 1: + arg_path = Path(sys.argv[1]) + if arg_path.is_absolute(): + icon_path = arg_path + else: + # First try relative to current directory + if arg_path.exists(): + icon_path = arg_path.resolve() + else: + # Then try relative to breeze_path + icon_path = (Path(breeze_path) / arg_path).resolve() + + if not icon_path.exists(): + print(f"Error: Icon not found at {icon_path}") + sys.exit(1) + else: + print("Select an icon using fzf...") + icon_path = get_icon_with_fzf(breeze_path) + if not icon_path: + print("No icon selected.") + sys.exit(0) + + app = QApplication(sys.argv) + window = MainWindow(icon_path) + window.show() + exit_code = app.exec() + + if window.parser.history: + print(f"\nIcon colors updated: {icon_path.resolve()}") + + sys.exit(exit_code) + + +if __name__ == "__main__": + main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ee397a4 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +PyQt6 diff --git a/svg_parser.py b/svg_parser.py new file mode 100644 index 0000000..ca3af6a --- /dev/null +++ b/svg_parser.py @@ -0,0 +1,56 @@ +import re +from pathlib import Path + +class SvgColorParser: + def __init__(self, filepath): + self.filepath = Path(filepath) + self.content = self.filepath.read_text(encoding="utf-8") + self.colors = {} # tag -> hex + self.history = [] # list of contents for undo + self.redo_history = [] + self._parse() + + def _parse(self): + # Find the regardless of attribute order + style_match = re.search(r']*?id="current-color-scheme"[^>]*>(.*?)', self.content, re.DOTALL) + if style_match: + style_content = style_match.group(1) + # Find classes e.g. .ColorScheme-Text { color:#232629; } + class_matches = re.finditer(r'\.(ColorScheme-[a-zA-Z0-9]+)\s*\{\s*color:\s*(#[a-fA-F0-9]{3,6})', style_content) + self.colors = {m.group(1): m.group(2) for m in class_matches} + + def update_color(self, tag, new_hex): + if tag not in self.colors: return + old_hex = self.colors[tag] + self.history.append(self.content) + self.redo_history.clear() + + # Globally replace the exact hex string, case insensitive + # We need to compile a regex for the old hex. + hex_pattern = re.compile(re.escape(old_hex), re.IGNORECASE) + self.content = hex_pattern.sub(new_hex, self.content) + + self.colors[tag] = new_hex + self.save() + self._parse() # re-parse to ensure consistency + + def save(self): + self.filepath.write_text(self.content, encoding="utf-8") + + def undo(self): + if self.history: + self.redo_history.append(self.content) + self.content = self.history.pop() + self.save() + self._parse() + return True + return False + + def redo(self): + if self.redo_history: + self.history.append(self.content) + self.content = self.redo_history.pop() + self.save() + self._parse() + return True + return False