├── .gitignore ├── .projectile ├── README.org ├── conftest.py ├── jedhy ├── __init__.py ├── api.hy ├── inspection.hy ├── macros.hy └── models.hy ├── setup.py └── tests ├── __init__.py ├── hytest.hy ├── test_inspection.hy └── test_models.hy /.gitignore: -------------------------------------------------------------------------------- 1 | .cache* 2 | build* 3 | dist* 4 | jedhy.egg* 5 | __pycache__* -------------------------------------------------------------------------------- /.projectile: -------------------------------------------------------------------------------- 1 | -.#+ 2 | -*__init__.py 3 | 4 | -.hypothesis 5 | -.cache 6 | -__pycache__ 7 | 8 | -/.git 9 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | * Jedhy 2 | 3 | /Last Hy version verified working for: Hy 0.16.0/ 4 | 5 | ~jedhy~ provides autocompletion, documentation introspection and formatting, and 6 | other tools useful to IDEs for [[https://github.com/hylang/hy][Hy]], a lisp embedded in Python. 7 | 8 | Leveraged in Emacs [[https://github.com/hylang/hy-mode][hy-mode]] IDE which I maintain. 9 | 10 | Install from pypi ~pip install jedhy~. 11 | 12 | * Features 13 | 14 | 1. Completes: 15 | 1. All standard python constructs (builtins, user-defined funcs, modules, etc.) 16 | 2. Hy constructs (macros, shadowed operators, compile table) 17 | 3. All of the above with Hy's mangling rules handled. 18 | 2. Documentation introspection and formatting (eg. for Emacs ~Eldoc mode~). 19 | 3. Annotations (eg. for Emacs ~Company mode~). 20 | 21 | Jedhy is fully tested. The tests folder is the easiest way to see example usage. 22 | 23 | See ~jedhy.api~, the simple entry point. 24 | 25 | ** Example Usage 26 | 27 | #+BEGIN_SRC lisp 28 | (setv jedhy (jedhy.api.API)) 29 | 30 | ;; Add some stuff to namespace 31 | (import [numpy :as np]) 32 | (import itertools) 33 | (defclass Foo [object] "A class\nDetails..." (defn --init-- [self x y])) 34 | (defmacro foo-macro [x]) 35 | 36 | (jedhy.set-namespace :locals- (locals) :macros- --macros--) 37 | 38 | ;; COMPLETION 39 | (jedhy.complete "pr") ; print 40 | (jedhy.complete "print.") ; print.--call--, print.--str--, ... 41 | (jedhy.complete "np.a") ; np.add, np.array, ... 42 | (jedhy.complete "foo-") ; foo-macro 43 | 44 | ;; DOCS 45 | (jedhy.docs "itertools") ; "Functional tools for creating and using iterators." 46 | (jedhy.docs "Foo") ; "Foo: (x y) - A class" 47 | (jedhy.full-docs "Foo") ; "Foo: (x y) - A class\nDetails..." 48 | 49 | ;; ANNOTATION 50 | (jedhy.annotate "itertools") ; 51 | (jedhy.annotate "try") ; 52 | #+END_SRC 53 | -------------------------------------------------------------------------------- /conftest.py: -------------------------------------------------------------------------------- 1 | import hy # This import is mandatory for pytest to load hy modules. 2 | from _pytest.python import Module 3 | 4 | 5 | def pytest_collect_file(path, parent): 6 | if path.ext == ".hy" and "test_" in path.basename: 7 | return Module(path, parent) 8 | -------------------------------------------------------------------------------- /jedhy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekaschalk/jedhy/c2ad5e07afefb9f4f6a59932929d791c3e68faaa/jedhy/__init__.py -------------------------------------------------------------------------------- /jedhy/api.hy: -------------------------------------------------------------------------------- 1 | "Expose jedhy's `API` for IDE and metaprogramming use-cases." 2 | 3 | ;; * Imports 4 | 5 | (require [jedhy.macros [*]]) 6 | (import [jedhy.macros [*]]) 7 | 8 | (import [jedhy.inspection [Inspect]] 9 | [jedhy.models [Candidate 10 | Namespace 11 | Prefix]]) 12 | 13 | ;; * API 14 | 15 | (defclass API [object] 16 | (defn --init-- [self &optional globals- locals- macros-] 17 | (self.set-namespace globals- locals- macros-) 18 | 19 | (setv self.-cached-prefix None)) 20 | 21 | (defn set-namespace [self &optional globals- locals- macros-] 22 | "Rebuild namespace for possibly given `globals-`, `locals-`, and `macros-`. 23 | 24 | Typically, the values passed are: 25 | globals- -> (globals) 26 | locals- -> (locals) 27 | macros- -> --macros--" 28 | (setv self.namespace (Namespace globals- locals- macros-))) 29 | 30 | (defn complete [self prefix-str] 31 | "Completions for a prefix string." 32 | (setv [cached-prefix prefix] [self.-cached-prefix 33 | (Prefix prefix-str :namespace self.namespace)]) 34 | (setv self.-cached-prefix prefix) 35 | 36 | (.complete prefix :cached-prefix cached-prefix)) 37 | 38 | (defn annotate [self candidate-str] 39 | "Annotate a candidate string." 40 | (-> candidate-str 41 | (Candidate :namespace self.namespace) 42 | (.annotate))) 43 | 44 | (defn -inspect [self candidate-str] 45 | "Inspect a candidate string." 46 | (-> candidate-str 47 | (Candidate :namespace self.namespace) 48 | (.get-obj) 49 | Inspect)) 50 | 51 | (defn docs [self candidate-str] 52 | "Docstring for a candidate string." 53 | (-> candidate-str self.-inspect (.docs))) 54 | 55 | (defn full-docs [self candidate-str] 56 | "Full documentation for a candidate string." 57 | (-> candidate-str self.-inspect (.full-docs)))) 58 | -------------------------------------------------------------------------------- /jedhy/inspection.hy: -------------------------------------------------------------------------------- 1 | "Implements argspec inspection and formatting for various types." 2 | 3 | ;; * Imports 4 | 5 | (require [jedhy.macros [*]]) 6 | (import [jedhy.macros [*]]) 7 | 8 | (import inspect 9 | hy) 10 | 11 | ;; * Parameters 12 | 13 | (defclass Parameter [object] 14 | (defn --init-- [self symbol &optional default] 15 | (setv self.symbol (unmangle symbol)) 16 | (setv self.default default)) 17 | 18 | (defn --str-- [self] 19 | (if (none? self.default) 20 | self.symbol 21 | (.format "[{} {}]" self.symbol self.default)))) 22 | 23 | ;; * Signature 24 | 25 | (defclass Signature [object] 26 | (defn --init-- [self func] 27 | (try (setv argspec (inspect.getfullargspec func)) 28 | (except [e TypeError] 29 | (raise (TypeError "Unsupported callable for hy Signature.")))) 30 | 31 | (setv self.func func) 32 | (setv [self.args 33 | self.defaults 34 | self.kwargs 35 | self.varargs 36 | self.varkw] 37 | ((juxt self.-args-from self.-defaults-from self.-kwargs-from 38 | self.-varargs-from self.-varkw-from) 39 | argspec))) 40 | 41 | #@(staticmethod 42 | (defn -parametrize [symbols &optional defaults] 43 | "Construct many Parameter for `symbols` with possibly defaults." 44 | (when symbols 45 | (tuple (map Parameter 46 | symbols 47 | (or defaults (repeat None))))))) 48 | 49 | #@(classmethod 50 | (defn -args-from [cls argspec] 51 | "Extract args without defined defaults from `argspec`." 52 | (setv symbols (drop-last (len (or argspec.defaults [])) 53 | argspec.args)) 54 | 55 | (cls.-parametrize symbols))) 56 | 57 | #@(classmethod 58 | (defn -defaults-from [cls argspec] 59 | "Extract args with defined defaults from `argspec`." 60 | (setv args-without-defaults (cls.-args-from argspec)) 61 | (setv symbols (drop (len args-without-defaults) 62 | argspec.args)) 63 | 64 | (cls.-parametrize symbols argspec.defaults))) 65 | 66 | #@(classmethod 67 | (defn -kwargsonly-from [cls argspec] 68 | "Extract kwargs without defined defaults from `argspec`." 69 | (setv kwargs-with-defaults (.keys (or argspec.kwonlydefaults {}))) 70 | (setv symbols (remove #f(in kwargs-with-defaults) 71 | argspec.kwonlyargs)) 72 | 73 | (cls.-parametrize symbols))) 74 | 75 | #@(classmethod 76 | (defn -kwonlydefaults-from [cls argspec] 77 | "Extract kwargs with defined defaults from `argspec`." 78 | (when argspec.kwonlydefaults 79 | (setv [symbols defaults] (zip #* (.items argspec.kwonlydefaults))) 80 | 81 | (cls.-parametrize symbols defaults)))) 82 | 83 | #@(classmethod 84 | (defn -kwargs-from [cls argspec] 85 | "Chain kwargs with and without defaults, since `argspec` doesn't order." 86 | (->> argspec 87 | ((juxt cls.-kwargsonly-from cls.-kwonlydefaults-from)) 88 | flatten 89 | (remove none?) 90 | tuple))) 91 | 92 | #@(staticmethod 93 | (defn -varargs-from [argspec] 94 | (and argspec.varargs [(unmangle argspec.varargs)]))) 95 | 96 | #@(staticmethod 97 | (defn -varkw-from [argspec] 98 | (and argspec.varkw [(unmangle argspec.varkw)]))) 99 | 100 | #@(staticmethod 101 | (defn -format-args [args opener] 102 | (unless args 103 | (return "")) 104 | 105 | (setv args (map str args)) 106 | (setv opener (if opener (+ opener " ") (str))) 107 | 108 | (+ opener (.join " " args)))) 109 | 110 | #@(classmethod 111 | (defn -acc-lispy-repr [cls acc args-opener] 112 | (setv [args opener] args-opener) 113 | (setv delim (if (and acc args) " " (str))) 114 | 115 | (+ acc delim (cls.-format-args args opener)))) 116 | 117 | #@(property 118 | (defn -arg-opener-pairs [self] 119 | [[self.args None] 120 | [self.defaults "&optional"] 121 | [self.varargs "#*"] 122 | [self.varkw "#**"] 123 | [self.kwargs "&kwonly"]])) 124 | 125 | (defn --str-- [self] 126 | (reduce self.-acc-lispy-repr self.-arg-opener-pairs (str)))) 127 | 128 | ;; * Docstring conversion 129 | 130 | (defn -split-docs [docs] 131 | "Partition docs string into pre/-/post-args strings." 132 | (setv arg-start (inc (.index docs "("))) 133 | (setv arg-end (.index docs ")")) 134 | 135 | [(cut docs 0 arg-start) 136 | (cut docs arg-start arg-end) 137 | (cut docs arg-end)]) 138 | 139 | (defn -argstring-to-param [arg-string] 140 | "Convert an arg string to a Parameter." 141 | (unless (in "=" arg-string) 142 | (return (Parameter arg-string))) 143 | 144 | (setv [arg-name - default] (.partition arg-string "=")) 145 | 146 | (if (= "None" default) 147 | (Parameter arg-name) 148 | (Parameter arg-name default))) 149 | 150 | (defn -optional-arg-idx [args-strings] 151 | "First idx of an arg with a default in list of args strings." 152 | (defn -at-arg-with-default? [idx-arg] 153 | (when (in "=" (second idx-arg)) (first idx-arg))) 154 | 155 | (->> args-strings 156 | enumerate 157 | (map -at-arg-with-default?) 158 | (remove none?) ; Can't use `some` since idx could be zero 159 | first)) 160 | 161 | (defn -insert-optional [args] 162 | "Insert &optional into list of args strings." 163 | (setv optional-idx (-optional-arg-idx args)) 164 | 165 | (unless (none? optional-idx) 166 | (.insert args optional-idx "&optional")) 167 | 168 | args) 169 | 170 | (defn builtin-docs-to-lispy-docs [docs] 171 | "Convert built-in-styled docs string into a lispy-format." 172 | ;; Check if docs is non-standard 173 | (unless (and (in "(" docs) 174 | (in ")" docs)) 175 | (return docs)) 176 | 177 | (setv replacements [["..." "#* args"] 178 | ["*args" "#* args"] 179 | ["**kwargs" "#** kwargs"] 180 | ["\n" "newline"] 181 | ["-->" "- return"]]) 182 | 183 | (setv [pre-args - post-args] (.partition docs "(")) 184 | 185 | ;; Format before args and perform unconditional conversions 186 | (setv [pre-args args post-args] 187 | (->> post-args 188 | (.format "{}: ({}" pre-args) 189 | (reduce (fn [s old-new] 190 | (.replace s (first old-new) (second old-new))) 191 | replacements) 192 | -split-docs)) 193 | 194 | ;; Format and reorder args and reconstruct the string 195 | (+ pre-args 196 | (as-> args args 197 | (.split args ",") 198 | (map str.strip args) 199 | (list args) 200 | (-insert-optional args) 201 | (map (comp str -argstring-to-param) args) 202 | (.join " " args)) 203 | post-args)) 204 | 205 | ;; * Inspect 206 | ;; ** Internal 207 | 208 | (defclass Inspect [object] 209 | (defn --init-- [self obj] 210 | (setv self.obj obj)) 211 | 212 | #@(property 213 | (defn -docs-first-line [self] 214 | (or (and self.obj.--doc-- 215 | (-> self.obj.--doc-- (.splitlines) first)) 216 | ""))) 217 | 218 | #@(property 219 | (defn -docs-rest-lines [self] 220 | (or (and self.obj.--doc-- 221 | (->> self.obj.--doc-- (.splitlines) rest (.join "\n"))) 222 | ""))) 223 | 224 | #@(property 225 | (defn -args-docs-delim [self] 226 | (or (and self.obj.--doc-- 227 | " - ") 228 | ""))) 229 | 230 | (defn -cut-obj-name-maybe [self docs] 231 | (if (or self.class? 232 | self.method-wrapper?) 233 | (-> docs 234 | (.replace "self " "") 235 | (.replace "self" "")) 236 | docs)) 237 | 238 | (defn -cut-method-wrapper-maybe [self docs] 239 | (if self.method-wrapper? 240 | (+ "method-wrapper" 241 | (cut docs (.index docs ":"))) 242 | docs)) 243 | 244 | (defn -format-docs [self docs] 245 | (-> docs 246 | self.-cut-obj-name-maybe 247 | self.-cut-method-wrapper-maybe)) 248 | 249 | ;; ** Properties 250 | 251 | #@(property 252 | (defn obj-name [self] 253 | (unmangle self.obj.--name--))) 254 | 255 | #@(property 256 | (defn lambda? [self] 257 | "Is object a lambda?" 258 | (= self.obj-name ""))) 259 | 260 | #@(property 261 | (defn class? [self] 262 | "Is object a class?" 263 | (inspect.isclass self.obj))) 264 | 265 | #@(property 266 | (defn method-wrapper? [self] 267 | "Is object of type 'method-wrapper'?" 268 | (instance? (type print.--str--) self.obj))) 269 | 270 | #@(property 271 | (defn compile-table? [self] 272 | "Is object a Hy compile table construct?" 273 | (= self.-docs-first-line 274 | "Built-in immutable sequence."))) 275 | 276 | ;; ** Actions 277 | 278 | (defn signature [self] 279 | "Return object's signature if it exists." 280 | (try (Signature self.obj) 281 | (except [e TypeError] None))) 282 | 283 | (defn docs [self] 284 | "Formatted first line docs for object." 285 | (setv signature (self.signature)) 286 | 287 | (self.-format-docs 288 | (cond [(and signature (not self.compile-table?)) 289 | (.format "{name}: ({args}){delim}{docs}" 290 | :name self.obj-name 291 | :args signature 292 | :delim self.-args-docs-delim 293 | :docs self.-docs-first-line)] 294 | [self.compile-table? 295 | "Compile table"] 296 | [True 297 | (builtin-docs-to-lispy-docs self.-docs-first-line)]))) 298 | 299 | (defn full-docs [self] 300 | "Formatted full docs for object." 301 | ;; TODO There are builtins to format the -docs-rest-lines part I should use 302 | (unless self.compile-table? 303 | (if self.-docs-rest-lines 304 | (.format "{}\n\n{}" 305 | (self.docs) 306 | self.-docs-rest-lines) 307 | (self.docs))))) 308 | -------------------------------------------------------------------------------- /jedhy/macros.hy: -------------------------------------------------------------------------------- 1 | "Some general purpose imports and code." 2 | 3 | ;; * Imports 4 | 5 | (require [hy.extra.anaphoric [*]]) 6 | 7 | (import functools 8 | [toolz.curried :as tz] 9 | 10 | [hy.lex [unmangle 11 | mangle :as unfixed-mangle]]) 12 | 13 | ;; * Hy Overwrites 14 | 15 | ;; We have different requirements in the empty string case 16 | (defn mangle [s] 17 | (if (!= s "") (unfixed-mangle s) (str))) 18 | 19 | ;; * Tag Macros 20 | 21 | (deftag t [form] 22 | "Cast evaluated form to a tuple. Useful via eg. #t(-> x f1 f2 ...)." 23 | `(tuple ~form)) 24 | 25 | (deftag $ [form] 26 | "Partially apply a form eg. (#$(map inc) [1 2 3])." 27 | `(functools.partial ~@form)) 28 | 29 | (deftag f [form] 30 | "Flipped #$." 31 | `(tz.flip ~@form)) 32 | 33 | ;; * Misc 34 | 35 | (defn -allkeys [d &kwonly [parents (,)]] 36 | "In-order tuples of keys of nested, variable-length dict." 37 | (if (isinstance d (, list tuple)) 38 | [] 39 | #t(->> d 40 | (tz.keymap (fn [k] (+ parents (, k)))) 41 | dict.items 42 | (*map (fn [k v] 43 | (if (isinstance v dict) 44 | (-allkeys v :parents k) 45 | [k]))) 46 | tz.concat))) 47 | 48 | (defn allkeys [d] 49 | (->> d -allkeys (map last) tuple)) 50 | -------------------------------------------------------------------------------- /jedhy/models.hy: -------------------------------------------------------------------------------- 1 | "Implements Namespace-dependent methods and structures." 2 | 3 | ;; * Imports 4 | 5 | (require [jedhy.macros [*]]) 6 | (import [jedhy.macros [*]]) 7 | (import builtins 8 | 9 | hy 10 | hy.compiler 11 | hy.macros 12 | [hy.compiler [-special-form-compilers :as -compile-table]] 13 | 14 | ;; Below imports populate --macros-- for Namespace 15 | [hy.core.language [*]] 16 | [hy.core.macros [*]]) 17 | 18 | ;; * Fixes 19 | 20 | ;; See this issue for below #1467: https://github.com/hylang/hy/issues/1467 21 | (hy.eval `(import hy.macros)) 22 | (hy.eval `(require [hy.extra.anaphoric [*]])) 23 | ;; Overwrite Hy's mangling 24 | (import [jedhy.macros [mangle]]) 25 | 26 | ;; * Namespace 27 | 28 | (defclass Namespace [object] 29 | (defn --init-- [self &optional globals- locals- macros-] 30 | ;; Components 31 | (setv self.globals (or globals- (globals))) 32 | (setv self.locals (or locals- (locals))) 33 | (setv self.macros (tz.keymap unmangle (or macros- --macros--))) 34 | (setv self.compile-table (self.-collect-compile-table)) 35 | (setv self.shadows (self.-collect-shadows)) 36 | 37 | ;; Collected 38 | (setv self.names (self.-collect-names))) 39 | 40 | #@(staticmethod 41 | (defn -to-names [key] 42 | "Function for converting keys (strs, functions, modules...) to names." 43 | (unmangle (if (instance? str key) 44 | key 45 | key.--name--)))) 46 | 47 | (defn -collect-compile-table [self] 48 | "Collect compile table as dict." 49 | (->> -compile-table 50 | (tz.keymap self.-to-names))) 51 | 52 | (defn -collect-shadows [self] 53 | "Collect shadows as a list, purely for annotation checks." 54 | (->> hy.core.shadow 55 | dir 56 | (map self.-to-names) 57 | tuple)) 58 | 59 | (defn -collect-names [self] 60 | "Collect all names from all places." 61 | (->> 62 | (chain (allkeys self.globals) 63 | (allkeys self.locals) 64 | (.keys self.macros) 65 | (.keys self.compile-table)) 66 | (map self.-to-names) 67 | distinct 68 | tuple)) 69 | 70 | (defn eval [self mangled-symbol] 71 | "Evaluate `mangled-symbol' within the Namespace." 72 | ;; Short circuit a common case (completing without "." present at all) 73 | (when (not mangled-symbol) 74 | (return None)) 75 | 76 | (setv hy-tree (read-str mangled-symbol)) 77 | 78 | (try (hy.eval hy-tree :locals self.globals) 79 | (except [e NameError] 80 | (try (hy.eval hy-tree :locals self.locals) 81 | (except [] None)))))) 82 | 83 | ;; * Candidate 84 | 85 | (defclass Candidate [object] 86 | (defn --init-- [self symbol &optional namespace] 87 | (setv self.symbol (unmangle symbol)) 88 | (setv self.mangled (mangle symbol)) 89 | (setv self.namespace (or namespace (Namespace)))) 90 | 91 | (defn --str-- [self] 92 | self.symbol) 93 | 94 | (defn --repr-- [self] 95 | (.format "Candidate<(symbol={}>)" self.symbol)) 96 | 97 | (defn --eq-- [self other] 98 | (when (instance? Candidate other) 99 | (= self.symbol other.symbol))) 100 | 101 | (defn --bool-- [self] 102 | (bool self.symbol)) 103 | 104 | (defn compiler? [self] 105 | "Is candidate a compile table construct and return it." 106 | (try (get self.namespace.compile-table self.symbol) 107 | (except [e KeyError] None))) 108 | 109 | (defn macro? [self] 110 | "Is candidate a macro and return it." 111 | (try (get self.namespace.macros self.symbol) 112 | (except [e KeyError] None))) 113 | 114 | (defn evaled? [self] 115 | "Is candidate evaluatable and return it." 116 | (try (.eval self.namespace self.symbol) 117 | (except [e Exception] None))) 118 | 119 | (defn shadow? [self] 120 | "Is candidate a shadowed operator, do *not* return it." 121 | (or (in self.symbol self.namespace.shadows) 122 | None)) 123 | 124 | (defn get-obj [self] 125 | "Get object for underlying candidate." 126 | ;; Compiler *must* come after .evaled to catch objects that are 127 | ;; both shadowed and in the compile table as shadowed (eg. `+`) 128 | (or (self.macro?) 129 | (self.evaled?) 130 | (self.compiler?))) 131 | 132 | (defn attributes [self] 133 | "Return attributes for obj if they exist." 134 | (setv obj (self.evaled?)) 135 | 136 | (when obj 137 | (->> obj dir (map unmangle) tuple))) 138 | 139 | #@(staticmethod 140 | (defn -translate-class [klass] 141 | "Return annotation given a name of a class." 142 | (cond [(in klass ["function" "builtin_function_or_method"]) 143 | "def"] 144 | [(= klass "type") 145 | "class"] 146 | [(= klass "module") 147 | "module"] 148 | [True 149 | "instance"]))) 150 | 151 | (defn annotate [self] 152 | "Return annotation for a candidate." 153 | (setv obj (self.evaled?)) 154 | (setv obj? (not (none? obj))) ; Obj could be instance of bool 155 | 156 | ;; Shadowed takes first priority but compile table takes last priority 157 | (setv annotation (cond [(self.shadow?) 158 | "shadowed"] 159 | 160 | [obj? 161 | (self.-translate-class obj.--class--.--name--)] 162 | 163 | [(.compiler? self) 164 | "compiler"] 165 | 166 | [(.macro? self) 167 | "macro"])) 168 | 169 | (.format "<{} {}>" annotation self))) 170 | 171 | ;; * Prefix 172 | 173 | (defclass Prefix [object] 174 | "A completion candidate." 175 | 176 | (defn --init-- [self prefix &optional namespace] 177 | (setv self.prefix prefix) 178 | (setv self.namespace (or namespace (Namespace))) 179 | 180 | (setv self.candidate (self.-prefix->candidate prefix self.namespace)) 181 | (setv self.attr-prefix (self.-prefix->attr-prefix prefix)) 182 | 183 | (setv self.completions (tuple))) 184 | 185 | (defn --repr-- [self] 186 | (.format "Prefix<(prefix={})>" self.prefix)) 187 | 188 | #@(staticmethod 189 | (defn -prefix->candidate [prefix namespace] 190 | (->> (.split prefix ".") 191 | butlast 192 | (.join ".") 193 | (Candidate :namespace namespace)))) 194 | 195 | #@(staticmethod 196 | (defn -prefix->attr-prefix [prefix] 197 | "Get prefix as str of everything after last dot if a dot is there." 198 | (->> (.split prefix ".") 199 | last 200 | unmangle 201 | ;; TODO since 0.15 below line shouldnt be needed 202 | (#%(if (= %1 "_") "-" %1))))) 203 | 204 | #@(property 205 | (defn has-attr? [self] 206 | "Does prefix reference an attr?" 207 | (in "." self.prefix))) 208 | 209 | #@(property 210 | (defn obj? [self] 211 | "Is the prefix's candidate an object?" 212 | (bool (.get-obj self.candidate)))) 213 | 214 | (defn complete-candidate [self completion] 215 | "Given a potential string `completion`, attach to candidate." 216 | (if self.candidate 217 | (+ (str self.candidate) "." completion) 218 | completion)) 219 | 220 | (defn complete [self &optional cached-prefix] 221 | "Get candidates for a given Prefix." 222 | ;; Short circuit the case: "1+nonsense.real-attr" eg. "foo.--prin" 223 | (when (and self.has-attr? ; The and ordering here matters for speed 224 | (not self.obj?)) 225 | (setv self.completions (tuple)) 226 | (return self.completions)) 227 | 228 | ;; Complete on relevant top-level names or candidate-dependent names 229 | (if (and cached-prefix 230 | (= self.candidate cached-prefix.candidate)) 231 | (setv self.completions cached-prefix.completions) 232 | (setv self.completions (or (.attributes self.candidate) 233 | self.namespace.names))) 234 | 235 | (->> self.completions 236 | (filter #f(str.startswith self.attr-prefix)) 237 | (map self.complete-candidate) 238 | tuple))) 239 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # import hy 2 | from setuptools import find_packages, setup 3 | from setuptools.command.install import install 4 | 5 | __AUTHOR__ = 'Eric Kaschalk' 6 | __AUTHOR_EMAIL__ = 'ekaschalk@gmail.com' 7 | 8 | 9 | setup(name='jedhy', 10 | version=1, 11 | description='Autocompletion and introspection tools for Hy.', 12 | author=__AUTHOR__, 13 | author_email=__AUTHOR_EMAIL__, 14 | maintainer=__AUTHOR__, 15 | maintainer_email=__AUTHOR_EMAIL__, 16 | url='https://github.com/ekaschalk/jedhy', 17 | license='MIT', 18 | keywords='python hy completion introspection refactoring emacs vim', 19 | 20 | packages=["jedhy", "jedhy"], 21 | package_data={ 22 | 'jedhy': ['*.hy', '__pycache__/*'], 23 | }, 24 | 25 | install_requires=["toolz"], 26 | # include_package_data=True, 27 | ) 28 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekaschalk/jedhy/c2ad5e07afefb9f4f6a59932929d791c3e68faaa/tests/__init__.py -------------------------------------------------------------------------------- /tests/hytest.hy: -------------------------------------------------------------------------------- 1 | (import pytest) 2 | 3 | 4 | ;; * Asserts 5 | 6 | (defn assert= [x y] 7 | (assert (= x y))) 8 | 9 | (defn assert-in [x y] 10 | (assert (in x y))) 11 | 12 | (defn assert-not-in [x y] 13 | (assert (not-in x y))) 14 | 15 | (defn assert-all-in [x y] 16 | (assert (->> x (map (fn [z] (in z y))) all))) 17 | 18 | ;; * Pytest Fixtures 19 | 20 | (defmacro deffixture [fn-name docstring params &rest body] 21 | "Pytest parametrize reader." 22 | `(with-decorator 23 | (pytest.fixture :params ~params :scope "module") 24 | (defn ~fn-name [request] 25 | ~docstring 26 | (setv it request.param) 27 | ~@body))) 28 | 29 | (defmacro with-fixture [fixture fn-name args &rest body] 30 | `(defn ~fn-name [~fixture] 31 | (setv ~args ~fixture) 32 | ~@body)) 33 | 34 | (defmacro assert-raises [error &rest code] 35 | `(with [(pytest.raises ~error)] ~@code)) 36 | -------------------------------------------------------------------------------- /tests/test_inspection.hy: -------------------------------------------------------------------------------- 1 | (require [jedhy.macros [*]]) 2 | (import [jedhy.macros [*]]) 3 | (require [tests.hytest [*]]) 4 | (import [tests.hytest [*]]) 5 | 6 | (import [jedhy.inspection [Signature 7 | Inspect 8 | builtin-docs-to-lispy-docs]]) 9 | 10 | ;; * Argspec Extraction 11 | ;; ** Maximal Cases 12 | 13 | (defn test-maximal-argspec [] 14 | (defn func [a b 15 | &optional c [d 0] 16 | &rest args 17 | &kwonly e [f 1] 18 | &kwargs kwargs]) 19 | 20 | (assert= "a b &optional c [d 0] #* args #** kwargs &kwonly e [f 1]" 21 | (-> func Signature str))) 22 | 23 | 24 | (defn test-maximal-argspec-minus-kwonly [] 25 | (defn func [a b 26 | &optional c [d 0] 27 | &rest args 28 | &kwargs kwargs]) 29 | 30 | (assert= "a b &optional c [d 0] #* args #** kwargs" 31 | (-> func Signature str))) 32 | 33 | 34 | (defn test-maximal-argspec-minus-kwargs [] 35 | (defn func [a b 36 | &optional c [d 0] 37 | &rest args 38 | &kwonly e [f 1]]) 39 | 40 | (assert= "a b &optional c [d 0] #* args &kwonly e [f 1]" 41 | (-> func Signature str))) 42 | 43 | 44 | (defn test-maximal-argspec-minus-normal-args [] 45 | (defn func [&optional c [d 0] 46 | &rest args 47 | &kwonly e [f 1] 48 | &kwargs kwargs]) 49 | 50 | (assert= "&optional c [d 0] #* args #** kwargs &kwonly e [f 1]" 51 | (-> func Signature str))) 52 | 53 | ;; ** Optional/Kwonly with/without defaults 54 | 55 | (defn test-optional-without-defaults [] 56 | (defn func [&optional c 57 | &kwonly e [f 1]]) 58 | 59 | (assert= "&optional c &kwonly e [f 1]" 60 | (-> func Signature str))) 61 | 62 | 63 | (defn test-optional-with-only-defaults [] 64 | (defn func [&optional [c 0] [d 0] 65 | &kwonly e [f 1]]) 66 | 67 | (assert= "&optional [c 0] [d 0] &kwonly e [f 1]" 68 | (-> func Signature str))) 69 | 70 | 71 | (defn test-kwonly-without-defaults [] 72 | (defn func [&kwonly f g]) 73 | 74 | (assert= "&kwonly f g" 75 | (-> func Signature str))) 76 | 77 | 78 | (defn test-kwonly-with-only-defaults [] 79 | (defn func [&kwonly [f 1] [g 1]]) 80 | 81 | (assert= "&kwonly [f 1] [g 1]" 82 | (-> func Signature str))) 83 | 84 | ;; ** Trivial cases 85 | 86 | (defn test-no-sig [] 87 | (defn func []) 88 | 89 | (assert= "" 90 | (-> func Signature str))) 91 | 92 | 93 | (defn test-simplest-sig [] 94 | (defn func [a]) 95 | 96 | (assert= "a" 97 | (-> func Signature str))) 98 | 99 | ;; * Builtin Docs to Lispy Formatting 100 | ;; ** Standard Cases 101 | 102 | (defn test-builtin-docstring-conversion-maximal-case [] 103 | (assert= 104 | "print: (value #* args &optional [sep ' '] [end 'newline'] [file sys.stdout] [flush False])" 105 | (builtin-docs-to-lispy-docs 106 | "print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)"))) 107 | 108 | 109 | (defn test-builtin-docstring-conversion-optional-only-with-defaults [] 110 | (assert= 111 | "tee: (iterable &optional [n 2]) - return tuple of n independent iterators." 112 | (builtin-docs-to-lispy-docs 113 | "tee(iterable, n=2) --> tuple of n independent iterators."))) 114 | 115 | 116 | (defn test-builtin-docstring-conversion-optional-only-without-defaults [] 117 | (assert= 118 | "x: (foo &optional bar) - x" 119 | (builtin-docs-to-lispy-docs 120 | "x(foo, bar=None) - x"))) 121 | 122 | 123 | (defn test-builtin-docstring-conversion-no-docs [] 124 | (assert= 125 | "x: (foo &optional bar)" 126 | (builtin-docs-to-lispy-docs 127 | "x(foo, bar=None)"))) 128 | 129 | 130 | (defn test-builtin-docstring-conversion-no-optionals [] 131 | (assert= 132 | "combinations: (iterable r) - return combinations object" 133 | (builtin-docs-to-lispy-docs 134 | "combinations(iterable, r) --> combinations object"))) 135 | 136 | 137 | (defn test-builtin-docstring-conversion-single-optional [] 138 | (assert= 139 | "int: (&optional [x 0]) -> integer" 140 | (builtin-docs-to-lispy-docs 141 | "int(x=0) -> integer"))) 142 | 143 | 144 | (defn test-builtin-docstring-conversion-fails-nonstandard-args-opener [] 145 | (assert= 146 | "foo[ok] bar" 147 | (builtin-docs-to-lispy-docs 148 | "foo[ok] bar"))) 149 | 150 | ;; * Inspection 151 | ;; ** Formatting 152 | 153 | (defn test-inspect-cut-self-maybe [] 154 | (defn a-func-so-dont-trim [x]) 155 | (assert= "foo: (self x)" 156 | (-> a-func-so-dont-trim Inspect (.-cut-obj-name-maybe "foo: (self x)"))) 157 | 158 | (defclass Foo []) 159 | (assert= "foo: (x)" 160 | (-> Foo Inspect (.-cut-obj-name-maybe "foo: (self x)"))) 161 | (assert= "foo: ()" 162 | (-> Foo Inspect (.-cut-obj-name-maybe "foo: (self)")))) 163 | 164 | 165 | (defn test-inspect-cut-method-wrapper [] 166 | (defclass Foo []) 167 | (assert= "method-wrapper: ..." 168 | (-> Foo.--call-- Inspect (.-cut-method-wrapper-maybe "foo: ...")))) 169 | 170 | ;; ** Properties 171 | 172 | (defn test-inspect-obj-name-mangled [] 173 | (defn func_foo []) 174 | (assert= "func-foo" 175 | (-> func_foo Inspect (. obj-name)))) 176 | 177 | 178 | (defn test-inspect-obj-name-special-chars [] 179 | (defn loud-noises/! [] "Rabble!" "") 180 | (assert= "loud-noises/!: () - Rabble!" 181 | (-> loud-noises/! Inspect (.docs)))) 182 | 183 | 184 | (defn test-inspect-class-and-method-wrappers [] 185 | (defclass Foo []) 186 | (assert= "Foo" 187 | (-> Foo Inspect (. obj-name))) 188 | (assert (-> Foo Inspect (. class?))) 189 | (assert (-> Foo.--call-- Inspect (. method-wrapper?)))) 190 | 191 | 192 | (defn test-inspect-lambdas [] 193 | (assert (-> (fn []) Inspect (. lambda?)))) 194 | 195 | ;; ** Actions 196 | 197 | (defn test-inspect-docs-of-class [] 198 | (defclass Foo [object] "A class\nDetails..." (defn --init-- [self x y])) 199 | (assert= "Foo: (x y) - A class" 200 | (-> Foo Inspect (.docs)))) 201 | 202 | 203 | (defn test-inspect-docs-of-builtin [] 204 | (assert= 205 | "print: (value #* args &optional [sep ' '] [end '\\n'] [file sys.stdout] [flush False])" 206 | (-> print Inspect (.docs)))) 207 | 208 | 209 | (defn test-inspect-docs-of-module [] 210 | (import itertools) 211 | (assert= 212 | "Functional tools for creating and using iterators." 213 | (-> itertools Inspect (.docs)))) 214 | 215 | 216 | (defn test-inspect-docs-of-module-function [] 217 | (import itertools) 218 | (assert= 219 | "tee: (iterable &optional [n 2]) - return tuple of n independent iterators." 220 | (-> itertools.tee Inspect (.docs)))) 221 | 222 | 223 | (defn test-inspect-docs-instance [] 224 | (assert= "int: ([x]) -> integer" 225 | (-> 1 Inspect (.docs)))) 226 | 227 | 228 | (defn test-inspect-docs-shadow [] 229 | (assert= "-: (a1 #* a-rest) - Shadowed `-` operator subtracts each `a-rest` from `a1`." 230 | (-> - Inspect (.docs)))) 231 | -------------------------------------------------------------------------------- /tests/test_models.hy: -------------------------------------------------------------------------------- 1 | (require [jedhy.macros [*]]) 2 | (import [jedhy.macros [*]]) 3 | (require [tests.hytest [*]]) 4 | (import [tests.hytest [*]]) 5 | 6 | (import [jedhy.models [Candidate 7 | Namespace 8 | Prefix]]) 9 | 10 | ;; * Namespace 11 | ;; ** Components 12 | 13 | (defn test-namespace-core-macros [] 14 | (assert-all-in ["->" "with" "when"] 15 | (. (Namespace) macros))) 16 | 17 | 18 | (defn test-namespace-user-macros [] 19 | (defmacro foo-macro [x]) 20 | 21 | (assert-not-in "foo-macro" 22 | (. (Namespace) macros)) 23 | (assert-in "foo-macro" 24 | (. (Namespace :macros- --macros--) macros))) 25 | 26 | 27 | (defn test-namespace-imported-macros [] 28 | ;; TODO This test should have another part where I import a macro 29 | ;; that was not imported within the Namespace's parent file. 30 | (assert-in "ap-map" 31 | (. (Namespace) macros))) 32 | 33 | 34 | (defn test-namespace-compiler [] 35 | (assert-all-in ["try" "+=" "require"] 36 | (. (Namespace) compile-table))) 37 | 38 | 39 | (defn test-namespace-shadows [] 40 | (assert-all-in ["get" "is" "not?" "+"] 41 | (. (Namespace) shadows))) 42 | 43 | ;; ** Names 44 | 45 | (defn test-namespace-all-names [] 46 | (assert-all-in ["not?" "for" "->" "ap-map" "first" "print"] 47 | (. (Namespace) names))) 48 | 49 | 50 | (defn test-namespace-names-with-locals [] 51 | (setv x False) 52 | (defn foo []) 53 | (assert-all-in ["foo" "x"] 54 | (. (Namespace :locals- (locals)) names))) 55 | 56 | ;; * Prefixes 57 | ;; ** Building 58 | 59 | (deffixture prefixes 60 | "Prefixes, their candidate symbol, and attr-prefix." 61 | [["obj" "" "obj"] 62 | ["obj.attr" "obj" "attr"] 63 | ["obj." "obj" ""] 64 | ["obj.attr." "obj.attr" ""] 65 | ["" "" ""]] 66 | (#%[(Prefix %1) %2 %3] #* it)) 67 | 68 | (with-fixture prefixes 69 | test-split-prefix [prefix candidate attr-prefix] 70 | 71 | (assert= prefix.candidate.symbol candidate) 72 | (assert= prefix.attr-prefix attr-prefix)) 73 | 74 | ;; ** Completion 75 | 76 | (defn test-completion-builtins [] 77 | (assert-in "print" 78 | (.complete (Prefix "prin"))) 79 | (assert-in "ArithmeticError" 80 | (.complete (Prefix "Ar")))) 81 | 82 | 83 | (defn test-completion-method-attributes [] 84 | (assert-in "print.--call--" 85 | (.complete (Prefix "print."))) 86 | (assert-in "print.--call--" 87 | (.complete (Prefix "print.__c")))) 88 | 89 | 90 | (defn test-completion-macros [] 91 | (assert-in "->>" 92 | (.complete (Prefix "-")))) 93 | 94 | 95 | (defn test-completion-compiler [] 96 | (assert-in "try" 97 | (.complete (Prefix "tr")))) 98 | 99 | 100 | (defn test-completion-core-language [] 101 | (assert-in "iterable?" 102 | (.complete (Prefix "iter")))) 103 | 104 | 105 | (defn test-completion-modules [] 106 | (import itertools) 107 | (assert-in "itertools.tee" 108 | (.complete (Prefix "itertools.t" (Namespace :locals- (locals)))))) 109 | 110 | 111 | (defn test-completion-candidate-doesnt-exist-with-attr-prefix [] 112 | (assert= (,) 113 | (.complete (Prefix "gibberish." (Namespace))))) 114 | 115 | ;; * Candidates 116 | ;; ** Compiler 117 | 118 | (defn test-candidate-compiler [] 119 | (assert (->> "doesn't exist" 120 | Candidate 121 | (.compiler?) 122 | none?))) 123 | 124 | ;; ** Shadows 125 | 126 | (defn test-candidate-shadow [] 127 | (setv shadow? (fn [x] (.shadow? (Candidate x)))) 128 | 129 | (assert (->> ["get" "is" "is_not"] 130 | (map shadow?) 131 | all)) 132 | (assert (->> "doesn't exist" 133 | shadow? 134 | none?))) 135 | 136 | ;; ** Python 137 | 138 | (defn test-candidate-evaled-fails [] 139 | (assert (none? (-> "doesn't exist" Candidate (.evaled?))))) 140 | 141 | (defn test-candidate-evaled-builtins [] 142 | (assert= print (-> "print" Candidate (.evaled?)))) 143 | 144 | (defn test-candidate-evaled-methods [] 145 | (assert= print.--call-- (-> "print.--call--" Candidate (.evaled?)))) 146 | 147 | (defn test-candidate-evaled-modules [] 148 | (import builtins) 149 | (assert= builtins 150 | (-> "builtins" 151 | (Candidate (Namespace :locals- (locals))) 152 | (.evaled?)))) 153 | 154 | ;; ** Attributes 155 | 156 | (defn test-candidate-attributes-fails [] 157 | (assert (none? (-> "doesn't exist" Candidate (.attributes))))) 158 | 159 | (defn test-candidate-attributes-builtin [] 160 | (assert-all-in ["--str--" "--call--"] 161 | (-> "print" Candidate (.attributes)))) 162 | 163 | (defn test-candidate-attributes-module [] 164 | (import builtins) 165 | (assert-all-in ["eval" "AssertionError"] 166 | (-> "builtins" 167 | (Candidate (Namespace :locals- (locals))) 168 | (.attributes)))) 169 | 170 | (defn test-candidate-attributes-nested [] 171 | (assert-all-in ["--str--" "--call--"] 172 | (-> "print.--call--" Candidate (.attributes)))) 173 | 174 | ;; ** Namespacing 175 | 176 | (defn test-candidate-namespace-globals [] 177 | (import itertools) 178 | (assert-in "from-iterable" 179 | (-> "itertools.chain" 180 | (Candidate (Namespace :locals- (locals))) 181 | (.attributes)))) 182 | 183 | (defn test-candidate-namespace-locals [] 184 | (defclass AClass []) 185 | (assert (none? 186 | (-> "AClass" 187 | Candidate 188 | (.attributes)))) 189 | (assert (none? 190 | (-> "AClass" 191 | (Candidate (Namespace :globals- (globals))) 192 | (.attributes)))) 193 | (assert-in "--doc--" 194 | (-> "AClass" 195 | (Candidate (Namespace :locals- (locals))) 196 | (.attributes))) 197 | 198 | (setv doesnt-exist 1) 199 | (assert (-> "doesnt-exist" 200 | (Candidate (Namespace :locals- (locals))) 201 | (.evaled?)))) 202 | 203 | ;; ** Annotations 204 | 205 | (deffixture annotations 206 | "Annotations and candidates." 207 | [["print" ""] 208 | ["first" ""] 209 | ["try" ""] 210 | ["is" ""] 211 | ["get" ""] 212 | ["->" ">"] 213 | ["as->" ">"]] 214 | (#%[%2 (Candidate %1)] #* it)) 215 | 216 | (with-fixture annotations 217 | test-annotations [annotation candidate] 218 | (assert= annotation (.annotate candidate))) 219 | 220 | (defn test-annotate-class [] 221 | (defclass AClass []) 222 | (assert= "" 223 | (-> "AClass" (Candidate (Namespace :locals- (locals))) (.annotate)))) 224 | 225 | (defn test-annotate-module-and-aliases [] 226 | (import itertools) 227 | (assert= "" 228 | (-> "itertools" (Candidate (Namespace :locals- (locals))) (.annotate))) 229 | 230 | (import [itertools :as it]) 231 | (assert= "" 232 | (-> "it" (Candidate (Namespace :locals- (locals))) (.annotate)))) 233 | 234 | (defn test-annotate-vars [] 235 | (setv xx False) 236 | (assert= "" 237 | (-> "xx" (Candidate (Namespace :locals- (locals))) (.annotate)))) 238 | --------------------------------------------------------------------------------