Emacs external code formatting in Python
One of the nice things with Emacs is its extensibility. You can write some Emacs Lisp to add some new functionality, if it does not already exist in MELPA. There are times however when writing an extension in another language might be useful (eg: Using C, C++, Perl, Lua etc).
Recently I was writing some User RPL code in Emacs for my trusty HP 50g, and decided to write a simple formatter to illustrate how to call external code that modifies the current Emacs buffer you are working on.
The Elisp program makes use of shell-command-on-region to call out to an external python program that reads from stdin and outputs the formatted text to stdout. We can indicate to Emacs to replace the text in the current buffer with the output from the external command.
An extract of the interesting parts of the Emacs extension is shown here.
;;; fs-userrpl-tools.el --- Some tools to make UserRPL programming a better experience ;;; Code: (defgroup fs-userrpl nil "Tools for UserRPL programming." :prefix "fs-userrpl-" :group 'applications) (defcustom fs-userrpl-formatter-program "/home/frank/repos/userrpl_tools/userrpl_format.py" "External program that formats UserRPL code." :type '(string) :group 'fs-userrpl) (defun fs-userrpl-format-on-buffer () "Format the current buffer using UserRPL formatting." (interactive) (save-excursion (shell-command-on-region (point-min) (point-max) fs-userrpl-formatter-program t t) (whitespace-cleanup) )) (provide 'fs-userrpl-tools) ;;; fs-userrpl-tools.el ends here
Basically this will allow you to type M-x fs-userrpl-format-on-buffer that calls our external Python script userrpl_format.py to format input text passed to it from Emacs. The python script simply reads from stdin and outputs modified text to stdout. It is shown here.
#!/usr/bin/env python3 # # Copyright (C) Frank Singleton b17flyboy@gmail.com # # Simple UserRPL code formatter. Called from emacs # # For now '\<<' triggers indent increase, and '\>>' triggers indent decrease # All lines are stripped of excess whitespace via strip() also # import sys def do_main(): """Main function""" # defines local indent style indent = 0 indent_inc = 4 indent_string = ' ' for line in sys.stdin: line = line.strip() if line.startswith(r'\<<'): # print line then increase indent print('{}{}'.format(indent_string * indent, line)) indent += indent_inc elif line.startswith(r'\>>'): # decrease indent then print line indent -= indent_inc print('{}{}'.format(indent_string * indent, line)) else: # print at current indent print('{}{}'.format(indent_string * indent, line)) if __name__ == '__main__': do_main()
So, invoking M-x fs-userrpl-format-on-buffer will format existing code like this
@ @ Simple prototype to support syncing time from remote PC. @ @ Copyright (C) Frank Singleton b17flyboy at gmail.com @ @ Wait for 9 characters to be received from serial port. @ eg: 21.152185 (HH.MMSSss) and set TIME on calculator @ @ For example, use hptimesync.py. @ @ 1. Press SYNCT on calculator @ 2. Run hptimesync.py on laptop (eg: Fedora VM) @ @ Note: received time string is placed on stack as string @ so you can see what was received. @ @ 'SYNCT' PURGE \<< MEM DROP @ force garbage collection 9600 BAUD CLEAR ERR0 @ clear last error status @ init IO, clear buffers/errors CLOSEIO OPENIO @ wait for 9 characters. simple protocol @ no error checking for now. 9 SRECV DROP DUP STR\-> \->TIME \>> 'SYNCT' STO
as something more pleasing, like this.
@ @ Simple prototype to support syncing time from remote PC. @ @ Copyright (C) Frank Singleton b17flyboy at gmail.com @ @ Wait for 9 characters to be received from serial port. @ eg: 21.152185 (HH.MMSSss) and set TIME on calculator @ @ For example, use hptimesync.py. @ @ 1. Press SYNCT on calculator @ 2. Run hptimesync.py on laptop (eg: Fedora VM) @ @ Note: received time string is placed on stack as string @ so you can see what was received. @ @ 'SYNCT' PURGE \<< MEM DROP @ force garbage collection 9600 BAUD CLEAR ERR0 @ clear last error status @ init IO, clear buffers/errors CLOSEIO OPENIO @ wait for 9 characters. simple protocol @ no error checking for now. 9 SRECV DROP DUP STR\-> \->TIME \>> 'SYNCT' STO
So go check out the project on Bitbucket to see how shell-command-on-region works and how userrpl_format.py implements a simple formatter.
Cheers …