Emacs Lisp: Write Emacs Commands Using {Perl, Python, Ruby, …}

Master emacs+lisp, benefit for life. Testimonials. Thank you for support.
, , …,

This page shows you how you can write a text processing script in your favorite language, then make it available in emacs as a command.

Problem Description

If you are new to emacs lisp, it may take several months for you to become productive. However, you are probably familiar with Perl, Python, Ruby, PHP. You can use your existing knowledge to write many text processing scripts and make them available in emacs as commands, so that you can just select text, press a key, then the selected text will be transformed according to your script.

Solution

Your shell script will need to:

Let's assume your script is the unix program “wc”. (the “wc” command counts the number of words, lines, chars in the text.) For example, try this in shell: cat ‹file name› | wc.

You need a elisp command that:

  1. Grab the current region.
  2. Pass the text to a external program.
  3. Take the output from Stdout.
  4. Replace the current region by that text.

Lucky for us, the elisp function shell-command-on-region does this exactly.

Here's the elisp wrapper:

(defun do-something-region (startPos endPos)
  "Do some text processing on region.
This command calls the external script “wc”."
(interactive "r")
  (let (scriptName)
    (setq scriptName "/usr/bin/wc") ; full path to your script
    (shell-command-on-region startPos endPos scriptName nil t nil t)
    ))

Put the above code in your emacs init file and restart emacs. Or, just select the lisp code and call eval-region. 〔☛ Emacs: How to Evaluate Emacs Lisp Code

To use your command, first make a text selection, then call the command by name. You can give it a key. 〔☛ Emacs: How to Define Keys

With the above, you can write many little text processing scripts in your favorite language, and have them all available in emacs as commands.

emacs ♥

Full Example with Python

In this example, the emacs wrapper calls a python script. The script receives input from stdin AND the one “command line arg”.

here's the emacs lisp wrapper:

(defun python-ref-linkify ()
  "Transform current line (a file path) into a link.
For example, this line:

/home/xah/web/xahlee_info/python_doc_2.7.6/library/stdtypes.html#mapping-types-dict

becomes

<span class=\"ref\"><a href=\"../python_doc_2.7.6/library/stdtypes.html#mapping-types-dict\">5. Built-in Types — Python v2.7.6 documentation #mapping-types-dict</a></span>

The path is relative to current file. The link text is the linked file's title, plus fragment url part, if any.

Requires a python script. See code."
  (interactive)
  (let (scriptName bds)
    (setq bds (bounds-of-thing-at-point 'filename) )
    (save-excursion
      (setq scriptName (format "/usr/bin/python3 /home/xah/git/xahscripts/emacs_pydoc_ref_linkify.py3 %s" (buffer-file-name)) )
      (shell-command-on-region (car bds) (cdr bds) scriptName nil "REPLACE" nil t)
      )
    ))

here's the python script it calls:

#!/usr/bin/python3
# -*- coding: utf-8 -*-

# input: stdin and 1st arg from command line
# output: stdout

# input is a file path, like this:
# /home/xah/web/xahlee_info/python_doc_2.7.6/library/stdtypes.html#mapping-types-dict
# first arg is a file path like this
# /home/xah/web/xahlee_info/python/python3_traverse_dir.html
# output should be like this

# <span class="ref"><a href="../python_doc_2.7.6/library/stdtypes.html#mapping-types-dict">5. Built-in Types — Python v2.7.6 documentation #mapping-types-dict</a></span>

# the path is a relative path, relative to script arg. The link text is from the file's title, plus the fragment url, if any

# this is called by a emacs command python-ref-linkify

# Xah Lee
# http://xahlee.info/
# 2013-11-29

import sys
import os.path
import re

default_input = "/home/xah/web/xahlee_info/python_doc_3.3.3/library/os.html#os.walk"
default_bufferpath = "/home/xah/web/xahlee_info/python/python3_traverse_dir.html"

input_text = sys.stdin.read()
input_text = default_input if input_text == "" else input_text

buffer_path = sys.argv[1] if len(sys.argv) == 2 else default_bufferpath

########################

input_text = re.sub(r"^file://", "" , input_text.strip())

if re.search(r"#", input_text):
    doc_path, frac = input_text.split("#")
else:
    doc_path, frac = input_text, ""

doc_title = ""

with open(doc_path, "r") as f1:
    for xline in f1:
        xmatch = re.search(r"<title>([^<]+?)</title>", xline, re.UNICODE)
        # <title>16.1. os — Miscellaneous operating system interfaces — Python v3.3.3 documentation</title>
        if xmatch:
            doc_title = xmatch.group(1)
            break

# if doc_title == "":
#     sys.exit("no title found in 「{}」".format(doc_path))

relative_path = os.path.relpath( doc_path, start= os.path.dirname(buffer_path))

print("""<span class="ref"><a href="{}">{}</a></span>""".format(relative_path + "#" + frac, doc_title + " #" + frac) )

thanks to Jordan Greenberg for python tips.

Simple Example with JavaScript & Node.Js

Emacs: Decode URI Percent Encoding

Like what you read?
Buy Xah Emacs Tutorial
or share some
blog comments powered by Disqus