How to include external scripts in Emacs

Make your life easier. Use custom scripts in your editor of choice. Recently I had to convert a lot of JSON to Ruby hashes and vice versa for testing purposes. So I will demonstrate it on this “real life” example.

This is a sinewy task. Regular expressions to the rescue! Wait,… no. Misusing regular expressions as parsers and generators usually does not work, especially in edge case scenarios. Trust me. Been there, done that.

The best solution for this problem is to incorporate the parsers and generators that the code will face later anyways, namely eval and JSON.parse. If they fail to convert my snippets, so will the interpreter down the road. Here are two short scripts that solve the problem.

convert_json_to_rb_hash.rb

#!/usr/local/bin/ruby
require 'json'
require 'awesome_print'

input = ARGF.read
json_as_hash = JSON.parse(input)
# pretty print the hash to STDOUT
ap(json_as_hash, {:index => false, :plain => true})

convert_rb_hash_to_json.rb

#!/usr/local/bin/ruby
require 'json'

input = ARGF.read
rb_hash = eval(input)
# pretty print json to STDOUT
puts JSON.pretty_generate(rb_hash)

Both scripts are very straight forward. You get input from STDIN, parse it and then convert it to the required format. A nice bonus is that the output is pretty printed.

Now that I have the external scripts I have to embed them in Emacs, this is why I need some wrapper functions.

Here are the two functions that I use in my .emacs.d, you can easily call them with M-x mars-convert-json-to-hash on a region and they will convert the code in place:

(defun mars-convert-json-to-hash (startPos endPos)
  "Convert a json object to a ruby hash..
This command calls the external script 'convert_json_to_rb_hash.rb'."
  (interactive "r")
  (let (scriptName)
    (setq scriptName "/Users/martin.stoev/bin/convert_json_to_rb_hash.rb") ; full path to your script
    (shell-command-on-region startPos endPos scriptName nil t nil t))
  (indent-region startPos endPos))

(defun mars-convert-hash-to-json (startPos endPos)
  "Convert a json object to a ruby hash..
This command calls the external script 'convert_rb_hash_to_json.rb'."
  (interactive "r")
  (let (scriptName)
    (setq scriptName "/Users/martin.stoev/bin/convert_rb_hash_to_json.rb") ; full path to your script
    (shell-command-on-region startPos endPos scriptName nil t nil t))
  (indent-region startPos endPos))

Here is a typical use case. First you copy paste some JSON into your code, then you select the region that matches the object and execute M-x mars-convert-json-to-hash.

TODO: add images here

After execution everything is converted and intended as expected.