part 1 of this 2 part series on making your module.exports reusable as standalone programs in JavaScript/CoffeeScript, I explained that this pattern allows for flexible reuse of a function like main
:
var main = function(args) {
console.log(args)
}
module.exports = main
if (module.parent === null) {
main(process.argv)
}
In this article, I’ll show how to make a hubot script that wraps the dig
command into a reusable standalone program. Assuming you’re using Linux, OSX, or another BSD and you have dig installed, let’s get started.
Lets pretend that this would actually be useful, and keep in mind this is just proof of concept code. It isn’t gonna win any prizes for inventiveness, brevity, clarity, or robustness. Indeed, it will fail in a few subtle ways if you actually try to use it, so don’t!
That disclaimer aside, here’s how reusable code looks in a hubot CoffeeScript plugin:
{spawn} = require 'child_process'
util = require 'util'
dig = (args, res) ->
if args.length > 0
domain = args[0].replace(/http(s)?:\/\//gi, '')
cp = spawn 'dig', [domain]
output = ''
cp.stdout.on 'data', (data) ->
output += data.toString()
cp.stderr.on 'data', (data) ->
output += data.toString()
cp.on 'exit', (code) ->
if res
res.reply output
else
console.log output
else
reply = 'Please provide a domain'
if res
res.reply reply
else
console.log reply
module.exports = (robot) ->
robot.respond /dig/i, (res) ->
args = res.envelope.message.text.split(' ').slice(2)
dig args, res
if module.parent is null
dig process.argv.slice(2)
Not much to say here. The wrapper does a little slicing and dicing and just does an A record lookup. Since some incoming messages from places like Slack or XMPP will automatically prepend domain names with http/https
there’s a regex to strip that out.
Invoking via a hubot does what you’d expect:
hubot> hubot-j dig jamon.ca
hubot> Shell:
; <<>> DiG 9.8.3-P1 <<>> jamon.ca
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 57573
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;jamon.ca. IN A
;; ANSWER SECTION:
jamon.ca. 300 IN A 45.32.177.243
;; Query time: 48 msec
;; SERVER: 4.2.2.2#53(4.2.2.2)
;; WHEN: Sun Mar 27 12:25:58 2016
;; MSG SIZE rcvd: 42
And run via the command like, you get the same output:
jamon@vagrant: /tmp/hubot>>
0 % ./node_modules/.bin/coffee scripts/dig.coffee jamon.ca
; <<>> DiG 9.8.3-P1 <<>> jamon.ca
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 54298
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;jamon.ca. IN A
;; ANSWER SECTION:
jamon.ca. 141 IN A 45.32.177.243
;; Query time: 20 msec
;; SERVER: 4.2.2.2#53(4.2.2.2)
;; WHEN: Sun Mar 27 12:28:37 2016
;; MSG SIZE rcvd: 42
The main point is this structure that makes the dig
function reusable in a bot, and as a standalone program:
dig = (args, res) ->
.....
module.exports = (robot) ->
....
if module.parent is null
dig process.arvg.slice(2)
The reason that this works as I mentioned in part 1 is that the module.parent
object does not exist when invoking via the command line. Here’s a full console.dir
listing to demonstrate what I mean:
Module {
id: '.',
exports: [Function],
parent: null,
filename: '/tmp/hubot/scripts/test.coffee',
loaded: false,
children:
[ Module {
id: '/tmp/hubot/node_modules/coffee-script/lib/coffee-script/command.js',
exports: [Object],
parent: [Circular],
filename: '/tmp/hubot/node_modules/coffee-script/lib/coffee-script/command.js',
loaded: true,
children: [Object],
paths: [Object] } ],
paths:
[ '/tmp/hubot/scripts/node_modules',
'/tmp/hubot/node_modules',
'/home/vagrant/projects/node_modules',
'/home/vagrant/node_modules',
'/home/node_modules',
'/node_modules' ] }
The astute reader will also note that the id
attribute is set to .
here. That’s another place where you could check to see how the program is invoked. I prefer checking module.parent
since it is just null or not, but either will work.
And that’s about it. I’m curious to hear what others make of this pattern, if you’ve used it, any pitfalls that I might have overlooked etc.
Feel free to get in touch via the various means over to your right -> -> ->
Cheers! Jamon