From 6189639a387c7b2c9f68d528db6254d18ced2161 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=B6rmet=20Yiltiz?= Date: Fri, 31 Jan 2025 19:06:47 -0600 Subject: [PATCH 01/11] Initial draft for lisp eval support --- lisp_eval/__init__.py | 106 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 lisp_eval/__init__.py diff --git a/lisp_eval/__init__.py b/lisp_eval/__init__.py new file mode 100644 index 0000000..84aa07c --- /dev/null +++ b/lisp_eval/__init__.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2025 Hormet Yiltiz + +from builtins import pow +from pathlib import Path +import subprocess + +from albert import * + +md_iid = '2.3' +md_version = "1.6" +md_name = "S-Exp Eval" +md_description = "Evaluate S-Expression via Fennel or Emacs" +md_license = "BSD-3" +md_url = "https://github.com/albertlauncher/python/tree/main/fennel_eval" +md_authors = "@manuelschneid3r" + +class Plugin(PluginInstance, TriggerQueryHandler): + + def __init__(self): + # search for a language supporting S-Exp: fennel, janet, elisp, clojure, racket + # TODO: this should be a configurable option + # Users should make available the executables in the system PATH + lang_opts = { + 'janet': { + 'prog': 'janet', + 'args': ['-e', '(print {})'], + 'url': 'https://janet-lang.org/assets/janet-big.png' + }, + 'fennel': { + 'prog': 'fennel', + 'args': ['-e', '(print {})'], + 'url': 'https://janet-lang.org/assets/janet-big.png' + }, + 'elisp': { + 'prog': 'emacs', + 'args': ['--batch', '--eval', '(print {})'], + 'url': 'https://www.gnu.org/software/emacs/images/emacs.png' + }, + 'racket': { + 'prog': 'racket', + 'args': ['-e', '(print {})'], + 'url': 'https://racket-lang.org/img/racket-logo.svg' + }, + } + self.lang_opts = lang_opts + + test_sexp = '(+ 1 1)' + detected_langs = [] + for lang, args in lang_opts.items(): + script = args['args'][-1].format(test_sexp) + try: + proc = subprocess.run([args['prog'], *args['args'][0:-1], script], input=script.encode(), + stdout=subprocess.PIPE) + result = proc.stdout.strip() + if result: + detected_langs.append(lang) + except FileNotFoundError as ex: + # TODO: does Albert has a logger? + continue + + + PluginInstance.__init__(self) + TriggerQueryHandler.__init__( + self, self.id, self.name, self.description, + synopsis='', + defaultTrigger='() ' + ) + self.detected_langs = detected_langs + self.call_external = lang_opts[detected_langs[0]] + self.iconUrls = self.call_external['url'] + + def handleTriggerQuery(self, query): + act = lambda script: ( + lambda: runDetachedProcess([ + self.call_external['prog'], + *self.call_external['args'][0:-1], + self.call_external['args'][-1].format(script)] + ) + ) + + stripped = query.string.strip() + if stripped: + try: + result = act(stripped) + + except Exception as ex: + result = ex + + result_str = str(result) + + query.add(StandardItem( + id=self.id, + text=result_str, + subtext=type(result).__name__, + inputActionText=query.trigger + result_str, + iconUrls=self.iconUrls, + actions = [ + Action("copy", "Copy result to clipboard", lambda r=result_str: setClipboardText(r)), + Action("exec", "Evaluate S-Expression", lambda r=result_str: act(r)), + ] + )) + +# if __name__ == '__main__': +# plugin = Plugin() +# plugin.run() \ No newline at end of file From fc59e702d44490ea5844f89866eff2c77705dcf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=B6rmet=20Yiltiz?= Date: Tue, 4 Feb 2025 00:46:03 -0600 Subject: [PATCH 02/11] works now --- lisp_eval/__init__.py | 152 +++++++++++++++++++++++--------------- lisp_eval/cuddles.png | Bin 0 -> 10836 bytes lisp_eval/emacs-small.png | Bin 0 -> 4755 bytes lisp_eval/fennel.svg | 24 ++++++ lisp_eval/janet.png | Bin 0 -> 20664 bytes lisp_eval/racket.svg | 17 +++++ 6 files changed, 134 insertions(+), 59 deletions(-) create mode 100644 lisp_eval/cuddles.png create mode 100644 lisp_eval/emacs-small.png create mode 100644 lisp_eval/fennel.svg create mode 100644 lisp_eval/janet.png create mode 100644 lisp_eval/racket.svg diff --git a/lisp_eval/__init__.py b/lisp_eval/__init__.py index 84aa07c..6e0d44d 100644 --- a/lisp_eval/__init__.py +++ b/lisp_eval/__init__.py @@ -7,100 +7,134 @@ from albert import * -md_iid = '2.3' -md_version = "1.6" +md_iid = "2.3" +md_version = "1.0" md_name = "S-Exp Eval" -md_description = "Evaluate S-Expression via Fennel or Emacs" +md_description = "Evaluate S-Expression via Fennel, Emacs, Janet, Racket or Hylang." md_license = "BSD-3" -md_url = "https://github.com/albertlauncher/python/tree/main/fennel_eval" -md_authors = "@manuelschneid3r" +md_url = "https://github.com/albertlauncher/python/tree/main/lisp_eval/" +md_authors = "@hyiltiz" -class Plugin(PluginInstance, TriggerQueryHandler): +class Plugin(PluginInstance, TriggerQueryHandler): def __init__(self): # search for a language supporting S-Exp: fennel, janet, elisp, clojure, racket # TODO: this should be a configurable option # Users should make available the executables in the system PATH lang_opts = { - 'janet': { - 'prog': 'janet', - 'args': ['-e', '(print {})'], - 'url': 'https://janet-lang.org/assets/janet-big.png' + "elisp": { + "prog": "emacs", + "args": ["--batch", "--eval", "(print {})"], + "url": "emacs-small.png", + }, + "elisp": { + "prog": "Emacs", + "args": ["--batch", "--eval", "(print {})"], + "url": "emacs-small.png", + }, + "fennel": { + "prog": "fennel", + "args": ["-e", "(print {})"], + "url": "fennel.svg", }, - 'fennel': { - 'prog': 'fennel', - 'args': ['-e', '(print {})'], - 'url': 'https://janet-lang.org/assets/janet-big.png' + "janet": { + "prog": "janet", + "args": ["-e", "(print {})"], + "url": "janet.png", }, - 'elisp': { - 'prog': 'emacs', - 'args': ['--batch', '--eval', '(print {})'], - 'url': 'https://www.gnu.org/software/emacs/images/emacs.png' + "hylang": { + "prog": "hy", # this is a pip3 dependency: `hy` + "args": ["-c", "(print {})"], + "url": "cuddles.png", }, - 'racket': { - 'prog': 'racket', - 'args': ['-e', '(print {})'], - 'url': 'https://racket-lang.org/img/racket-logo.svg' + "racket": { + "prog": "racket", + "args": ["-e", "(print {})"], + "url": "racket.svg", }, } self.lang_opts = lang_opts - test_sexp = '(+ 1 1)' + test_sexp = "(+ 1 1)" detected_langs = [] for lang, args in lang_opts.items(): - script = args['args'][-1].format(test_sexp) + script = args["args"][-1].format(test_sexp) + print(f"Testing {lang} with test script {script}") try: - proc = subprocess.run([args['prog'], *args['args'][0:-1], script], input=script.encode(), - stdout=subprocess.PIPE) + proc = subprocess.run( + [args["prog"], *args["args"][0:-1], script], + input=script.encode(), + stdout=subprocess.PIPE, + ) result = proc.stdout.strip() if result: detected_langs.append(lang) + break # TODO: unless we provide alternatives in drop-down menu, do not bother detecting the rest except FileNotFoundError as ex: - # TODO: does Albert has a logger? + warning(str(ex)) continue - PluginInstance.__init__(self) - TriggerQueryHandler.__init__( - self, self.id, self.name, self.description, - synopsis='', - defaultTrigger='() ' - ) self.detected_langs = detected_langs self.call_external = lang_opts[detected_langs[0]] - self.iconUrls = self.call_external['url'] + self.iconUrls = [f"file:{Path(__file__).parent}/{self.call_external['url']}"] + TriggerQueryHandler.__init__( + self, + self.id, + self.name, + self.description, + synopsis=f";_$l4Ga>bj`7i>_sD%i5KePy&ROKzi@JCo_|o$;@P0 zdhb1Cv=UgYJaXLymPdgWP zoSrRh?!8UY)cY|^e{oZfT-4Z8gj3wuQ>dB+@N*^19GIWwH}`%ZZRxumK6^z$Tfd7F zJ0{L3?mRt7-r9E^faRY6hExDbJIr5U5)&tW<^+6)6u!$1K=>4Z>1z0X7Eq2hO1n-w z185e(M!yZxgbN#g7Um^0Kr_<(9p(i96c4WRZ&^#@l=BEtG^!mT_;GK$+s zva*>Z${R^)K?}*L=pur;ljeZwIDmM!w54}NSr@C~>{sG+o}LcCSpyp;g!!8>8_cgg zNz#g1Nu;ov_@ovP&u9tR6)GTGeK};aPY(I^Xf`!B`(%?XzByz^FrRoODoCWDngW!G zU_z-B0Y>;;ARSrtW-p`s*x+RJic}J3RNx5ex=Es}k@%(+Qh?9|iYarOKbHh$mlAH( zG1Gz6)DsDI(jTi%e8)2V(PW1{}?ahIl%7ai)4~@ND6r(iXBi&G0B}A8-K=>v34* zGHRh=!NntN2D;rlL8XiZcxbO}djO9z){O;___Ety56}8+L0doTsWAke8E9;(f`OxM zM1r(|@{q;~JeNU~L#a-@9}_`@^9Ok5qabmwWD#KIs62iKQxHpGQL|O4f>DEjQNn7n z#g{YY;GqFTyZcGD9-~i{vj;jps^9;aDFAD?m1Hc!nr+w zOGUKJOpTm_wfHYc;+ZTmMvo>>gT;Ct7OlnD+!cZ+lv{3B;4u_ zn7EF<$*DX>c7+HW0z9;l9hO_cBpb3)(b|85LxQK~wBn9|nILAH%~=#Qn*B419S%I2?mYvZ*=2K}sV5ety`yE)mzWge z(aCR(SC7ls=@~6Hhm>ZJtmbg4Oxn`7)Eoe1mv<8Ppj?Lr4?R)JqO@fKMAGKIyB!KZ zGBW^#9*S3@d_sVS&YvOemI(mmH}^UgfC}3Ot}->+jEkw84si787MN=eqj9(dYU%rv zgT;TobH*}YW24Tz>f>Wd3LDG0KPI0DYEN1Mppymd0}C87?**Qi%$P+8$|;?Y;Gt)5 zPMNv(&?R_LUw7o>>D2(Dw2|ULD8d~j+H6UD2rFYv=T-L}F70w~%16mi73sVBXsYEk_7%H;IRC^ANsoy1Dly6y=^ zeFXry4Mk3S)zar^ijoD6mMY6JhEsiHBC-&tiif&^sWb6t``EGwa6Ivc^hK#9suvH| zFTQi|hlwS9@|$}fr2~sJfC95iSil&Vp}dNb%7A*nIk{%SfTyTqV3Mj_N4*GWEMgFi z1&d))er9RAK@eg8#t8!;F!*lOZT^H5RTd~C&qMLjVa*#fq)@kFA`~LZTl$dJ5vH5- zvUc|jN6D4N<<}cDrokp!@6g~;wD!*j@C54y5z{HxJ%a#`2xV;K7;RbD+>(cC-S3^ zA3jRm*lPvt*dwgw09>tw@o@lK&HaJDx)ccjO6{pB|W)r98&z&KHQHl6sZI$K&nqi*3=C&qD zhKdG_<~?IqQx*u8O>C-VVm*B^*hSabV{Yq7j4q2!AV9IaAIm420O-k@NIWZ*xArfv zC%fkg>~}0SqQ)J>sM>w^2xr}QCZf|RCNLN2^@soNFYOu}`&bdSX#v&s4c$G}NPSGT zH(~7`W+oonU`mq;6^PD*J#?o%xa$j4E$K`Wqu4W$-F)U;dK0LxnqbRSj&(a>kUksC zIg1OsQm~uP+%%<~ux4)|X-n_e){lYP9%oMN(h-)r%51M8a7fH*#L(EYXN&>VqcruV zF@U%)O3ZFN!!aSg8O4aOf2HMPdC&{oK%(ireojo7h6%Rx#xhJlp93XO4S3O0#?l6Q z!&Dmx4{2jPurUuR(h?}L?$p__bLZtPeGB1621bfG46FSrX=gL=R!G)9v~v0^rfYON|(BTth)3tD4GY3J!FPWCob zM;Bn@gwuwz@N5Z>wPx1H%{l=%Jpr}FeM#v3kuJ|%7LyJ;MhFZaziR79xe0?Ak| zkhSJW$7+HUini;s#lSU0?`!jDaUd)vWH;M7GkxTQ>Un8P-(B`>`z$h)7t@>=>|%3a z);T^ZyR37x?7Oh6Yta6>xK^DwgVJZOu}#FcPVhFq)|gxUxD?Z2v=l~ubMMo32cW|C zfw>|hk8={#og}+L1?;9XJaJ8pDe(bYDA(?egTlm|DQCUonW5~aGkp%L)Y$I!=jAPZ zx7rPO0L@cCw`Q5bL%~uMcGH=i0huKx5XpM?q1jCDc|l6Dj6ti2!DL1pUc86q>zLd6bSY*TS&&lxlI z$V@Oh#LBTG)Z-{s3WoIZF$GXj$AAkM(aGlAJRYvXrZgBT zDm2l6rjLmN8)%L_1QFMp(_lf!w)kaa?g-|ab>=+0_)&JzPr9&uU>4kv!W=2&mtMqz zz~VDBWab#qm=k-(ZlclB=H3U4$;1z<5o5_^R`1qoz=e+m`_+E3^2h~3MAPLwVm2OR zGy5mCpv6=Gl`C5NuD3%rVn2dtttlWv(TnG#8?#tnd8Lz2J>$vO-Wd)JiZ2hPlJ~a- zk`LU1$=C4AOjM(4>_ru5ixqZDDl6$6be0aCjnQDNVI7`ZIpQLHb0mwr^`#fN^`2E^ z?v;zlBWpL1&-W)eBzQh>3n91P_XL?dbvl_cZ6>(`{{7z80E2(;l_)pG{=wR?PS_pv^#I8-OE>p)5)6GJ|PPiUrTPj`DQYC@?_%VqJM7- zBG=t~JDELuHd(f88F6-YhG%sm*Dt@5Y}ylJZc7(@iO+z?`h6=QGa)a_h=f zWZJZ8Bsn>mba!_XZ*Onn;^IPqX!VQ#q9n041CawSpFs*MR>g?0LI!}d5UYiq#`C~h zF?b6?481A(iVZhx+DD#QyO#9z^$~jf{PWM`l~-P&7WM8&o^=>0wC?S1sAux<@F2hb z`YZLU7cN{NYvK9v=aZ&P9j3~L+PUauAfAV{ezTyxe}m4wIR5zX=Ak3W)Ct5yvy>g1_pWgJq=w*V;XP1`foQfYt}5%-rlYSpqiQ*;_B+EUex=a z_}ehiJ{}F{Dbr?l(%IRmUeJpdFOn@=wx|~rCwgW7*&Q{m0fe?3 zKFj?1^U0}Gr>JMWbmcM>VSvcHd@DyPj*F; zDbr_C3znIgNq+n7H}cCbzmSB41TE=9&kg_GH?9Hn%6r??&*J6fMOs=~DDa>sru(cz zc3-AJ8wl1{(9(C##bE&i`uWS@!NeXaoS>~>ILDb(k-2XJ){-u*$|I!;m*7OJsT zn&+xwGSE%q=!3Vu^d`=#2<&Kc@lCf;QQYwip!FZ^G#WtnQDKWr^=XBgMl`&bNd@f# zlN}yFSP?00?tPpt2O27MmTH9c6Uh}nm^d=K`~|Dp9Kxt$jn)9EZA0qRvzE1D+PM+#YR=@bR+V;VDI6-y} zBsT|Jwb&K2a4bhqs`Aloew^=V^4N24en#dlSfm1yGl=&oArX>V5s~3ur_@@@zQ%)q>M14 zvWjoq7bc9|>dX1j+JN})-dHM``O%JW_59F1l)S$+fc)dr{gj7#ktB&yGq zk3L7uKR@;&`5XLRQ$mc^hR)1$F#hKe!p{symM>=!V#G)jRXa!Llq@|ICH`zzaBk|3 zKwcN);Mo^Pg#j=Vjs!J&Vjd2uBDCt%wZHIpQ>M=%(`U`2euj)mV*zAR%R1kjpWsFE zI}j=QM;xzgX$+^-g++!rS^S0*&hgyRx$XhHdL}mr$!KqalzHma56Kl^z+QFTaDNCaYya^T{Cqvd7<_*O z&ay=XgMmC5yZa1|BXRj_w)o^+WZbR+>2d#)uaevEUqjZtw?jQ&dLQRby>s1r?qvC0 zkC3aby_u3^Rw)>a690?cbq0s5qTOX*xX{ZQoAo#F?B*|ALY{kTi$Tz6&Zpn_oJ^ZB z7wn+ttwO5>PwV@zjKW3it}{IR5@pkO2IVRkj|RJkk*Q$&Jh=9sMgV9Bx$BV^DEnvq zhdZrh;9mlj-M&9E?~6o!`8;;#85&$ggL6n)!M)(2{f}vT=pK+J*WCCA^8DLdM+DHC z4PTI1S1hE;ZLrYM%AVpSkVeN2M~h$2D{5lnKIV>8VdW%{KCAbH3R1WEan4&~vo>tn zOG%Y`pIC1YJTJcMMsEG{<3lu(w`@lv0r&rRk5GPmET{Ae77Zp2|77_c;L7u05r4Dh zCSd??`JE4vIagjwp4sr_Z~%c}egD&M5NDT3X zKwcvl*f+8W8DRvbD6R#m!$%vy)-_ryA9;=n7_MBkj=b>BHp(Vi`PfTTQginFg;c>K zrX0rfScBl)^2n^>IV@82B~?=tx3~pxo5qZv;~q+`TXqKpq6LeWk_C%yqUaP?U%#BZ z`Cm`FsYCM!;fGZ=ceBxHEqe#_I~d6w_E7Biz*FlfHUO%uht|AKKH3>>Z@qzj1Mh4t ziyGAk&n#N#9>{FE!ZO!BlS;N?X53z@U%?)#kKvS#c@2Dyyxuv1Uw$pvtm_X%iuUXd z<_GN!6CU-75r5+uk@xqQoRZ~nyfW*BAz%?E?+)Q7vt7qbpWdU%E+Dw5!H?l83wS|pJwLQGH1J`=2gxFQQXRc z!A!0Y*h0an%9S>b&0P%|L!>Ej5G0(clceDIc~W@dg4R@Yog-<*9aLLq0Mf6Z>UKrv zlrp2zykjM=Znm+zZmR~EwrZrlKO%37t-uqUs<_cTfLnX{8L`!#f{0sA4+9Q85mbWu zUJ@awp`;XQn?up!<HOa7W`DvVU`eoMe_ABQfH7;+I}b)v{aBm=@b8#Bzh1Ketiy z1@X@)A@Yv12Emip*iU@YipiFt?kksQsRjGqV(|^}3!A2ZaaHZ8EqD{<&FY2BZ~Kn; zXO>z;Dgl5_q>F22SvkaVm0JL>S_?dxC7_Y_4>gm@$>t_ zg#Jh3@}IT%rou9cTz3TJ_BgD88zLa$=F6g8dF&hs<5x2dA}~%b?gLHjRyRXs z^H4SUc>hO|3y8eaFnG|2#s(1E{5aRi9&?*P%=E&=u_l5}$)2aube{ZCiNLmN=tF7Y~NRqOZ0vZ75 z!qGVC6IK(8z6sK~;09i_2`FFzK-3?HH{z@{D=74z>CU0vShR`IBSC{xv!x9qM;>Gp zK5mUY0=IkJW(_Jac2eQibQ`pJvdWK9e}l0f5DQa>A|dCaFd3jH)ttuIHKFpQ!BOMX**q8ZRMuszWZ}*rUWqM%18?Q8ns0HWu$CN* zmos(^zeM>sF8|-nc`n1LM!4QqYYaWfy$MWstOn<*$l6)OwIj-Vn zG@P~KbHSVny-{X5(u{F0)pI6hF`23`f;@H?su){rA}0I-lI8blUVnc?p1Vzs<__R? zr;Dp85?EAL@#0-Uxk@IyJaxaI6veYl?MY}_!7Z>>KdH-%0(eB&*l$q!WR!MM_YE)O z&;CiW+ijYZOP5qn-W|-B*^ef()*r-u5In5bMudI~%X@W?usLa>&k29!vF&*=vBwJpsLGUO#&(d)p zWCVOUrnE?9RR>!BUbX_zR&Y^+z<}3z`#!+m`KA|>Bt@%L)ER*EcpR^6u2xDXO1#Mu zN(%t!qF1cM;#zvJ%T{Q*?)dz?8pE!;q^=v?cFK4L6}oiHdC`fv<#XHvcnvnJY>3yA zLfo>MVAwWe>H(w0n8&gX4OOyH^SYV&wO4_9J7dXrhNLMUwb*!$5Ndg(9^s-9sT!&I zb(%3Y`zXBzRrbhM8^vbe&HW)VtN3PJX+Wh0vP!AY{H`#eH9SGt@&3z)q7Xuy-Tb*0 zy*OaQ?YB$=v`^j<$ZHzzeHkE$9Xm&kCdgFkOK3kwEEH6AI`5Cn+h;2&bRaTME4T=T zAV>r50NOxEhRP`EWS)NE$N9g|^a3p-9&DH#OTH5%XImn_!t4r-fModty79Qw!uApJ zUZ=4Q4dvDPCCMLivZs+Is+tXAvC>!a!Tep+1m(AQQWT?4P}_x zp_VF0C-F~`->vgDJ_x`69nZAK49A7qeywt{H|zG-H0T4@8km7 z_VRYJ*OACCp8;=Gg*5WxENh|V}OH4jFK-=VKp+txF6yC3HmL*a?!BtfN7#fJRC#_781 zYqaPyoxZ@Pr}4Zp`))N;(Rk7s7Q~xr0L4k0sEPqqYNNlb;lxaBvYT=R7VYhc5UUq6 zLEg-8G+dx{M5RZlK*$t*_fV9`Lss54s%>&8TD(p-4;d}0HwYe_@v}Tgx-ogMFUEoa?Q`7voI2M1Euj%mHOYSlN`3{2u&}T3Y)YnT~uvWW28e-;;$`D zqH50cl1{QGQcMHr$2eZu63y%V`15Zru(&k{7&@GfWh@@yc}yD8g*pVchtW1-5JbOw zM&$Vfq$r2cONE6^(?GHZ={7{bG>#$a1{MEl9VGZteLEC3c-HE`3Ck?J3Hv*{2l7rL zyC1Cf7-3Pf$z=MCak}v&xUF-#$&_+tLZw ztn1Ft!GIHP7Lu+3NM~|nbqmarLeb*S8FAskgTOR}h6FlYRNFOd zG0;1srInh#kXywhfEqB3p{>RQ!PcVm;tM=9^*1c_N>UQ#2(h06a3rt}US+@eS`9pz zlA7szLIpu+ob7_m!jL!y zA}<<$pP~`cj}TNd384C6UT3`Yp)EF|`0BduNQ~D6Owmbg0P(=Jr@SxUoQr`eiaV_W zp2+NyN$?hhA>#K%NvM1htvh!F3&{RR%XlLI%qWZ5djMov#~E6equVPt03hmG$so4r zMPxFq0WU+T?lmi$Pfh~$BDXbwj;0n;RkI@*&on#1BSetRvlev~+{aB+V{9-s^E9k^ z+ysmpgl81}$&&B)j+MM<*_$>*)xc&4H1lvzC_lgeH#mt(Di(E6&w$<-4LWAxH3F{Q z$Z%Z#)3zuypsR1u^BRfwgA-}3XggwA5_TH1mZx>5>8ZGMS z7m}vD#*%7QDo6(E6v3csIYPj;`dHt!b_VBCaha^Lp=UwK#c&+xkfGbmGlYa zRgUO+$=c5l%nMTY94-tK6g4p~eUw$5&hX__C8NJ8daEh^FgXX_Kxbfz(xSv0Pth=K zXArO1+{MBSJEhNSy6HA0zaNRqzhCz{$NIjX44y{)78NT`)O?z_Gu-3HD@O9Bq349R z{2EKCuA26Zq2Hk+fzsF@Z551aIuD3ma71R&LaRuh6k+9TH-GMFyjfiqKgLhc3qx1l zCqZU$<`yKG$lL^~xMirT1ga&KA^ROiUO&t$jL0g!5}xNQ6-Wf|;9JVD(RvX;vTPKu z6AK@IvYz40p=cQ+fhUw#HB_4I$GPC2B!Ad?IjeZ-YTEYEr9q;{gFW=H-G{iOc@_fz z$~8CtMc-2hw}PfsDD_<~_?D%G<- z*;Mg>A=QO@h7B_D@9_Gb5qZ{4njVT4ZKADt-J3$|{y?l07w;mx*%rHhU;~+)GeVc0 zPD$YX9ZpctI(?@B4~&X@`=l08_ily0h8{-Kd=sP_m_DTlV{-cVKDbwzWnG#<*7FA< zMK4=D;_Rt9O%RP`eTYVmvFa$tPb|5PqOt?IyOpNCZ3(hwtu`cXLAg|iQxMA!>NTK) zh74#7MOdLCs9C_1=sF2-GIO<-}=|*8t0*_l5}%nak8fdk!<{2p)`sVVDK`7)It+QaL_ay<)J| zGeWp6C9l$U2fBEK2?J@-s1HE-t%JlJ-g=C0fW5TF-0Oho|H9NoLo%4AZr>j*QL*Xr z4(hvxSB!XtHE6VSg3~Kj@}&7By9zwD@z%Xz0)N=(LEUw>{0VRqVsCig@xf6g@4owpjgRzOF%Nx?{)|C#j^7D$xN(ywzPKMk@)|udIu7RhG1-`Niw1#TvqJ}y zqo}saIe}j>Ii6SMnkK4R9-Lb6Ab{g#aBjW?fcWkS75IVlJ_OSBE3j8L_$A37$&}Pw zh3Aym1?dZJdL|OZaUrq)pP_upk(2^;c6eWyFeQ##HmW2_%q^d}cc|T((Oek}TuR%| zXsJ+7mFhz26PjM|XMNWSOi|7S&!d|`PR`|^U8o?wy5$sm#+SZP)^OaJuBS4j#;BZ_ zvQ@W9c7u0Z{)-vnYAtG{yt2bNHLr4(cWnL!@IGoyGE&3xDE6RcAoECafo2t_?+B{+ zBWu_`+xxFj;XACQQ*dGek_nusKGAHQ!81^#$@;!F7%VF?$ftnu7n& z!U|DSkG({$k7i;JrJiOK8NRm-j8MINB(RAVf%NHN8a((6hoi-xvIrSvWJqdUKul-j zqG6!UP$zRdKf+qIUYKduycTkw>9__G$NUntiwL#zPxAj*qa%k$2tO}PTr-VDh!F#f z#9J|Mf(wV$i~2(4^3JowQ=7!6K;4fZ8fsE-PH%x4CRxp9^nptrO|c7ylDEj` z&8j#~4uA(nt0J(giotjtwfJpsn83#h`K}|CO(&gMM3^zsMOCi5ht2}KL>~>Ld2$Us z(bdKTtf@2vq?QW6(?VeK>v~8u*j1<&>C^*8yusTFPUKgNn*EQ=DxT-&&urb8qQMS=w*MOZoYKxE!t>|Rc(5-@U6Vjz$-)f!3;v{&e}H4Vj6&`8E)?GV9hfJ%_= z*8q(=ec{jT_Kuaj63rk zDSoL1ZA1*V3mVl3D9i|B4HoitP%2>u2rP6%-U?PmAo(d?+C=?)o1P|^V6cAg86i9r znqGLNDfbAHGL1o^e0OlF@)maI86K&5RdWtRiax_QjAot&lYYBHh14lFzMz(nK8aEW z(-7F$0Ku{^I-jz4^hqbIal-Eeb+scRtLQpKRfkD7M|ft@709Gy$m8nwNsz5%ciW01 zTUzT166qPN8`~9>+YIXM=gmirYch^tL004LV16-xCJEHzW<0t8>@Bkk`~#B}s~G~% zS@5_XVt3f4-df4z3`zB3@3{QGf&}>oY`|9_r93^u^A2M~8t0*CvD@Kj@fZ8Tg>M9< zD(}yfRL{$i)iKr)3$DAJ0q~qT5|_V<-O&j+b_M5hEbR39oqv-233eqX;3%p+Hf@Im zG*QgeVJR}Zk`r_UrYIM|5MBeqIHMQL4#~JiXlwHfN7CVNGqX{xRndiq*RM&oXrP!f& z>J4_=ZQVYt`a*84&8IIhViYV#qY7J#16xDP<;MN zBOg!6=d!EB4uc~sqj2GNRT+|@R&#ZR*<#SXpW`yVel?+)STj*@ln@lBN7%P(kD zvmJv|l#_x}6s`d&3byKEq7QE)+PQ4=~8*guz;OM^aJTLfn$rhiSpTLIN#IE23 zA2FQLS?&S6Hd9);VAp-*Xm9o9{O?Fy{zi5cC;0G+k-TG6S|gZGHLb#8M*J^U*Mc-E zWmj;5k7Plm>#pG361pMOu22Eh3=1bp8*EvFfYEKrjg2Ba!-W~_3bMn=KS}-aq|FGk^}%cz^)!UsBth#^bvgoHXhla?-&3C6L#a@8!5uZQN3Xq zg-h6#V+S?l744G_Mv8XP#g880BC0tAR>k7Pa%xm(SWbP)!}m5+F%L zK~!koty*c2Tvv7e&VBd2*YEW@J-alUM$$-@HyOOe#(UTtNZ6eeMWD(QP$|PNNGhQ! zR3()ZKL|zkq*yA1B0xfnsbCyqJ7By>w$Nf*(pbxkW|1^Y&(izrx7>U3<8}A+j3i?x z?y6VQeY^Yicg{WMJKs5P2ygJ=;wscJ2>>t+7on;H0HjQb?nJ+^MS}dOA%sXvNj8BP zq4pJYRoT+jnwaOy$2AxX0EXy;(#rq<+Zchb=H3tv(BeWel>&t=`7!kO9;Z))j4 z2mrnAeiZ7*0053L3{TC!IR*#-y7_!}*KIhn{yYHS-kqPy_89%6HLrG;n@HX&6ZUQ? z&An#APD{szVM&k>UB}S8G4PgwXpnj+ReXstn%dS^ui$z0Jf!W z!NuweZ~h4+kwy&_AZc3vG(hk6zcVi6z93LNZ{61O+0nCg>v4Yyl8rEhmpt!U& z4O4&pbnWD~PAxvM+;*JoK`mOjB0}SGiw=MY01&jm)BQUhy0=UA|6?+j`@ptauX4Nh zjUw5fhcIOfs)-j2K*wGO$3WX_P4F$lOxuE+??S4#8*V1k<(F!ArIFd&pBg_sS3bT# zFe%Vg^qCW=>DAY7Kot9)5_T{7s*c~D`26QGvilqP-NR#pw_HWZ{w|PVfVIB#%Z#?y zTXs?NiHbC$KtToEK7Ljj(lp`bvPgAz8>$iP5x#imaPFG9>EfZuL3^LE6uioP_8!#K z>gzKA035rQ%6@@9u=h{C>>~B&12^tAd#@XZOxR$3h4Ae3ddnGsf>#d_`3k+gF2=@l z=pRf%MH*`b4^oPDo>++la(#WI!YCJ%8t)s+?=K!JJodt%bG5P5nB=70hfvinp96%A zFm0`WaPJqtY$^K>NAA5I>EXUO8JREd^^Wjh%?JVo(!id5-Pm_sFNTIQa9jz?HUJP* zq%d=~i2wY~3FweF@`VUQ0vBGL#KkkGBA*%${cP%={#h6fY31?jNn!#(@7?u<&!j2y z_e1wwi_AzrSn11qoi~z{HG>Ek8O`9WHw|KRM;6o$e})i*gd=0GHy>*&9=`Ua=U^BD z)chPVSV|M;PP{@(=gw4Xy7H0#nfcaZEkCEs5mdvu4Fhu4E);{a=&{DI63pP;J07|t zYxn+S;D(7z&(&kE0lvjDhzqL*3_)OKc(o8wpumb@ng&cuAf2&r*E_~=!_9*bV#9Zj zKYSX$cyt<$YjSHpFhD|J`q*JwUYtF;)R_6`;fp_+{M_|F$KSkk4=hLoh~gY*s5R{C zeg|f&hjGjBM}~Xl_P@*T9Gd9czYBD^L*h6wqfox3p(6${;Mfw$l!;8v zMmlXFm$T8`+^G@I+`~LI}c>KMQJFnlK+qG>o+ce``D~6N;Z-3Vg z3=OA|N?WijBQCrI5C|j)f&db~zR;gNxxx>B^91VE0D`EacJPLVVPJ0R1kFvKcqTNw zk3T*8Z|6)gfl!}@1b{W~1g;-`zZ-hN{rT))g6Q?D z#?_@d$|WBSFN9Z*(5Qs`>dOTzU#P+Nn&5Sdf;+)82m*cm!ze7w-WK}N-2l$rY!2f& z|1_i_dZBayd(+pCvaf$9yJHYm+J%nPCWdK+jx-4Av~@-BUOmL(#VXF6SjEiQA}-F9 zP_KtDrHDZS5Fn-&-2u&4t>4)ohBmvgf^Da`r+>t#&6GZL-}r-1JT?8z^CmfvN*6HJ zcdJ<{uiZGXeQe0>&Bm?n6Zy0t8}W;wyN4EAWoznl~$2X_%;#*Nl3-v=phv z6Xz?hHjJL+m>5o7eO)@0|5W$x;f$4Znv!92p=!ry^O%JoEMKhR-05|s(iXb=To4g@ z`dnQ5)`3_uFi!065#c1+~azb!?q z8I^S(jampWx9M`TgEsbOYyscO0}xac(0XZ6`^x{Brz#hu2}zc;lSaY<8KUD0uW{TR zOktY_bfj_Qz(q{HRKU<^hId_+$LOvujO@7S|$h-7cbrMMQ~gpw>B-zUdNJ9{9B>C2+ zsH2M`O)!XHk_k!DHAp74RwNZkg9#}T#^x__bMA4umXH~ZS_IoR@a_-q#3j6v5CG3(A%Ot*GEw21$WHfpv{l1Ss7eg=|eEW+0QXaLb)T_>KEUVn(@KQ>Gby zJ;KUD6|03h&c3q3r;iqBX|BQvN5Xa_Qe8H*-a?z5$=n8yCTN0SK!6}LptXX63Kss4@SmwSM+$b9N_FLE0{dI#B*oXu{d9WWg190lDqp| zux{2d#apPkqvEzgMg!ou$sr983kV2kr4Z>bP%5ko2rWqn9Yl><*zlku1w)!!xH)b} z-uu8F3||R615=Z$eBkj}Or2PPUymTAKvzD|CIGE?Gq-Olj?E^YV~-2H5iSJLUMB|p z(1X%yDS|GB96<)!=TP~pDhd~anm1tD_6CjWm?5_9OkWW^GcY~5#{c#0lUP}(K^g=* zA)74K5@Q=3+a@+^Q+5nafIw~V4539F=xw6_uTg>0;rWL0mWqBsi$eVzSEAy47=~xO zV!1<$0L&V!8C`kjieaOb#5mi!qQidT>Qxt05+uP!eZrwx^D13KWNn7>t?fE$y|=1 zI(k#Zn4c;_MU3s^Su%``hMWjU=WI;u@5TPNZO7F&4^a1j3p*i+K+u6gP>2>B!!tPh$_l^z!xPj$l!DXIpbJ6B^*YG)I@oo6 zFTI}OaTI9ONoAkEPpBhqNb)kv_Kb^tS`6^gx@;wDZ5`;nRhW@o4 zF`dQ@zc2`iq=~Kpm&R`BrW@`bqV>5tR?pSiAr|L}z;y8>O_wK+N+G{GUp`StS+^rl z^PnVdMh!2j3BirCr#5{X+ z73W_r;_R~ptk2b9#AgWu2Er)7@wG!-i)xR(T08b#05H=oP^lo|W6ilxdJJoo8OY{n z{jQPUe^1ux`S;O1*E+oeL#&nBG9~C_nT>8q1saWd2(K6*oprEle-CzD-%A5KvgjE~ zBG>OingS|foIAY2QwLX2TJTUjUq^M>2P?pUfB^zDL6XGL!gKU$`N(V~D1G4A;(s3* zG8+!u^ZA`)}oRx(iyVW)xkjRCTBj_!>QfF0Q?8JNDf& zNZs39SgwR=8c?Ce-0^kHzO>5IFRq}v;sc5h)#KSIv1k@BYF`#ioG!gWFRuK8L*4l3 z3l|>w-W$Eelz7+pAN;im^HW#da2va+9JJCK6|~s`CR`H}H}uiOTYBN7EUYioxwufr z+JzdeFVwj(T}4pWux$xxh*(hq?IH!HW{n__0t5_57?`e{qUV+$2Z+A-+}scT^3pp1 zuVH{BqKFx=DEZEz&wR}y^TDx+{mADBV-4AwRyS|n`CbI=Ymf${VL%E4(jXWlAVeGz zEoy|OHqUobWn8QbD5Y_#^b)iv*)wzB|B@}cg&(bI2?r6p*S!4 z^t0zaY*Xrup%B@;O(seU1glwM#fnZAs$2MUnELfI=DFqBN5DVZ|Cl&L#xD0qh z8!*^FH7L`u)#vF{`Q@t5{$~%&|Hoe?WRLKpRShJd(fIYZm>58s(kSU7Op>HoN7Kb4 z&-bTxpK4UAH`S`^Inzv#EfX+>8EbVN3o+6gYEtNpYATw==#q5^SZpFtJ{Icd=uqKl zzTnLqYUtX7htB`_M*tA%Is*ydGT^Uxk1Rj{34sWzwWQ|CuN)u9OgtSl8kzO=#j!B( zY+@o?I%GkDAYedrfbO`ofM5aHJd=3cKqIQtLT#2#7LU?P>o2T&y8d-9D*w@ug~yI; z`18u(-_VWjRubK`{X;oNIQK&9k0zbW?HM=UpK-d8b@E7AIhev^1I_ShvI{i8T7$Jl zr~)cA*09=G!g{bAEqe>gbya&bDN{c + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lisp_eval/janet.png b/lisp_eval/janet.png new file mode 100644 index 0000000000000000000000000000000000000000..ca4cb6c67911af77c317f2086aafce9a37a8b474 GIT binary patch literal 20664 zcmb??g;x~s_y5f9(%lV9Be`_L(w!nmcPUCL2m(WhNC*fhA}x{^?J?R``q)KJI}rI!qixYjF_1i001&QT}^WUfZbfeND1LL zrv|sR`kTRtsiB3|%@7#I0Ym@aj*36H3QgcfaIxa~IP$p+Fk2ADFpiU(!Oi48p2_x~OmWFj1Zo`gHRf0ges}VfN5ws*N+UM?dE7whS>@A4^Pjki5Hp)aoY)UsRY=K` zbzGN^Y(Hd@uL@L}2_|geD(XH|-I1vn{Wq0yFKY)^zWS%a*-2m$SCz6_5jR_2lCfBm zTHzC&vx7_D#<5S};&u3l#!=Pe*5J%a`!f^3+Eaw%iq~m!E^elMR(Vv`ZJ=7pZ7#=<_o7lA)Xo2=+92q;UrZQM49>-vJL{8{{?DYp%>>#N6Qdptin*sJ)t{{D$r zBQN`>8C>(iQpMK%_lmihirVr9>1-eUPCbP}3$BXgreS;DuF<~A>DYHJ{uST7KfFNw zoEV-f&o0fAcq`0367KrCP@!@@vvSn7wlK|+q0_52MSAbt4x^`L@U?&XG&5s zL!4zSQH}ZUDkmR*PB)2;;f#Be)|+ZsRw8NYhuYX3!3@QPM+JM^X@4yLT3+wr_fmRd zqGe*KpyGK7I~R+#G_@KRarV2r`{@BCH<8a0WNv&Lz$4){UHN*JwM$<-taEkPTqQo6 zq-5vo4uxcmmbC(a(H%WaHH(nB-MKF>i%e+&r!@{udQDTF(iw(=y1J+V2BbJMb2`}z z8U2qrUXqz#c|NsAr?iYbd>s6E`oQ7xi`}0S;?0F40=q|BMpnBG;f)V%>y~xr(ui~t z9`ne@`OH3=@(tE(Di0(T2`L;uxHFhaD}YWTHTb7UR`^Yv_p7XOhvM64Px zdkfwW;{HEU@*8i1Ofs?MJX5Y3oq>~fjxtd|$?oS}yM3p&vU_OC;o}**Q&i}@IV6`b z^ANh~4u%rIa^d#=)*OTrBoiz6%<=A|_ zYl$`6_@+I?pP)=|&c*6iKj`{U6q4c5ne6^c;3d{vKle96D<0pDFF|%N!cX>UePczq z*6wAe`4MY#kCe4%C){nvjrdKf)=RaDN1{ZypseEtX<-8YjL%)NMPagg%{g zh%w!aT&vHA`_yV84tBWAg&cB`dOJx&$Ksl~Mde!Fw1(keQ&d+~w_vJBv z;F;H!yWS8l>+7`xocNL^q;=owSZ0eEayN|FRQ=Gz4yS*gZ3VWw!(L_;#F&_+gHC2b z%@k5R7)m~C(|d_M2vYN@>h87#x@$94fiLol-k9o1RQ$io_hfj>k<&|>9|@lpPlF;h zZvqY#tC|vkDu*;Kq&`GjB|wZD%|ya@s)*mH{lqU$eZ7JU<|drwfu^pyIiKBdIi5j9 z(sJ2TPiZKJii31IxLBfy1Vj=jVxmC-_oWN7I&3S4?>9^Oi@g`kP

<={P` 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 + + + + + + + + From b5791b465eff88391e7471a4deb113f01b3a7b2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=B6rmet=20Yiltiz?= Date: Tue, 4 Feb 2025 01:09:03 -0600 Subject: [PATCH 03/11] cuttom setting works! --- lisp_eval/__init__.py | 54 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/lisp_eval/__init__.py b/lisp_eval/__init__.py index 6e0d44d..c4df9fa 100644 --- a/lisp_eval/__init__.py +++ b/lisp_eval/__init__.py @@ -1,6 +1,10 @@ # -*- coding: utf-8 -*- # Copyright (c) 2025 Hormet Yiltiz +""" +Evaluates an S-Expression using an available Lisp language. Choose from the detected interpreters. +""" + from builtins import pow from pathlib import Path import subprocess @@ -69,14 +73,23 @@ def __init__(self): result = proc.stdout.strip() if result: detected_langs.append(lang) - break # TODO: unless we provide alternatives in drop-down menu, do not bother detecting the rest + pass # break DONE: unless we provide alternatives in drop-down menu, do not bother detecting the rest except FileNotFoundError as ex: warning(str(ex)) continue PluginInstance.__init__(self) self.detected_langs = detected_langs - self.call_external = lang_opts[detected_langs[0]] + + self._detected_langs = self.readConfig('detected_langs', list[str]) + if self._detected_langs is None: + self._detected_langs = detected_langs + + self._lang = self.readConfig('lang', str) + if self._lang is None: + self._lang = detected_langs[0] + + self.call_external = self.lang_opts[self._lang] self.iconUrls = [f"file:{Path(__file__).parent}/{self.call_external['url']}"] TriggerQueryHandler.__init__( self, @@ -87,6 +100,43 @@ def __init__(self): defaultTrigger="() ", ) + + @property + def detected_langs(self): + return self._detected_langs + + @detected_langs.setter + def detected_langs(self, value): + self._detected_langs = value + self.writeConfig('detected_langs', value) + + @property + def lang(self): + return self._lang + + @lang.setter + def lang(self, value): + self._lang = value + print('Setting lang to', self.lang) + print('Setting _lang to', self._lang) + self.writeConfig('lang', value) + self.call_external = self.lang_opts[self._lang] + self.iconUrls = [f"file:{Path(__file__).parent}/{self.call_external['url']}"] + + def configWidget(self): + return [ + { + 'type': 'label', + 'text': __doc__.strip(), + }, + { + 'type': 'combobox', + 'property': 'lang', + 'label': 'Lisp Interpreter', + 'items': self._detected_langs, + }, + ] + def runSubprocess(self, query_script): try: script = self.call_external["args"][-1].format(query_script) From 6ffc7a46740ab6919ff1d67ba2ff1f47ad55e23d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=B6rmet=20Yiltiz?= Date: Tue, 4 Feb 2025 01:25:43 -0600 Subject: [PATCH 04/11] all works --- lisp_eval/__init__.py | 41 ++++++++--------------------------------- lisp_eval/config.json | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 33 deletions(-) create mode 100644 lisp_eval/config.json diff --git a/lisp_eval/__init__.py b/lisp_eval/__init__.py index c4df9fa..98c2478 100644 --- a/lisp_eval/__init__.py +++ b/lisp_eval/__init__.py @@ -5,8 +5,8 @@ Evaluates an S-Expression using an available Lisp language. Choose from the detected interpreters. """ -from builtins import pow from pathlib import Path +import json import subprocess from albert import * @@ -19,44 +19,19 @@ md_url = "https://github.com/albertlauncher/python/tree/main/lisp_eval/" md_authors = "@hyiltiz" +_config_file = 'config.yaml' +_config_file = 'config.json' class Plugin(PluginInstance, TriggerQueryHandler): def __init__(self): # search for a language supporting S-Exp: fennel, janet, elisp, clojure, racket # TODO: this should be a configurable option # Users should make available the executables in the system PATH - lang_opts = { - "elisp": { - "prog": "emacs", - "args": ["--batch", "--eval", "(print {})"], - "url": "emacs-small.png", - }, - "elisp": { - "prog": "Emacs", - "args": ["--batch", "--eval", "(print {})"], - "url": "emacs-small.png", - }, - "fennel": { - "prog": "fennel", - "args": ["-e", "(print {})"], - "url": "fennel.svg", - }, - "janet": { - "prog": "janet", - "args": ["-e", "(print {})"], - "url": "janet.png", - }, - "hylang": { - "prog": "hy", # this is a pip3 dependency: `hy` - "args": ["-c", "(print {})"], - "url": "cuddles.png", - }, - "racket": { - "prog": "racket", - "args": ["-e", "(print {})"], - "url": "racket.svg", - }, - } + + lang_opts = json.load((Path(__file__).parent / Path(_config_file)).open()) + print('---------------------------') + print(type(lang_opts)) + print(lang_opts) self.lang_opts = lang_opts test_sexp = "(+ 1 1)" diff --git a/lisp_eval/config.json b/lisp_eval/config.json new file mode 100644 index 0000000..f2c9fe3 --- /dev/null +++ b/lisp_eval/config.json @@ -0,0 +1,43 @@ +{ + "elisp": { + "prog": "Emacs", + "args": [ + "--batch", + "--eval", + "(print {})" + ], + "url": "emacs-small.png" + }, + "fennel": { + "prog": "fennel", + "args": [ + "-e", + "(print {})" + ], + "url": "fennel.svg" + }, + "janet": { + "prog": "janet", + "args": [ + "-e", + "(print {})" + ], + "url": "janet.png" + }, + "hylang": { + "prog": "hy", + "args": [ + "-c", + "(print {})" + ], + "url": "cuddles.png" + }, + "racket": { + "prog": "racket", + "args": [ + "-e", + "(print {})" + ], + "url": "racket.svg" + } +} From 9d0d8d5c5ccaac0cc12462951c5561757f63434f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=B6rmet=20Yiltiz?= Date: Tue, 4 Feb 2025 01:40:14 -0600 Subject: [PATCH 05/11] add easter egg --- lisp_eval/__init__.py | 6 +++++- lisp_eval/config.json | 11 +++++++++++ lisp_eval/lambda.svg | 4 ++++ 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 lisp_eval/lambda.svg diff --git a/lisp_eval/__init__.py b/lisp_eval/__init__.py index 98c2478..6860af4 100644 --- a/lisp_eval/__init__.py +++ b/lisp_eval/__init__.py @@ -96,7 +96,11 @@ def lang(self, value): print('Setting _lang to', self._lang) self.writeConfig('lang', value) self.call_external = self.lang_opts[self._lang] - self.iconUrls = [f"file:{Path(__file__).parent}/{self.call_external['url']}"] + icon_fname = Path(__file__).parent / self.call_external['url'] + if icon_fname.exists(): + self.iconUrls = [f"file:{icon_fname}"] + else: + self.iconUrls = [f"file:{Path(__file__).parent}" + "/lambda.svg"] def configWidget(self): return [ diff --git a/lisp_eval/config.json b/lisp_eval/config.json index f2c9fe3..821d775 100644 --- a/lisp_eval/config.json +++ b/lisp_eval/config.json @@ -32,6 +32,17 @@ ], "url": "cuddles.png" }, + "R": { + "prog": "R", + "args": ["--no-restore", + "--no-save", + "--no-readline", + "--quiet", + "--no-echo", + "-e", + "'{}'"], + "url": "r-is-also-a-lisp-shrug.png" + }, "racket": { "prog": "racket", "args": [ diff --git a/lisp_eval/lambda.svg b/lisp_eval/lambda.svg new file mode 100644 index 0000000..891e86d --- /dev/null +++ b/lisp_eval/lambda.svg @@ -0,0 +1,4 @@ + + + + From 1c61d2d4ed8cc928b7bfdf6f393000024ec64c04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=B6rmet=20Yiltiz?= Date: Tue, 4 Feb 2025 17:45:38 -0600 Subject: [PATCH 06/11] use info() and debug(), not print() --- lisp_eval/__init__.py | 153 ++++++++++++++++++++++++++---------------- lisp_eval/config.json | 2 +- 2 files changed, 95 insertions(+), 60 deletions(-) diff --git a/lisp_eval/__init__.py b/lisp_eval/__init__.py index 6860af4..f3f14a7 100644 --- a/lisp_eval/__init__.py +++ b/lisp_eval/__init__.py @@ -3,116 +3,149 @@ """ Evaluates an S-Expression using an available Lisp language. Choose from the detected interpreters. + +The plugin looks for executables defined in the config.json file for a list of languages in the *system PATH*. Please install your preferred language support, then make its executable available in system PATH. For example, using Bash in your terminal: + +pip3 install hy # install hylang interpreter using your preferred package manager +hy --version # ensure it is installed +which -a hy # to see where it is installed to +sudo ln -s $(which hy) /usr/local/bin/ # make it available in system PATH + + +Now quit and restart Albert. In Albert Settings, select hylang as your Lisp Interpreter. """ from pathlib import Path import json import subprocess +from pprint import pprint from albert import * -md_iid = "2.3" -md_version = "1.0" +md_iid = "2.4" +md_version = "2.4" md_name = "S-Exp Eval" md_description = "Evaluate S-Expression via Fennel, Emacs, Janet, Racket or Hylang." md_license = "BSD-3" md_url = "https://github.com/albertlauncher/python/tree/main/lisp_eval/" md_authors = "@hyiltiz" -_config_file = 'config.yaml' -_config_file = 'config.json' +_config_file = "config.json" + class Plugin(PluginInstance, TriggerQueryHandler): + @property + def detected_langs(self): + return self._detected_langs + + @detected_langs.setter + def detected_langs(self, value): + self._detected_langs = value + self.writeConfig("detected_langs", value) + + @property + def lang(self): + return self._lang + + @lang.setter + def lang(self, value): + self._lang = value + debug("Setting config.lang and self._lang: ", self._lang) + self.writeConfig("lang", value) + self.call_external = self.lang_opts[self._lang] + icon_fname = Path(__file__).parent / self.call_external["url"] + if icon_fname.exists(): + self.iconUrls = [f"file:{icon_fname}"] + else: + self.iconUrls = [f"file:{Path(__file__).parent}" + "/lambda.svg"] + def __init__(self): # search for a language supporting S-Exp: fennel, janet, elisp, clojure, racket - # TODO: this should be a configurable option # Users should make available the executables in the system PATH lang_opts = json.load((Path(__file__).parent / Path(_config_file)).open()) - print('---------------------------') - print(type(lang_opts)) - print(lang_opts) + debug("-----: lisp_eval init: ---------") + debug(str(type(lang_opts))) self.lang_opts = lang_opts - test_sexp = "(+ 1 1)" detected_langs = [] for lang, args in lang_opts.items(): + if lang.lower() == 'r': + # R is secretly a Lisp too! + test_sexp = "`+`(1, 1)" + else: + test_sexp = "(+ 1 1)" script = args["args"][-1].format(test_sexp) - print(f"Testing {lang} with test script {script}") + debug(f"Testing {lang} with:") + debug(' '.join([args["prog"], *args["args"][0:-1], script])) try: proc = subprocess.run( [args["prog"], *args["args"][0:-1], script], input=script.encode(), stdout=subprocess.PIPE, + capture_output=False, + check=False, ) + debug(str(proc)) result = proc.stdout.strip() - if result: + + if (proc.stderr is None + and result is not None + and result == b'2' + and proc.returncode == 0): + debug(f'Adding : {lang}') detected_langs.append(lang) - pass # break DONE: unless we provide alternatives in drop-down menu, do not bother detecting the rest except FileNotFoundError as ex: warning(str(ex)) continue + debug('\n') + PluginInstance.__init__(self) self.detected_langs = detected_langs - self._detected_langs = self.readConfig('detected_langs', list[str]) - if self._detected_langs is None: - self._detected_langs = detected_langs + self._detected_langs = self.readConfig("detected_langs", list[str]) + if self._detected_langs is None or self._detected_langs != detected_langs: + self.detected_langs = detected_langs + self.writeConfig("detected_langs", detected_langs) - self._lang = self.readConfig('lang', str) + self._lang = self.readConfig("lang", str) + debug(f'config.lang = {self._lang}') if self._lang is None: - self._lang = detected_langs[0] + self.lang = detected_langs[0] self.call_external = self.lang_opts[self._lang] - self.iconUrls = [f"file:{Path(__file__).parent}/{self.call_external['url']}"] + icon_fname = Path(__file__).parent / self.call_external["url"] + if icon_fname.exists(): + self.iconUrls = [f"file:{icon_fname}"] + else: + self.iconUrls = [f"file:{Path(__file__).parent}" + "/lambda.svg"] TriggerQueryHandler.__init__( self, self.id, self.name, self.description, - synopsis=f" Date: Tue, 4 Feb 2025 17:53:05 -0600 Subject: [PATCH 07/11] refactor to simplify --- lisp_eval/__init__.py | 181 +++++++++++++++++------------------------- 1 file changed, 74 insertions(+), 107 deletions(-) diff --git a/lisp_eval/__init__.py b/lisp_eval/__init__.py index f3f14a7..8f3e23c 100644 --- a/lisp_eval/__init__.py +++ b/lisp_eval/__init__.py @@ -11,13 +11,13 @@ which -a hy # to see where it is installed to sudo ln -s $(which hy) /usr/local/bin/ # make it available in system PATH - Now quit and restart Albert. In Albert Settings, select hylang as your Lisp Interpreter. """ from pathlib import Path import json import subprocess +from typing import List, Dict, Any, Optional from pprint import pprint from albert import * @@ -30,110 +30,90 @@ md_url = "https://github.com/albertlauncher/python/tree/main/lisp_eval/" md_authors = "@hyiltiz" -_config_file = "config.json" +_CONFIG_FILE = "config.json" class Plugin(PluginInstance, TriggerQueryHandler): - @property - def detected_langs(self): - return self._detected_langs - - @detected_langs.setter - def detected_langs(self, value): - self._detected_langs = value - self.writeConfig("detected_langs", value) - - @property - def lang(self): - return self._lang - - @lang.setter - def lang(self, value): - self._lang = value - debug("Setting config.lang and self._lang: ", self._lang) - self.writeConfig("lang", value) - self.call_external = self.lang_opts[self._lang] - icon_fname = Path(__file__).parent / self.call_external["url"] - if icon_fname.exists(): - self.iconUrls = [f"file:{icon_fname}"] - else: - self.iconUrls = [f"file:{Path(__file__).parent}" + "/lambda.svg"] - def __init__(self): - # search for a language supporting S-Exp: fennel, janet, elisp, clojure, racket - # Users should make available the executables in the system PATH - - lang_opts = json.load((Path(__file__).parent / Path(_config_file)).open()) - debug("-----: lisp_eval init: ---------") - debug(str(type(lang_opts))) - self.lang_opts = lang_opts - - detected_langs = [] - for lang, args in lang_opts.items(): - if lang.lower() == 'r': - # R is secretly a Lisp too! - test_sexp = "`+`(1, 1)" - else: - test_sexp = "(+ 1 1)" + super().__init__() + self.lang_opts: Dict[str, Any] = self._load_lang_options() + self.detected_langs: List[str] = self._detect_languages() + self._lang: Optional[str] = self._initialize_language() + self._initialize_icon() + self._initialize_trigger_query_handler() + self._log_initialization() + + def _load_lang_options(self) -> Dict[str, Any]: + """Load language options from the config file.""" + config_path = Path(__file__).parent / _CONFIG_FILE + with config_path.open() as f: + return json.load(f) + + def _detect_languages(self) -> List[str]: + """Detect available languages that support S-Expressions. + Users should make available the executables in the system PATH. + """ + detected_langs: List[str] = [] + for lang, args in self.lang_opts.items(): + # R is secretly a Lisp too! + test_sexp = "`+`(1, 1)" if lang.lower() == 'r' else "(+ 1 1)" script = args["args"][-1].format(test_sexp) - debug(f"Testing {lang} with:") - debug(' '.join([args["prog"], *args["args"][0:-1], script])) + debug(f"Testing {lang} with: {' '.join([args['prog'], *args['args'][0:-1], script])}") try: proc = subprocess.run( [args["prog"], *args["args"][0:-1], script], input=script.encode(), stdout=subprocess.PIPE, - capture_output=False, + stderr=subprocess.PIPE, check=False, ) - debug(str(proc)) result = proc.stdout.strip() - - if (proc.stderr is None - and result is not None - and result == b'2' - and proc.returncode == 0): - debug(f'Adding : {lang}') + if proc.returncode == 0 and result == b'2': detected_langs.append(lang) except FileNotFoundError as ex: - warning(str(ex)) - continue - - debug('\n') - - PluginInstance.__init__(self) - self.detected_langs = detected_langs - - self._detected_langs = self.readConfig("detected_langs", list[str]) - if self._detected_langs is None or self._detected_langs != detected_langs: - self.detected_langs = detected_langs - self.writeConfig("detected_langs", detected_langs) - - self._lang = self.readConfig("lang", str) - debug(f'config.lang = {self._lang}') - if self._lang is None: - self.lang = detected_langs[0] - - self.call_external = self.lang_opts[self._lang] - icon_fname = Path(__file__).parent / self.call_external["url"] - if icon_fname.exists(): - self.iconUrls = [f"file:{icon_fname}"] - else: - self.iconUrls = [f"file:{Path(__file__).parent}" + "/lambda.svg"] + warning(f"Language {lang} not found: {ex}") + return detected_langs + + def _initialize_language(self) -> str: + """Initialize the language to be used for evaluation.""" + lang = self.readConfig("lang", str) + if lang is None or lang not in self.detected_langs: + lang = self.detected_langs[0] + self.writeConfig("lang", lang) + return lang + + def _initialize_icon(self): + """Initialize the icon for the plugin.""" + icon_fname = Path(__file__).parent / self.lang_opts[self._lang]["url"] + self.iconUrls = [f"file:{icon_fname}"] if icon_fname.exists() else [f"file:{Path(__file__).parent}/lambda.svg"] + + def _initialize_trigger_query_handler(self): + """Initialize the trigger query handler.""" TriggerQueryHandler.__init__( self, self.id, self.name, self.description, - synopsis=f" str: + return self._lang + + @lang.setter + def lang(self, value: str): + self._lang = value + self.writeConfig("lang", value) + self._initialize_icon() def configWidget(self): return [ @@ -145,60 +125,47 @@ def configWidget(self): "type": "combobox", "property": "lang", "label": "Lisp Interpreter", - "items": self._detected_langs, + "items": self.detected_langs, }, ] - def runSubprocess(self, query_script): + def runSubprocess(self, query_script: str) -> str: + """Run the subprocess to evaluate the S-Expression.""" try: - script = self.call_external["args"][-1].format(query_script) + script = self.lang_opts[self._lang]["args"][-1].format(query_script) proc = subprocess.run( - [ - self.call_external["prog"], - *self.call_external["args"][0:-1], - script, - ], + [self.lang_opts[self._lang]["prog"], *self.lang_opts[self._lang]["args"][0:-1], script], input=script.encode(), stdout=subprocess.PIPE, stderr=subprocess.PIPE, - capture_output=False, check=False, ) - debug(f"---------- :runSubprocess: ----------------------------------------------------") - debug(str(proc)) - debug("\n\n") result = ( proc.stderr.decode("utf-8", errors="replace").strip() + proc.stdout.decode("utf-8", errors="replace").strip() ) except Exception as ex: - critical(f"Python Subprocess call exception: {str(ex)}") + critical(f"Python Subprocess call exception: {ex}") result = str(ex) return result def handleTriggerQuery(self, query): stripped = query.string.strip() if stripped: - try: - result = self.runSubprocess(stripped) - except Exception as ex: - result = ex - - result_str = result - + result = self.runSubprocess(stripped) query.add( StandardItem( id=self.id, - text=result_str, + text=result, subtext=stripped, - inputActionText=self.call_external["prog"] + ": " + result_str, + inputActionText=f"{self.lang_opts[self._lang]['prog']}: {result}", iconUrls=self.iconUrls, actions=[ Action( "copy", "Copy result to clipboard", - lambda r=result_str: setClipboardText(r), + lambda r=result: setClipboardText(r), ), ], ) - ) + ) \ No newline at end of file From 2fd43d0f9864efe2a1d7a4f08a8c91c8615968ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=B6rmet=20Yiltiz?= Date: Tue, 4 Feb 2025 17:55:35 -0600 Subject: [PATCH 08/11] ready for PR --- lisp_eval/__init__.py | 66 +++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 37 deletions(-) diff --git a/lisp_eval/__init__.py b/lisp_eval/__init__.py index 8f3e23c..396a43e 100644 --- a/lisp_eval/__init__.py +++ b/lisp_eval/__init__.py @@ -18,7 +18,6 @@ import json import subprocess from typing import List, Dict, Any, Optional -from pprint import pprint from albert import * @@ -49,29 +48,31 @@ def _load_lang_options(self) -> Dict[str, Any]: with config_path.open() as f: return json.load(f) + def _run_subprocess(self, lang: str, script: str) -> Optional[subprocess.CompletedProcess]: + """Run a subprocess for the given language and script.""" + args = self.lang_opts[lang] + try: + return subprocess.run( + [args["prog"], *args["args"][0:-1], args["args"][-1].format(script)], + input=script.encode(), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=False, + ) + except FileNotFoundError as ex: + warning(f"Language {lang} not found: {ex}") + return None + def _detect_languages(self) -> List[str]: - """Detect available languages that support S-Expressions. - Users should make available the executables in the system PATH. - """ + """Detect available languages that support S-Expressions.""" detected_langs: List[str] = [] - for lang, args in self.lang_opts.items(): - # R is secretly a Lisp too! + for lang in self.lang_opts: test_sexp = "`+`(1, 1)" if lang.lower() == 'r' else "(+ 1 1)" - script = args["args"][-1].format(test_sexp) - debug(f"Testing {lang} with: {' '.join([args['prog'], *args['args'][0:-1], script])}") - try: - proc = subprocess.run( - [args["prog"], *args["args"][0:-1], script], - input=script.encode(), - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - check=False, - ) - result = proc.stdout.strip() - if proc.returncode == 0 and result == b'2': - detected_langs.append(lang) - except FileNotFoundError as ex: - warning(f"Language {lang} not found: {ex}") + debug(f"Testing {lang} with: {test_sexp}") + proc = self._run_subprocess(lang, test_sexp) + if proc and proc.returncode == 0 and proc.stdout.strip() == b'2': + detected_langs.append(lang) + debug(f"Confirmed working {lang} installation.") return detected_langs def _initialize_language(self) -> str: @@ -131,22 +132,13 @@ def configWidget(self): def runSubprocess(self, query_script: str) -> str: """Run the subprocess to evaluate the S-Expression.""" - try: - script = self.lang_opts[self._lang]["args"][-1].format(query_script) - proc = subprocess.run( - [self.lang_opts[self._lang]["prog"], *self.lang_opts[self._lang]["args"][0:-1], script], - input=script.encode(), - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - check=False, - ) - result = ( - proc.stderr.decode("utf-8", errors="replace").strip() - + proc.stdout.decode("utf-8", errors="replace").strip() - ) - except Exception as ex: - critical(f"Python Subprocess call exception: {ex}") - result = str(ex) + proc = self._run_subprocess(self._lang, query_script) + if proc is None: + return f"Error: Language {self._lang} not found." + result = ( + proc.stderr.decode("utf-8", errors="replace").strip() + + proc.stdout.decode("utf-8", errors="replace").strip() + ) return result def handleTriggerQuery(self, query): From aa82e0bc60ffd7b27c56eb0815a276b079ffd2bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=B6rmet=20Yiltiz?= Date: Tue, 4 Feb 2025 18:03:27 -0600 Subject: [PATCH 09/11] do not show errors twice --- lisp_eval/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp_eval/__init__.py b/lisp_eval/__init__.py index 396a43e..8eb4df9 100644 --- a/lisp_eval/__init__.py +++ b/lisp_eval/__init__.py @@ -150,7 +150,7 @@ def handleTriggerQuery(self, query): id=self.id, text=result, subtext=stripped, - inputActionText=f"{self.lang_opts[self._lang]['prog']}: {result}", + inputActionText=f"{self.lang_opts[self._lang]['prog']}", iconUrls=self.iconUrls, actions=[ Action( From b4b34096b7ee59d8f29215a6f6cc6e28defe44bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=B6rmet=20Yiltiz?= Date: Tue, 4 Feb 2025 20:13:47 -0600 Subject: [PATCH 10/11] adopt MIT license --- lisp_eval/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp_eval/__init__.py b/lisp_eval/__init__.py index 8eb4df9..7fcf307 100644 --- a/lisp_eval/__init__.py +++ b/lisp_eval/__init__.py @@ -25,7 +25,7 @@ md_version = "2.4" md_name = "S-Exp Eval" md_description = "Evaluate S-Expression via Fennel, Emacs, Janet, Racket or Hylang." -md_license = "BSD-3" +md_license = "MIT" md_url = "https://github.com/albertlauncher/python/tree/main/lisp_eval/" md_authors = "@hyiltiz" From 52fc01f3da784059a8ebf9b23d59b3e3791a4386 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=B6rmet=20Yiltiz?= Date: Wed, 30 Apr 2025 18:02:25 -0500 Subject: [PATCH 11/11] reformat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hörmet Yiltiz --- lisp_eval/__init__.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/lisp_eval/__init__.py b/lisp_eval/__init__.py index 7fcf307..4cd6220 100644 --- a/lisp_eval/__init__.py +++ b/lisp_eval/__init__.py @@ -21,7 +21,7 @@ from albert import * -md_iid = "2.4" +md_iid = "2.3" md_version = "2.4" md_name = "S-Exp Eval" md_description = "Evaluate S-Expression via Fennel, Emacs, Janet, Racket or Hylang." @@ -48,7 +48,9 @@ def _load_lang_options(self) -> Dict[str, Any]: with config_path.open() as f: return json.load(f) - def _run_subprocess(self, lang: str, script: str) -> Optional[subprocess.CompletedProcess]: + def _run_subprocess( + self, lang: str, script: str + ) -> Optional[subprocess.CompletedProcess]: """Run a subprocess for the given language and script.""" args = self.lang_opts[lang] try: @@ -67,10 +69,10 @@ def _detect_languages(self) -> List[str]: """Detect available languages that support S-Expressions.""" detected_langs: List[str] = [] for lang in self.lang_opts: - test_sexp = "`+`(1, 1)" if lang.lower() == 'r' else "(+ 1 1)" + test_sexp = "`+`(1, 1)" if lang.lower() == "r" else "(+ 1 1)" debug(f"Testing {lang} with: {test_sexp}") proc = self._run_subprocess(lang, test_sexp) - if proc and proc.returncode == 0 and proc.stdout.strip() == b'2': + if proc and proc.returncode == 0 and proc.stdout.strip() == b"2": detected_langs.append(lang) debug(f"Confirmed working {lang} installation.") return detected_langs @@ -86,7 +88,11 @@ def _initialize_language(self) -> str: def _initialize_icon(self): """Initialize the icon for the plugin.""" icon_fname = Path(__file__).parent / self.lang_opts[self._lang]["url"] - self.iconUrls = [f"file:{icon_fname}"] if icon_fname.exists() else [f"file:{Path(__file__).parent}/lambda.svg"] + self.iconUrls = ( + [f"file:{icon_fname}"] + if icon_fname.exists() + else [f"file:{Path(__file__).parent}/lambda.svg"] + ) def _initialize_trigger_query_handler(self): """Initialize the trigger query handler.""" @@ -160,4 +166,4 @@ def handleTriggerQuery(self, query): ), ], ) - ) \ No newline at end of file + )