0

When working with code generation, Python's inspect.cleandoc is quite useful, as it allows to write inline code in a multi-line string and then automatically adjust the indentation.

# -- Input:
def generate_code():
    return inspect.cleandoc(f"""
        if cond():
            do_stuff()
        else:
            do_other_stuff()
    """)
print(generate_code())

# -- Output:
if cond():
    do_stuff()
else:
    do_other_stuff()

Is there any similar convenience function in Emacs Lisp?

Motivation

Since it came up: The motivation for the question was trying to improve the following snippet:

(with-temp-buffer
  (insert "\nimport os"
          "\nimport sys"
          "\ndef _dotemacs_shellsend_setup():"
          "\n    sourcefile = " (format "%S" source-file)
          "\n    sourcedir = os.path.dirname(sourcefile)"
          "\n    print()"
          "\n    print(f'Sending file {sourcefile}...')"
          "\n    if os.getcwd() != sourcedir:"
          "\n        os.chdir(sourcedir)"
          "\n        print(f'{os.getcwd() = }')"
          "\n    if sys.argv[:1] != [sourcefile]:"
          "\n        sys.argv[:1] = [sourcefile]"
          "\n        print(f'{sys.argv[0] = }')"
          "\n_dotemacs_shellsend_setup()")
  (python-shell-send-buffer nil msg))

While editing this code, I several times ended up forgetting trailing newlines (hence moving them to the front), or accidentally deleting trailing double-quotes.

What I want to do instead is something like

(with-temp-buffer
  (insert (UNKNOWN-FUNCTION (concat "
      import os"
      import sys"
      def _dotemacs_shellsend_setup():
          sourcefile = " (format "%S" source-file) "
          sourcedir = os.path.dirname(sourcefile)
          print()
          print(f'Sending file {sourcefile}...')
          if os.getcwd() != sourcedir:
              os.chdir(sourcedir)
              print(f'{os.getcwd() = }')
          if sys.argv[:1] != [sourcefile]:
              sys.argv[:1] = [sourcefile]
              print(f'{sys.argv[0] = }')
      _dotemacs_shellsend_setup()")
  (python-shell-send-buffer nil msg))))

The aim is to strip the common indentation of all lines, in order to generate correctly not-indented Python code, while keeping the indentation of the in-code template consistent with the surrounding code structure.

While such a function wouldn't be too hard to write, I am interested in whether there is builtin functionality for this.

2
  • I don't see how that would be useful, since Elisp is a LISP. In other words, macros exist.
    – aadcg
    Jul 28, 2021 at 13:46
  • @aadcg I added an example. The current use-case was generated python code in a customized python-shell-send-buffer wrapper. So basically the use-case is generating non-lisp code.
    – kdb
    Jul 29, 2021 at 12:10

1 Answer 1

1

I would just leverage python for this.

(shell-command-to-string
 (format "python -c \"import inspect; print(inspect.cleandoc('''%s'''))\""
     (let ((source-file "test.py"))
       (concat "
                   import os
                   import sys
                   def _dotemacs_shellsend_setup():
                       sourcefile = " (format "%S" source-file) "
                       sourcedir = os.path.dirname(sourcefile)
                       print()
                       print(f'Sending file {sourcefile}...')
                       if os.getcwd() != sourcedir:
                           os.chdir(sourcedir)
                           print(f'{os.getcwd() = }')
                       if sys.argv[:1] != [sourcefile]:
                           sys.argv[:1] = [sourcefile]
                           print(f'{sys.argv[0] = }')
                   _dotemacs_shellsend_setup()"))))

This outputs a string:

import os
import sys
def _dotemacs_shellsend_setup():
    sourcefile = test.py
    sourcedir = os.path.dirname(sourcefile)
    print()
    print(f'Sending file {sourcefile}...')
    if os.getcwd() != sourcedir:
        os.chdir(sourcedir)
        print(f'{os.getcwd() = }')
    if sys.argv[:1] != [sourcefile]:
        sys.argv[:1] = [sourcefile]
        print(f'{sys.argv[0] = }')
_dotemacs_shellsend_setup()
5
  • What I am trying to solve is the mismatch between the indentation of the code-generation template and the surrounding Emacs lisp, without having to resorting to writing one string literal per line. In your solution, this mismatch present heavily.
    – kdb
    Jul 29, 2021 at 16:22
  • Tried to clarify it more.
    – kdb
    Jul 29, 2021 at 16:28
  • 1
    I updated my answer to get closer to what you want. Jul 29, 2021 at 17:45
  • My first thought was "its awkward to use an external process for string processing". Implementing a cleandoc emacs lisp function myself though, it turns out to be quite acceptable though, given the overhead. (Though the naming confusion, where python may refer either to a python3 or a python2 executable is an issue.)
    – kdb
    Aug 4, 2021 at 9:08
  • You could always be specific about which python executable to use, even with a full path to it. Aug 4, 2021 at 11:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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