In this post I will demonstrate using Gazelle to write a Grease/Tampermonkey script to replace certain IP addresses in gmail's login history page with names, if the IP address is known to belong to a particular location.
I'm using Tampermonkey on Chrome, but I understand that the Greasemonkey script should be similar. Your mileage may vary.
Setting Up Gazelle
Gazelle is/aspires to be easy to set up.You'll need GNU Emacs, which hosts the entire project.
You'll also need the Gazelle repository repository and shadchen-el.
you@home:~$ cd emacs-code # or wherever you put your emacs stuff
you@home:~/emacs-code$ git clone https://github.com/VincentToups/shadchen-el.git
you@home:~/emacs-code$ git clone https://github.com/VincentToups/gazelle.git
Then, in your emacs configuration, either .emacs.d/init.el or
.emacs add lines to the effect of:(push "~/emacs-code/shadchen-el/" load-path)
(push "~/emacs-code/gazelle/" load-path)
If you want to use Gazelle you then must, at some point, (require
'gazelle). It is very much worth byte-compiling all the Gazelle emacs lisp files, because they depend on a fair amount of macro magic and byte compiling should improve performance significantly.What Do We Want to Do?
Gmail provides a handy feature that allows you to see what IP addresses are currently logged into your gmail account. It is theDetails link at the very bottom right of one's inbox. It pops up a
page that looks like this:What we'd like to do is look at each of the
td elements in the page,
and if the element contains a description of an IP address we know, we
want to replace the text of that element with a more descriptive name. Here I have blacked out the IP addresses, but the lines are something to the effect of "* 000.00.00.000 (Geographic Location)".This is a pretty simple task usinq something like jQuery, so the idea here is to show how it works in Gazelle.
Writing the Script
Create a file calledname-ips-in-gmail.gazelle and put it someplace
convenient. Tampermonkey scripts begin with a comment header that
tells the system a few things about the script. Our script is going
to use Jquery, and so the most notable line is the @require form.
Comments are introduced in Gazelle via the comment form, which
takes strings. (comment "==UserScript=="
"@name Gazelle Example/IP Namer"
"@namespace http://use.i.E.your.homepage/"
"@version 0.1"
"@description enter something useful"
"@match
Now, it will be useful in this script to get the text of an html
element but without the text of any sub-elements. The behavior of
.text() in Jquery is the latter, rather than the former. So we
define a function to do what we want:
(define (just-text element)
(.. ($ element)
(clone)
(children)
(remove)
(end)
(text)))
This function simply clones the element in question, removes all of
its children, and then gets the text. We will want to replace the contents of an element if it contains a particular IP address, so we need a predicate function to test for strings containing some substring.
(define (string-contains? s substring)
(if (_> (.. s (index-of substring)) 0)
true
false))
In this case, we use the prim operation _> because we haven't
included a function definition of >. If we want that we could
write:(define (> a b)
(_> a b))
(See footnote 1)When using Gazelle with modules, the
hooves/operator-functions
module contains function or macro definitions for the common
operators (where appropriate). But here its not worth the trouble. Now the meat of the script, a function which will be applied to each
td element on the page, and which will test to see whether it should
be replaced with a known location. (define (each-fun index element)
(var text (just-text element))
(if (string-contains? text "000.00.000.000")
(.. ($ element) (text "Location 1")))
(if (string-contains? text "00.000.00.000")
(.. ($ element) (text "Location 2")))
(if (string-contains? text "00.00.000.000")
(.. ($ element) (text "Location 3"))))
Obviously you will need to replace the IPs above with meaningful
ones. You could also make an easier to extend implementation.And finally we perform the action:
(..
($ "td") (each
each-fun))
Once we have all this in a file, we can say:M-x gz:transcode-file
And we get this result in name-ips-in-gmail.js:// ==UserScript==
// @name Gazelle Example/IP Namer
// @namespace http://use.i.E.your.homepage/
// @version 0.1
// @description enter something useful
// @match
Now simply create a new User Script in Grease or Tampermonkey and copy
the .js file into it and you should be done. Congratulations,
you've written your first bits of Gazelle code!Footnote 1:
Since Gazelle is brand new, it bears discussing exactly what something like this:
(define (> a b)
(_> a b))
really means. This form introduces a function called `>` which, in its body, references the Javascript operator of the same name by using the symbol `_>`. This is expanded to Javascript code like:
var greaterThan = function(a,b){ return a>b }
In Gazelle we can refer to the function `greaterThan` with the symbol `>`. We could also restrict ourselves to uses of the operator `_>`, but the function is probably preferred, because we can map, reduce, filter, or apply with it. Frankly, I'll never understand why modern programming languages keep to the idea that one should have these second class citizen "operators" floating around.

2 comments:
I'm saying this out of total laziness, because I seem to have no desire to test it myself. But it would be cool to know if Gazelle worked correctly under Guile 2.0's implementation of Elisp. There aren't so very many Elisp applications that don't depend on the availability of Emacs-specific data structures. You might give it a try if you have a few hours to play around with Guile; you can switch the REPL with ",language elisp" and back with ",L scheme".
Maybe I'll get around to it some time, but its a bit crazy, even for me. I'd rather just port it to Common Lisp and be done with it.
Post a Comment