3

I would like to be able to start with code like this:

def foo(a, b, {"c": 1, 'd': 2, "e": 3}):

then execute some emacs function with my cursor anywhere between the {} characters, and rewrite the code to be:

def foo(a, b, c=1, d=2, e=3):

The complications are (1) there can be any number of keys/value pairs inside the {} construction, and the keys can be quoted with either single or double quotes. I don't require the values to be anything other than strings or numbers or variables.

Can anyone point me to such a function, or to something similar I could use as a starting point?

Would it be best to write this kind of thing in raw elisp, or is there a recommended refactoring package with higher-level features for building this kind of thing?

Initially I thought I could just use query-replace-regexp, but I think a dynamic number of replacement groups is not supported, and this would be needed to support complication (1).

3

It seems like you should pass this to python, let it handle the dictionary, and get a string back. Here is basically that, although it does replace all the " with ' before passing it to python.

(defun d2args ()
  (interactive)
  (unless (looking-at "{")
    (re-search-backward "{"))
  (mark-sexp)
  (let* ((start (region-beginning))
     (end (region-end))
     (d (buffer-substring-no-properties start end))
     (d (replace-regexp-in-string "\"" "'" d))
     (cmd (format "python -c \"d=%s; print(', '.join([f'{key}={val}' for key, val in d.items()]))\"" d))) 
    (setf (buffer-substring start end)
          (s-trim (shell-command-to-string cmd)))))

Here is an all elisp solution. Here I converted all single quotes to double quotes so that elisp can read the variable names. This should also work for arbitrary number of kwargs.

(require 'cl)
(require 's)

(defun d2args ()
  (interactive)
  (unless (looking-at "{")
    (re-search-backward "{"))
  (mark-sexp)
  (let* ((start (region-beginning))
     (end (region-end))
     (d (buffer-substring-no-properties (+ start 1) (- end 1)))
     (d (replace-regexp-in-string "'" "\"" d))
     (kwargs (s-split "," d)))

    (setf (buffer-substring start end)
      (mapconcat (lambda (kwarg)
               (cl-destructuring-bind (key val)
               (s-split ":" kwarg)
             (format "%s=%s" (read key) (s-trim val))))
             kwargs ", "))))
| improve this answer | |
  • thanks John, these work great & are great starting points for writing similar transforms! – billc Oct 23 '18 at 20:32
1

You can do most of it with query-replace-regexp. Replace each quote-key-quote-colon part by key-equals. Before you do the replacement, select the braced group. Before or after doing the replacement, remove the braces.

This assumes that the values are not too complex. They can be non-atomic expressions, but my solution assumes that they don't contain any text that looks like a quote-key-quote-colon part.

  1. C-M-u (python-nav-backward-up-list, a Python-specific variant of backward-up-list): go to the opening brace.
  2. C-M-SPC (mark-sexp): select the braced group.
  3. C-M-% (query-replace-regexp)
    of \(["']\)\([0-9A-Z_a-z]+\)\1: * (quote, identifier, same quote as before, colon, optional spaces)
    by \2= (the identifier and an equal sign)
  4. ! to replace everything. You can avoid this step by using M-x replace-regexp (not bound to a key by default) instead of calling query-replace-regexp.
  5. C-M-u again: go back to the opening brace.
  6. C-M-f DEL (DEL is backspace in Emacs): go to the closing brace and delete it.
  7. C-1 C-SPC delete (or variants): go back to the previous mark (which was set at step 2), which is in front of the opening brace, and delete the opening brace.

This is deterministic enough that you could put it in a macro if you don't feel like writing Lisp code.

| improve this answer | |
  • thanks Gilles -- I was doing something similar to this, but what you have written up here is a bit more efficient & complete than what I was doing. – billc Oct 23 '18 at 20:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.