1+ # -*- coding: utf-8 -*-
2+ # Copyright (c) 2025 Hormet Yiltiz
3+
4+ from builtins import pow
5+ from pathlib import Path
6+ import subprocess
7+
8+ from albert import *
9+
10+ md_iid = '2.3'
11+ md_version = "1.6"
12+ md_name = "S-Exp Eval"
13+ md_description = "Evaluate S-Expression via Fennel or Emacs"
14+ md_license = "BSD-3"
15+ md_url = "https://github.com/albertlauncher/python/tree/main/fennel_eval"
16+ md_authors = "@manuelschneid3r"
17+
18+ class Plugin (PluginInstance , TriggerQueryHandler ):
19+
20+ def __init__ (self ):
21+ # search for a language supporting S-Exp: fennel, janet, elisp, clojure, racket
22+ # TODO: this should be a configurable option
23+ # Users should make available the executables in the system PATH
24+ lang_opts = {
25+ 'janet' : {
26+ 'prog' : 'janet' ,
27+ 'args' : ['-e' , '(print {})' ],
28+ 'url' : 'https://janet-lang.org/assets/janet-big.png'
29+ },
30+ 'fennel' : {
31+ 'prog' : 'fennel' ,
32+ 'args' : ['-e' , '(print {})' ],
33+ 'url' : 'https://janet-lang.org/assets/janet-big.png'
34+ },
35+ 'elisp' : {
36+ 'prog' : 'emacs' ,
37+ 'args' : ['--batch' , '--eval' , '(print {})' ],
38+ 'url' : 'https://www.gnu.org/software/emacs/images/emacs.png'
39+ },
40+ 'racket' : {
41+ 'prog' : 'racket' ,
42+ 'args' : ['-e' , '(print {})' ],
43+ 'url' : 'https://racket-lang.org/img/racket-logo.svg'
44+ },
45+ }
46+ self .lang_opts = lang_opts
47+
48+ test_sexp = '(+ 1 1)'
49+ detected_langs = []
50+ for lang , args in lang_opts .items ():
51+ script = args ['args' ][- 1 ].format (test_sexp )
52+ try :
53+ proc = subprocess .run ([args ['prog' ], * args ['args' ][0 :- 1 ], script ], input = script .encode (),
54+ stdout = subprocess .PIPE )
55+ result = proc .stdout .strip ()
56+ if result :
57+ detected_langs .append (lang )
58+ except FileNotFoundError as ex :
59+ # TODO: does Albert has a logger?
60+ continue
61+
62+
63+ PluginInstance .__init__ (self )
64+ TriggerQueryHandler .__init__ (
65+ self , self .id , self .name , self .description ,
66+ synopsis = '<Evaluate Lisp S-Expression>' ,
67+ defaultTrigger = '() '
68+ )
69+ self .detected_langs = detected_langs
70+ self .call_external = lang_opts [detected_langs [0 ]]
71+ self .iconUrls = self .call_external ['url' ]
72+
73+ def handleTriggerQuery (self , query ):
74+ act = lambda script : (
75+ lambda : runDetachedProcess ([
76+ self .call_external ['prog' ],
77+ * self .call_external ['args' ][0 :- 1 ],
78+ self .call_external ['args' ][- 1 ].format (script )]
79+ )
80+ )
81+
82+ stripped = query .string .strip ()
83+ if stripped :
84+ try :
85+ result = act (stripped )
86+
87+ except Exception as ex :
88+ result = ex
89+
90+ result_str = str (result )
91+
92+ query .add (StandardItem (
93+ id = self .id ,
94+ text = result_str ,
95+ subtext = type (result ).__name__ ,
96+ inputActionText = query .trigger + result_str ,
97+ iconUrls = self .iconUrls ,
98+ actions = [
99+ Action ("copy" , "Copy result to clipboard" , lambda r = result_str : setClipboardText (r )),
100+ Action ("exec" , "Evaluate S-Expression" , lambda r = result_str : act (r )),
101+ ]
102+ ))
103+
104+ # if __name__ == '__main__':
105+ # plugin = Plugin()
106+ # plugin.run()
0 commit comments