Created
August 26, 2012 15:58
-
-
Save mschuerig/3481306 to your computer and use it in GitHub Desktop.
Screenshot script for webpages
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/env phantomjs | |
// Michael Schuerig, <[email protected]>, 2012. | |
// Portions are Copyright 2011 Valeriu Paloş ([email protected]). (see below) | |
// This script requires PhantomJS | |
// http://phantomjs.org/download.html | |
var jQueryUrl = "//ajax.googleapis.com/ajax/libs/jquery/1.8/jquery.min.js"; | |
var page = require('webpage').create(); | |
var system = require('system'); | |
var optionsSchema = [ | |
['j', 'jquery', '', 'Inject jQuery into the page.'], | |
['e', 'exec', '+:', 'Execute script in page context.'], | |
['f', 'file', '+:', 'Execute script file in page context.'], | |
['v', 'viewport', ':', 'Viewport size WxH; default 1280x800.'] | |
]; | |
var usageBanner = "Usage: webshot [OPTIONS]... URL IMAGE_FILE\n" | |
function main(argv) { | |
var options = parseOptions(optionsSchema, argv); | |
if (!options) { | |
phantom.exit(); | |
} | |
var url = options.arguments[1], | |
outFile = options.arguments[2]; | |
setViewportSize(options.viewport); | |
installErrorLogger(); | |
var chain = openPage(url); | |
if (options.jquery) { | |
chain = chain.chain(loadJQuery()); | |
} | |
chain.done(function() { | |
timeout(200).done(function() { | |
// console.debug('Doing something on the page'); | |
var files = options.file || []; | |
for (var i = 0, l = files.length; i < l; i++) { | |
page.injectJs(files[i]); | |
} | |
var scripts = options.exec || []; | |
for (var i = 0, l = scripts.length; i < l; i++) { | |
page.evaluate(new Function(scripts[i])); | |
} | |
page.render(outFile); | |
console.log('A screenshot of ' + url + ' was saved as ' + outFile + '.'); | |
}); | |
}); | |
Deferred.finish(phantom.exit); | |
} | |
function setViewportSize(viewport) { | |
var wh = []; | |
if (viewport) { | |
wh = viewport.split('x'); | |
} | |
wh[0] = wh[0] || 1280 | |
wh[1] = wh[1] || 800; | |
page.viewportSize = { width: wh[0], height: wh[1] }; | |
} | |
function installErrorLogger() { | |
page.onError = function (msg, trace) { | |
console.log(msg); | |
trace.forEach(function(item) { | |
console.log(' ', item.file, ':', item.line); | |
}); | |
} | |
} | |
function Deferred(func) { | |
var k = function() {}, | |
onDone = k, | |
onFail = k, | |
onAlways = k, | |
status = 'pending'; | |
this.done = function(f) { | |
onDone = f; | |
return this; | |
}; | |
this.fail = function(f) { | |
onFail = f; | |
return this; | |
}; | |
this.always = function(f) { | |
onAlways = f; | |
return this; | |
}; | |
this.isResolved = function() { | |
return status === 'resolved'; | |
}; | |
this.chain = function(next) { | |
this.done(function() { | |
// console.debug('chain#done'); | |
next.resolve(); | |
}); | |
return next; | |
} | |
var run = function(success) { | |
// console.debug('Deferred#run:', success); | |
try { | |
if (success) { | |
onDone(); | |
} else { | |
status = 'failed'; | |
onFail(); | |
} | |
} finally { | |
status = 'resolved'; | |
onAlways(); | |
Deferred.startResolving(); | |
} | |
}; | |
Deferred.deferreds.push(this); | |
this.resolve = function() { | |
// console.debug('Deferred#resolve'); | |
if (status === 'pending') { | |
status = 'resolving'; | |
func(run); | |
} | |
} | |
Deferred.startResolving(); | |
} | |
Deferred.deferreds = []; | |
Deferred.onFinish = function() {}; | |
Deferred.finish = function(f) { | |
Deferred.onFinish = f; | |
}; | |
Deferred.unresolved = function() { | |
var unresolved = []; | |
var ds = Deferred.deferreds; | |
for (var i = 0, l = ds.length; i < l; i++) { | |
var d = ds[i]; | |
if (!d.isResolved()) { | |
unresolved.push(d); | |
} | |
} | |
Deferred.deferreds = unresolved; | |
return unresolved; | |
}; | |
Deferred.startResolving = function() { | |
// console.debug('Deferred.startResolving'); | |
if (!Deferred.resolving) { | |
Deferred.resolving = true; | |
window.setTimeout(Deferred.resolve, 0); | |
} | |
}; | |
Deferred.resolve = function() { | |
// console.debug('Deferred.resolve'); | |
var us = Deferred.unresolved(); | |
if (us.length > 0) { | |
us[0].resolve(); | |
Deferred.resolving = false; | |
} else { | |
Deferred.resolving = false; | |
Deferred.onFinish(); | |
} | |
}; | |
function timeout(millis) { | |
return new Deferred(function(run) { | |
window.setTimeout( | |
function() { | |
// console.debug('Timeout'); | |
run(true); | |
}, | |
millis | |
) | |
}); | |
} | |
function openPage(url) { | |
return new Deferred(function(run) { | |
// console.debug('Opening page'); | |
page.open(url, function(status) { | |
if (status !== 'success') { | |
console.log('Unable to load the URL: ' + url); | |
run(false); | |
} else { | |
// console.debug('URL loaded: ' + url); | |
run(true); | |
} | |
}); | |
}); | |
} | |
function loadJQuery() { | |
return new Deferred(function(run) { | |
// console.debug('Loading jQuery'); | |
page.includeJs(jQueryUrl, function() { | |
// console.debug('jQuery loaded'); | |
run(true); | |
}); | |
}); | |
} | |
function parseOptions(schema, argv) { | |
// From https://gist.github.com/982499 | |
/** Command-line options parser (http://valeriu.palos.ro/1026/). | |
Copyright 2011 Valeriu Paloş ([email protected]). All rights reserved. | |
Released as Public Domain. | |
Expects the "schema" array with options definitions and produces the | |
"options" object and the "arguments" array, which will contain all | |
non-option arguments encountered (including the script name and such). | |
Syntax: | |
[«short», «long», «attributes», «brief», «callback»] | |
Attributes: | |
! - option is mandatory; | |
: - option expects a parameter; | |
+ - option may be specified multiple times (repeatable). | |
Notes: | |
- Parser is case-sensitive. | |
- The '-h|--help' option is provided implicitly. | |
- Parsed options are placed as fields in the "options" object. | |
- Non-option arguments are placed in the "arguments" array. | |
- Options and their parameters must be separated by space. | |
- Either one of «short» or «long» must always be provided. | |
- The «callback» function is optional. | |
- Cumulated short options are supported (i.e. '-tv'). | |
- If an error occurs, the process is halted and the help is shown. | |
- Repeatable options will be cumulated into arrays. | |
- The parser does *not* test for duplicate option definitions. | |
// Sample option definitions. | |
var schema = [ | |
['f', 'file', '!:', "Some file we really need.", | |
function(data) { | |
console.log("Hello: " + data); | |
} ], | |
['t', 'test', '!', 'I am needed also.'], | |
['d', '', '', 'Enable debug mode.'], | |
['', 'level', ':', 'Debug level (values 0-4).'], | |
['v', 'verbose', '+', 'Verbosity levels (can be used repeatedly).'], | |
]; | |
*/ | |
// Parse options. | |
try { | |
var tokens = []; | |
var options = {}; | |
var arguments = []; | |
for (var i = 0, item = argv[0]; i < argv.length; i++, item = argv[i]) { | |
if (item.charAt(0) == '-') { | |
if (item.charAt(1) == '-') { | |
tokens.push('--', item.slice(2)); | |
} else { | |
tokens = tokens.concat(item.split('').join('-').split('').slice(1)); | |
} | |
} else { | |
tokens.push(item); | |
} | |
} | |
while (type = tokens.shift()) { | |
if (type == '-' || type == '--') { | |
var name = tokens.shift(); | |
if (name == 'help' || name == 'h') { | |
throw 'help'; | |
continue; | |
} | |
var option = null; | |
for (var i = 0, item = schema[0]; i < schema.length; i++, item = schema[i]) { | |
if (item[type.length - 1] == name) { | |
option = item; | |
break; | |
} | |
} | |
if (!option) { | |
throw "Unknown option '" + type + name + "' encountered!"; | |
} | |
var value = true; | |
if ((option[2].indexOf(':') != -1) && !(value = tokens.shift())) { | |
throw "Option '" + type + name + "' expects a parameter!"; | |
} | |
var index = option[1] || option[0]; | |
if (option[2].indexOf('+') != -1) { | |
options[index] = options[index] instanceof Array ? options[index] : []; | |
options[index].push(value); | |
} else { | |
options[index] = value; | |
} | |
if (typeof(option[4]) == 'function') { | |
option[4](value); | |
} | |
option[2] = option[2].replace('!', ''); | |
} else { | |
arguments.push(type); | |
continue; | |
} | |
} | |
for (var i = 0, item = schema[0]; i < schema.length; i++, item = schema[i]) { | |
if (item[2].indexOf('!') != -1) { | |
throw "Option '" + (item[1] ? '--' + item[1] : '-' + item[0]) + | |
"' is mandatory and was not given!"; | |
} | |
} | |
options.arguments = arguments; | |
} catch(e) { | |
if (e == 'help') { | |
// console.log("Usage: ./«script» «options» «values»\n"); | |
console.log(usageBanner); | |
console.log("Options:"); | |
for (var i = 0, item = schema[0]; i < schema.length; i++, item = schema[i]) { | |
var names = (item[0] ? '-' + item[0] + (item[1] ? '|' : ''): ' ') + | |
(item[1] ? '--' + item[1] : ''); | |
var syntax = names + (item[2].indexOf(':') != -1 ? ' «value»' : ''); | |
syntax += syntax.length < 20 ? new Array(20 - syntax.length).join(' ') : ''; | |
console.log("\t" + (item[2].indexOf('!') != -1 ? '*' : ' ') | |
+ (item[2].indexOf('+') != -1 ? '+' : ' ') | |
+ syntax + "\t" + item[3]); | |
} | |
console.log("\n\t (* mandatory option)\n\t (+ repeatable option)\n"); | |
// process.exit(0); | |
return false; | |
} | |
console.error(e); | |
console.error("Use the '-h|--help' option for usage details."); | |
// process.exit(1); | |
return false; | |
} | |
return options; | |
} | |
main(system.args); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment