Personal emacs config
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

628 lines
24 KiB

  1. ;;; le-python.el --- lispy support for Python. -*- lexical-binding: t -*-
  2. ;; Copyright (C) 2016 Oleh Krehel
  3. ;; This file is not part of GNU Emacs
  4. ;; This file is free software; you can redistribute it and/or modify
  5. ;; it under the terms of the GNU General Public License as published by
  6. ;; the Free Software Foundation; either version 3, or (at your option)
  7. ;; any later version.
  8. ;; This program is distributed in the hope that it will be useful,
  9. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. ;; GNU General Public License for more details.
  12. ;; For a full copy of the GNU General Public License
  13. ;; see <http://www.gnu.org/licenses/>.
  14. ;;; Commentary:
  15. ;;
  16. ;;; Code:
  17. (require 'python)
  18. (require 'json)
  19. (defun lispy-trim-python (str)
  20. "Trim extra Python indentation from STR.
  21. STR is a string copied from Python code. It can be that each line
  22. of STR is prefixed by e.g. 4 or 8 or 12 spaces.
  23. Stripping them will produce code that's valid for an eval."
  24. (if (string-match "\\`\\( +\\)" str)
  25. (let* ((indent (match-string 1 str))
  26. (re (concat "^" indent)))
  27. (apply #'concat
  28. (split-string str re t)))
  29. str))
  30. (defun lispy-eval-python-bnd ()
  31. (let (bnd)
  32. (save-excursion
  33. (cond ((region-active-p)
  34. (cons
  35. (if (> (count-lines (region-beginning) (region-end)) 1)
  36. (save-excursion
  37. (goto-char (region-beginning))
  38. (skip-chars-backward " ")
  39. (point))
  40. (region-beginning))
  41. (region-end)))
  42. ((and (looking-at lispy-outline)
  43. (looking-at lispy-outline-header))
  44. (lispy--bounds-outline))
  45. ((looking-at "@")
  46. (setq bnd (cons (point)
  47. (save-excursion
  48. (forward-sexp)
  49. (skip-chars-forward "[ \t\n]")
  50. (cdr (lispy-bounds-python-block))))))
  51. ((setq bnd (lispy-bounds-python-block)))
  52. ((bolp)
  53. (lispy--bounds-c-toplevel))
  54. ((lispy-bolp)
  55. (lispy--bounds-c-toplevel))
  56. (t
  57. (cond ((lispy-left-p))
  58. ((lispy-right-p)
  59. (backward-list))
  60. (t
  61. (error "Unexpected")))
  62. (setq bnd (lispy--bounds-dwim))
  63. (ignore-errors (backward-sexp))
  64. (while (or (eq (char-before) ?.)
  65. (eq (char-after) ?\())
  66. (backward-sexp))
  67. (setcar bnd (point))
  68. bnd)))))
  69. (defun lispy-extended-eval-str (bnd)
  70. (let* ((str (lispy--string-dwim bnd))
  71. (lp (cl-count ?\( str))
  72. (rp (cl-count ?\) str)))
  73. (save-excursion
  74. (goto-char (cdr bnd))
  75. (while (< rp lp)
  76. (re-search-forward "[()]" nil t)
  77. (cond ((string= (match-string 0) "(")
  78. (cl-incf lp))
  79. ((string= (match-string 0) ")")
  80. (cl-incf rp))
  81. (t
  82. (error "Unexpected"))))
  83. (if (lispy-after-string-p ")")
  84. (let ((end (point)))
  85. (save-excursion
  86. (forward-sexp -1)
  87. (concat (buffer-substring-no-properties
  88. (car bnd) (point))
  89. (replace-regexp-in-string
  90. "[\\]*\n[\t ]*" " "
  91. (buffer-substring-no-properties
  92. (point) end)))))
  93. (buffer-substring-no-properties (car bnd) (point))))))
  94. (defun lispy-eval-python-str ()
  95. (let* ((bnd (lispy-eval-python-bnd))
  96. (str1 (lispy-trim-python
  97. (lispy-extended-eval-str bnd)))
  98. (str1.5 (replace-regexp-in-string "^ *#[^\n]+\n" "" str1))
  99. ;; (str2 (replace-regexp-in-string "\\\\\n +" "" str1.5))
  100. ;; (str3 (replace-regexp-in-string "\n *\\([])}]\\)" "\\1" str2))
  101. ;; (str4 (replace-regexp-in-string "\\([({[,]\\)\n +" "\\1" str3))
  102. ;; (str5 (replace-regexp-in-string "\"\n *\"" "\" \"" str4))
  103. )
  104. str1.5))
  105. (defun lispy-bounds-python-block ()
  106. (if (save-excursion
  107. (when (looking-at " ")
  108. (forward-char))
  109. (python-info-beginning-of-block-p))
  110. (let ((indent (if (bolp)
  111. 0
  112. (1+ (- (point) (line-beginning-position))))))
  113. (cons
  114. (line-beginning-position)
  115. (save-excursion
  116. (python-nav-end-of-block)
  117. (while (let ((pt (point))
  118. bnd)
  119. (skip-chars-forward "\n ")
  120. (when (setq bnd (lispy--bounds-comment))
  121. (goto-char (cdr bnd)))
  122. (beginning-of-line)
  123. (if (looking-at (format "[\n ]\\{%d,\\}\\(except\\|else\\|elif\\)" indent))
  124. t
  125. (goto-char pt)
  126. nil))
  127. (goto-char (match-beginning 1))
  128. (python-nav-end-of-block))
  129. (point))))
  130. (cons (if (looking-at " ")
  131. (1+ (point))
  132. (point))
  133. (save-excursion
  134. (end-of-line)
  135. (let (bnd)
  136. (when (setq bnd (lispy--bounds-string))
  137. (goto-char (cdr bnd))))
  138. (end-of-line)
  139. (while (member (char-before) '(?\\ ?\( ?\, ?\[ ?\{))
  140. (if (member (char-before) '(?\( ?\[ ?\{))
  141. (progn
  142. (up-list)
  143. (end-of-line))
  144. (end-of-line 2)))
  145. (point)))))
  146. (defun lispy-eval-python (&optional plain)
  147. (let ((res (lispy--eval-python
  148. (lispy-eval-python-str)
  149. plain)))
  150. (if (and res (not (equal res "")))
  151. (lispy-message
  152. (replace-regexp-in-string
  153. "%" "%%" res))
  154. (lispy-message
  155. (replace-regexp-in-string
  156. "%" "%%" lispy-eval-error)))))
  157. (defvar-local lispy-python-proc nil)
  158. (declare-function mash-make-shell "ext:mash")
  159. (defun lispy-set-python-process-action (x)
  160. (setq lispy-python-proc
  161. (cond ((consp x)
  162. (cdr x))
  163. ((require 'mash-python nil t)
  164. (save-window-excursion
  165. (get-buffer-process
  166. (mash-make-shell x 'mash-new-lispy-python))))
  167. (t
  168. (lispy--python-proc (concat "lispy-python-" x))))))
  169. (defun lispy-short-process-name (x)
  170. (when (string-match "^lispy-python-\\(.*\\)" (process-name x))
  171. (match-string 1 (process-name x))))
  172. (defun lispy-set-python-process ()
  173. "Associate a (possibly new) Python process to the current buffer.
  174. Each buffer can have only a single Python process associated with
  175. it at one time."
  176. (interactive)
  177. (let* ((process-names
  178. (delq nil
  179. (mapcar
  180. (lambda (x)
  181. (let ((name (lispy-short-process-name x)))
  182. (when name
  183. (cons name x))))
  184. (process-list)))))
  185. (ivy-read "Process: " process-names
  186. :action #'lispy-set-python-process-action
  187. :preselect (when (process-live-p lispy-python-proc)
  188. (lispy-short-process-name lispy-python-proc))
  189. :caller 'lispy-set-python-process)))
  190. (defvar lispy--python-middleware-loaded-p nil
  191. "Nil if the Python middleware in \"lispy-python.py\" wasn't loaded yet.")
  192. (defun lispy--python-proc (&optional name)
  193. (let* ((proc-name (or name
  194. (and (process-live-p lispy-python-proc)
  195. lispy-python-proc)
  196. "lispy-python-default"))
  197. (process (get-process proc-name)))
  198. (if (process-live-p process)
  199. process
  200. (let* ((python-shell-font-lock-enable nil)
  201. (inferior-python-mode-hook nil)
  202. (python-shell-interpreter
  203. (cond
  204. ((save-excursion
  205. (goto-char (point-min))
  206. (looking-at "#!\\(?:/usr/bin/env \\)\\(.*\\)$"))
  207. (match-string-no-properties 1))
  208. ((file-exists-p python-shell-interpreter)
  209. (expand-file-name python-shell-interpreter))
  210. (t
  211. python-shell-interpreter)))
  212. (python-binary-name (python-shell-calculate-command)))
  213. (setq process (get-buffer-process
  214. (python-shell-make-comint
  215. python-binary-name proc-name nil nil))))
  216. (setq lispy--python-middleware-loaded-p nil)
  217. (lispy--python-middleware-load)
  218. process)))
  219. (defun lispy--python-eval-string-dwim (str)
  220. (setq str (string-trim str))
  221. (let ((single-line-p (= (cl-count ?\n str) 0)))
  222. (cond ((and (or (string-match "\\`\\(\\(?:[., ]\\|\\sw\\|\\s_\\|[][]\\)+\\) += " str)
  223. (string-match "\\`\\(([^)]+)\\) *=[^=]" str))
  224. (save-match-data
  225. (or single-line-p
  226. (and (not (string-match-p "lp\\." str))
  227. (equal (lispy--eval-python
  228. (format "x=lp.is_assignment(\"\"\"%s\"\"\")\nprint (x)" str)
  229. t)
  230. "True")))))
  231. (concat str (format "\nprint (repr ((%s)))" (match-string 1 str))))
  232. ;; match e.g. "x in array" part of "for x in array:"
  233. ((and single-line-p
  234. (string-match "\\`\\([A-Z_a-z0-9]+\\|\\(?:([^)]+)\\)\\) in \\(.*\\)\\'" str))
  235. (let ((vars (match-string 1 str))
  236. (val (match-string 2 str)))
  237. (format "%s = list (%s)[0]\nprint ((%s))" vars val vars)))
  238. ((string-match "\\`def \\([a-zA-Z_0-9]+\\)\\s-*(\\s-*self" str)
  239. (let ((fname (match-string 1 str))
  240. (cname (car (split-string (python-info-current-defun) "\\."))))
  241. (concat str
  242. "\n"
  243. (format "lp.rebind('%s', '%s')" cname fname))))
  244. (t
  245. str))))
  246. (defun lispy--eval-python (str &optional plain)
  247. "Eval STR as Python code."
  248. (let ((single-line-p (= (cl-count ?\n str) 0)))
  249. (unless plain
  250. (setq str (lispy--python-eval-string-dwim str))
  251. (when (string-match "__file__" str)
  252. (lispy--eval-python (format "__file__ = '%s'\n" (buffer-file-name)) t))
  253. (when (and single-line-p (string-match "\\`return \\(.*\\)\\'" str))
  254. (setq str (match-string 1 str))))
  255. (let ((res
  256. (cond ((or single-line-p
  257. (string-match "\n .*\\'" str)
  258. (string-match "\"\"\"" str))
  259. (python-shell-send-string-no-output
  260. str (lispy--python-proc)))
  261. ((string-match "\\`\\([\0-\377[:nonascii:]]*\\)\n\\([^\n]*\\)\\'" str)
  262. (let* ((p1 (match-string 1 str))
  263. (p2 (match-string 2 str))
  264. (p1-output (python-shell-send-string-no-output
  265. p1 (lispy--python-proc)))
  266. p2-output)
  267. (cond
  268. ((string-match-p "SyntaxError:\\|error:" p1-output)
  269. (python-shell-send-string-no-output
  270. str (lispy--python-proc)))
  271. ((null p1-output)
  272. (lispy-message lispy-eval-error))
  273. ((null (setq p2-output (lispy--eval-python p2)))
  274. (lispy-message lispy-eval-error))
  275. (t
  276. (concat
  277. (if (string= p1-output "")
  278. ""
  279. (concat p1-output "\n"))
  280. p2-output)))))
  281. (t
  282. (error "unexpected")))))
  283. (cond
  284. ((string-match "SyntaxError: 'return' outside function\\'" res)
  285. (lispy--eval-python
  286. (concat "__return__ = None\n"
  287. (replace-regexp-in-string
  288. "\\(^ *\\)return"
  289. (lambda (x) (concat (match-string 1 x) "__return__ ="))
  290. str)
  291. "\nprint (repr(__return__))")
  292. t))
  293. ((string-match "^Traceback.*:" res)
  294. (set-text-properties
  295. (match-beginning 0)
  296. (match-end 0)
  297. '(face error)
  298. res)
  299. (setq lispy-eval-error res)
  300. nil)
  301. ((equal res "")
  302. (setq lispy-eval-error "(ok)")
  303. "")
  304. ((string-match-p "^<\\(?:map\\|filter\\|generator\\) object" res)
  305. (let ((last (car (last (split-string str "\n")))))
  306. (when (string-match "\\`print (repr ((\\(.*\\))))\\'" last)
  307. (setq str (match-string 1 last))))
  308. (lispy--eval-python (format "list(%s)" str) t))
  309. ((string-match-p "SyntaxError:" res)
  310. (setq lispy-eval-error res)
  311. nil)
  312. (t
  313. (replace-regexp-in-string "\\\\n" "\n" res))))))
  314. (defun lispy--python-array-to-elisp (array-str)
  315. "Transform a Python string ARRAY-STR to an Elisp string array."
  316. (when (and (stringp array-str)
  317. (not (string= array-str "")))
  318. (let ((parts (with-temp-buffer
  319. (python-mode)
  320. (insert (substring array-str 1 -1))
  321. (goto-char (point-min))
  322. (let (beg res)
  323. (while (< (point) (point-max))
  324. (setq beg (point))
  325. (forward-sexp)
  326. (push (buffer-substring-no-properties beg (point)) res)
  327. (skip-chars-forward ", "))
  328. (nreverse res)))))
  329. (mapcar (lambda (s)
  330. (if (string-match "\\`\"" s)
  331. (read s)
  332. (if (string-match "\\`'\\(.*\\)'\\'" s)
  333. (match-string 1 s)
  334. s)))
  335. parts))))
  336. (defun lispy-python-symbol-bnd ()
  337. (let ((bnd (or (bounds-of-thing-at-point 'symbol)
  338. (cons (point) (point)))))
  339. (save-excursion
  340. (goto-char (car bnd))
  341. (while (progn
  342. (skip-chars-backward " ")
  343. (lispy-after-string-p "."))
  344. (backward-char 1)
  345. (skip-chars-backward " ")
  346. (if (lispy-after-string-p ")")
  347. (backward-sexp 2)
  348. (backward-sexp)))
  349. (skip-chars-forward " ")
  350. (setcar bnd (point)))
  351. bnd))
  352. (defun lispy-python-completion-at-point ()
  353. (cond ((looking-back "^\\(import\\|from\\) .*" (line-beginning-position))
  354. (let* ((line (buffer-substring-no-properties
  355. (line-beginning-position)
  356. (point)))
  357. (str
  358. (format
  359. "import jedi; script=jedi.Script(\"%s\",1,%d); [_x_.name for _x_ in script.completions()]"
  360. line (length line)))
  361. (cands
  362. (lispy--python-array-to-elisp
  363. (lispy--eval-python str)))
  364. (bnd (bounds-of-thing-at-point 'symbol))
  365. (beg (if bnd (car bnd) (point)))
  366. (end (if bnd (cdr bnd) (point))))
  367. (list beg end cands)))
  368. ((lispy-complete-fname-at-point))
  369. (t
  370. (let* ((bnd (lispy-python-symbol-bnd))
  371. (str (buffer-substring-no-properties
  372. (car bnd) (cdr bnd))))
  373. (when (string-match "\\()\\)[^)]*\\'" str)
  374. (let ((expr (format "__t__ = %s" (substring str 0 (match-end 1)))))
  375. (setq str (concat "__t__" (substring str (match-end 1))))
  376. (cl-incf (car bnd) (match-end 1))
  377. (lispy--eval-python expr t)))
  378. (list (car bnd)
  379. (cdr bnd)
  380. (mapcar (lambda (s)
  381. (replace-regexp-in-string
  382. "__t__" ""
  383. (if (string-match "(\\'" s)
  384. (substring s 0 (match-beginning 0))
  385. s)))
  386. (python-shell-completion-get-completions
  387. (lispy--python-proc)
  388. nil str)))))))
  389. (defvar lispy--python-arg-key-re "\\`\\(\\(?:\\sw\\|\\s_\\)+\\) ?= ?\\(.*\\)\\'"
  390. "Constant regexp for matching function keyword spec.")
  391. (defun lispy--python-args (beg end)
  392. (let (res)
  393. (save-excursion
  394. (goto-char beg)
  395. (skip-chars-forward "\n\t ")
  396. (setq beg (point))
  397. (while (< (point) end)
  398. (forward-sexp)
  399. (while (and (< (point) end)
  400. (not (looking-at ",")))
  401. (forward-sexp))
  402. (push (buffer-substring-no-properties
  403. beg (point))
  404. res)
  405. (skip-chars-forward ", \n")
  406. (setq beg (point))))
  407. (nreverse res)))
  408. (defun lispy--python-step-in-loop ()
  409. (when (looking-at " ?for \\([A-Z_a-z,0-9 ()]+\\) in \\(.*\\):")
  410. (let* ((vars (match-string-no-properties 1))
  411. (val (match-string-no-properties 2))
  412. (res (lispy--eval-python
  413. (format "lp.list_step(\"%s\",%s)" vars val)
  414. t)))
  415. (lispy-message res))))
  416. (defun lispy--python-debug-step-in ()
  417. (unless (lispy--python-step-in-loop)
  418. (when (looking-at " *(")
  419. ;; tuple assignment
  420. (forward-list 1))
  421. (re-search-forward "(" (line-end-position))
  422. (backward-char)
  423. (let* ((p-ar-beg (point))
  424. (p-ar-end (save-excursion
  425. (forward-list)
  426. (point)))
  427. (p-fn-end (progn
  428. (skip-chars-backward " ")
  429. (point)))
  430. (method-p nil)
  431. (p-fn-beg (progn
  432. (backward-sexp)
  433. (while (eq (char-before) ?.)
  434. (setq method-p t)
  435. (backward-sexp))
  436. (point)))
  437. (fn (buffer-substring-no-properties
  438. p-fn-beg p-fn-end))
  439. (args
  440. (lispy--python-args (1+ p-ar-beg) (1- p-ar-end)))
  441. (args (if (and method-p
  442. (string-match "\\`\\(.*?\\)\\.\\([^.]+\\)\\'" fn))
  443. (cons (match-string 1 fn)
  444. args)
  445. args))
  446. (args-key (cl-remove-if-not
  447. (lambda (s)
  448. (string-match lispy--python-arg-key-re s))
  449. args))
  450. (args-normal (cl-set-difference args args-key))
  451. (fn-data
  452. (json-read-from-string
  453. (substring
  454. (lispy--eval-python
  455. (format "import inspect, json; json.dumps (inspect.getargspec (%s))"
  456. fn))
  457. 1 -1)))
  458. (fn-args
  459. (append (mapcar #'identity (elt fn-data 0))
  460. (if (elt fn-data 1)
  461. (list (elt fn-data 1)))))
  462. (fn-defaults
  463. (mapcar
  464. (lambda (x)
  465. (cond ((null x)
  466. "None")
  467. ((eq x t)
  468. "True")
  469. (t
  470. (prin1-to-string x))))
  471. (elt fn-data 3)))
  472. (fn-alist
  473. (cl-mapcar #'cons
  474. fn-args
  475. (append (make-list (- (length fn-args)
  476. (length fn-defaults))
  477. nil)
  478. fn-defaults)))
  479. fn-alist-x dbg-cmd)
  480. (if method-p
  481. (unless (member '("self") fn-alist)
  482. (push '("self") fn-alist))
  483. (setq fn-alist (delete '("self") fn-alist)))
  484. (setq fn-alist-x fn-alist)
  485. (dolist (arg args-normal)
  486. (setcdr (pop fn-alist-x) arg))
  487. (dolist (arg args-key)
  488. (if (string-match lispy--python-arg-key-re arg)
  489. (let ((arg-name (match-string 1 arg))
  490. (arg-val (match-string 2 arg))
  491. arg-cell)
  492. (if (setq arg-cell (assoc arg-name fn-alist))
  493. (setcdr arg-cell arg-val)
  494. (error "\"%s\" is not in %s" arg-name fn-alist)))
  495. (error "\"%s\" does not match the regex spec" arg)))
  496. (when (memq nil (mapcar #'cdr fn-alist))
  497. (error "Not all args were provided: %s" fn-alist))
  498. (setq dbg-cmd
  499. (mapconcat (lambda (x)
  500. (format "%s = %s" (car x) (cdr x)))
  501. fn-alist
  502. "; "))
  503. (if (lispy--eval-python dbg-cmd t)
  504. (progn
  505. (goto-char p-fn-end)
  506. (lispy-goto-symbol fn))
  507. (goto-char p-ar-beg)
  508. (message lispy-eval-error)))))
  509. (declare-function deferred:sync! "ext:deferred")
  510. (declare-function jedi:goto-definition "ext:jedi-core")
  511. (declare-function jedi:call-deferred "ext:jedi-core")
  512. (defun lispy-goto-symbol-python (_symbol)
  513. (save-restriction
  514. (widen)
  515. (let ((res (ignore-errors
  516. (or
  517. (deferred:sync!
  518. (jedi:goto-definition))
  519. t))))
  520. (if (member res '(nil "Definition not found."))
  521. (let* ((symbol (python-info-current-symbol))
  522. (symbol-re (concat "^\\(?:def\\|class\\).*" (car (last (split-string symbol "\\." t)))))
  523. (file (lispy--eval-python
  524. (format
  525. "import inspect\nprint(inspect.getsourcefile(%s))" symbol))))
  526. (cond ((and (equal file "None")
  527. (re-search-backward symbol-re nil t)))
  528. (file
  529. (find-file file)
  530. (goto-char (point-min))
  531. (re-search-forward symbol-re)
  532. (beginning-of-line))
  533. (t
  534. (error "Both jedi and inspect failed"))))
  535. (unless (looking-back "def " (line-beginning-position))
  536. (jedi:goto-definition))))))
  537. (defun lispy--python-docstring (symbol)
  538. "Look up the docstring for SYMBOL.
  539. First, try to see if SYMBOL.__doc__ returns a string in the
  540. current REPL session (dynamic).
  541. Otherwise, fall back to Jedi (static)."
  542. (let ((dynamic-result (lispy--eval-python (concat symbol ".__doc__"))))
  543. (if (> (length dynamic-result) 0)
  544. (mapconcat #'string-trim-left
  545. (split-string (substring dynamic-result 1 -1) "\\\\n")
  546. "\n")
  547. (require 'jedi)
  548. (plist-get (car (deferred:sync!
  549. (jedi:call-deferred 'get_definition)))
  550. :doc))))
  551. (defun lispy-python-middleware-reload ()
  552. (interactive)
  553. (setq lispy--python-middleware-loaded-p nil)
  554. (lispy--python-middleware-load))
  555. (defvar lispy-python-init-file "~/git/site-python/init.py")
  556. (defun lispy--python-middleware-load ()
  557. "Load the custom Python code in \"lispy-python.py\"."
  558. (unless lispy--python-middleware-loaded-p
  559. (let ((r (lispy--eval-python
  560. (format "import imp;lp=imp.load_source('lispy-python','%s');__name__='__repl__'"
  561. (expand-file-name "lispy-python.py" lispy-site-directory)))))
  562. (if r
  563. (progn
  564. (when (file-exists-p lispy-python-init-file)
  565. (lispy--eval-python
  566. (format "exec (open ('%s').read(), globals ())"
  567. (expand-file-name lispy-python-init-file))))
  568. (setq lispy--python-middleware-loaded-p t))
  569. (lispy-message lispy-eval-error)))))
  570. (defun lispy--python-arglist (symbol filename line column)
  571. (lispy--python-middleware-load)
  572. (let* ((boundp (lispy--eval-python symbol))
  573. (code (if boundp
  574. (format "lp.arglist(%s)" symbol)
  575. (format "lp.arglist_jedi(%d, %d, '%s')" line column filename)))
  576. (args (lispy--python-array-to-elisp
  577. (lispy--eval-python
  578. code))))
  579. (format "%s (%s)"
  580. symbol
  581. (mapconcat #'identity
  582. (delete "self" args)
  583. ", "))))
  584. (provide 'le-python)
  585. ;;; le-python.el ends here