Skip to content

Instantly share code, notes, and snippets.

@mschuerig
Created August 26, 2012 15:58
Show Gist options
  • Save mschuerig/3481306 to your computer and use it in GitHub Desktop.
Save mschuerig/3481306 to your computer and use it in GitHub Desktop.
Screenshot script for webpages
#! /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