I'm getting started with Grunt and wanting to pass a variable to a PhantomJS script I'm running via exec. What I want to be able to do is pass a url in for the script to take the screen capture from. Any help would be greatly appreciated, thanks!
Darren
Grunt script
exec('phantomjs screenshot.js',
function (error, stdout, stderr) {
// Handle output
}
);
screenshot.js
var page = require('webpage').create();
page.open('http://google.com', function () {
page.render('google.png');
phantom.exit();
});
Command-line arguments are accessible via module require('system').args (Module System). The first one is always the script name, which is then followed by the subsequent arguments
This script will enumerate all arguments and write out to console.
var args = require('system').args;
if (args.length === 1) {
console.log('Try to pass some arguments when invoking this script!');
}
else {
args.forEach(function(arg, i) {
console.log(i + ': ' + arg);
});
}
In your case, the solution is
Grunt
exec('phantomjs screenshot.js http://www.google.fr',
function (error, stdout, stderr) {
// Handle output
}
);
screenshot.js
var page = require('webpage').create();
var address = system.args[1];
page.open(address , function () {
page.render('google.png');
phantom.exit();
});
Here is an easy way to pass and pick args that are applicable. Very flexible and easy to maintain.
Use like:
phantomjs tests/script.js --test-id=457 --log-dir=somedir/
OR
phantomjs tests/script.js --log-dir=somedir/ --test-id=457
OR
phantomjs tests/script.js --test-id=457 --log-dir=somedir/
OR
phantomjs tests/script.js --test-id=457
Script:
var system = require('system');
// process args
var args = system.args;
// these args will be processed
var argsApplicable = ['--test-id', '--log-dir'];
// populated with the valid args provided in availableArgs but like argsValid.test_id
var argsValid = {};
if (args.length === 1) {
console.log('Try to pass some arguments when invoking this script!');
} else {
args.forEach(function(arg, i) {
// skip first arg which is script name
if(i != 0) {
var bits = arg.split('=');
//console.log(i + ': ' + arg);
if(bits.length !=2) {
console.log('Arguement has wrong format: '+arg);
}
if(argsApplicable.indexOf(bits[0]) != -1) {
var argVar = bits[0].replace(/\-/g, '_');
argVar = argVar.replace(/__/, '');
argsValid[argVar] = bits[1];
}
}
});
}
// enable below to test args
//require('utils').dump(argsValid);
//phantom.exit();
Related
I'm trying to do in my opinion a simple task but can't figure it out. Can't find it on google or here. Maybe i'm doing it completely wrong.
I need to change a property in a json file with gulp 4. To get the new value of the property I have another task or pipe (I don't care :-)) and want to pass it along.
Also, how to pass variable from command line? so in example: gulp release -branchname "foobar"
How can I do this. I know there are similar question here but nothing is working for me...
var gulp = require('gulp');
var git = require('gulp-git');
var jeditor = require("gulp-json-editor");
var paths = {
manifest: "vss-extension.json"
}
function getBranchName(){
console.log("Get the current branch name");
return git.revParse({args:'--abbrev-ref HEAD'}, function (err, branch) {
console.log('current git branch: ' + branch);
var currentBranch = branch;
return currentBranch;
});
// console.log("Current branch is: "+ branchName);
// return branchName;
}
function updateExtensionManifestVersion(foo){
var x = 10;
gulp.src(paths.manifest)
.pipe(jeditor(function(json) {
console.log("Append current branchname " + currentBranch + " to extension manifest id");
var currentId = json.id;
console.log("Current manifest id: "+ currentId);
// var branchName = git.revParse({args:'--abbrev-ref HEAD'}, function (err, branch) {
// console.log('current git branch: ' + branch);
// return branch;
// });
// console.log(branchName.branch);
var newId = currentId + "-" + branchName;
console.log("New manifest id: " + newId);
json.id = newId;
return json;
}))
.pipe(gulp.dest(paths.manifest));
}
gulp.task('release', gulp.series(getBranchName, updateExtensionManifestVersion, function(done) {
console.log("release is done");
done();
}));
To pass variables from command line you can use yargs as explained in this answer Pass Parameter to Gulp Task.
If you want to pass data between tasks set a global variable that will be accessible to both.
I'm using a Jakefile to help me update Wordpress pages from the command line. I'm using Jake's watch task to re-build ever time I edit a file. When I edit a file with Vim, after the first successful build, Jake fails with following error :
WatchTask started for: default
cp home.html dist/home.html
exec wp --path=../wordpress post list --post_type=page --format=json --fields=ID,post_name { silent: true }
exec wp --path=../wordpress post update 2 dist/home.html --post_type=page
Success: Updated post 2.
jake aborted.
Error: File-task home.html has no existing file, and no action to create one.
at FileBase.isNeeded (/usr/local/lib/node_modules/jake/lib/task/file_task.js:50:17)
at TaskBase.run (/usr/local/lib/node_modules/jake/lib/task/task.js:256:26)
(See full trace by running task with --trace)
I've tried using a sleep function in the rule to delay the rebuild. I tried this because Vim, when saving a file, write the contents to a new temp file and then renames the new temp file to the original file name. I think the build fails because it's trying to build before the file is fully renamed. Using the sleep doesn't work reliably, it may work once or twice but then it fails the same way as above.
Here is my Jakefile:
var shell = require('shelljs');
var sleep = require('sleep');
shell.config.verbose = true;
const destDir = 'dist';
const wpDir = '../wordpress';
var files = new jake.FileList();
files.include('*.html');
var outputFiles = files.toArray().map(function(fileName){
return destDir + '/' + fileName;
});
var sourceFile = function(name) {
return name.substr(name.lastIndexOf('/') + 1);
}
function objectToStr(object) {
var s = '';
for(var property in object){
s += property + ': ' + object[property] + '\n';
}
return s;
}
function rmExt(name) {
return name.substr(0, name.lastIndexOf('.'));
}
directory(destDir);
task('default', [destDir].concat(outputFiles));
task('clean', function() {
jake.rmRf(destDir);
});
rule('dist/%.html', sourceFile, function() {
shell.cp(this.source, this.name);
var pages = JSON.parse(shell.exec('wp --path=' + wpDir
+ ' post list --post_type=page --format=json --fields=ID,post_name',
{ silent: true }).stdout);
var postId = null;
var l = pages.length;
for(var i = 0; i < l; i++){
if(pages[i].post_name === rmExt(this.source)){
postId = pages[i].ID;
break;
}
}
if(postId !== null){
shell.exec('wp --path=' + wpDir + ' post update ' + postId
+ ' ' + this.name + ' --post_type=page');
}else{
shell.echo('Unable to find matching post ID for file: ' + this.name);
}
shell.echo('1');
sleep.sleep(2);
shell.echo('2');
});
watchTask('watch', ['default'], function() {
this.watchFiles.include('*.html');
}
I found a reliable solution using the library node-watch. I replaced the watchTask with a normal Jake task and invoked the task I wanted to call using node-watch to watch the current working directory. The library node-watch is able filter files based on regex so the watch won't be trigger for the Vim swap file and the like. Below is the code I used:
task('watch', function() {
var defaultTask = jake.Task['default'];
defaultTask.invoke();
defaultTask.reenable(true);
watch('./', { filter: /.*.html$/ }, function(evt, name) {
defaultTask.invoke();
defaultTask.reenable(true);
});
});
Note: I also invoked the task I want to call when the watch task is first run to make sure any changes made before the watch started are built.
I'm trying to write a file templating script using Node.js. I have a JSON file called template.json which stores template information. The idea behind my script is that, if I type something like:
tmpl.js java Joe
it will do the following:
Call touch Joe.java
Read template.json to get the template for Java files
Use its information to replace all the placeholders with Joe
Write the result to Joe.java
Execute emacsclient Joe.java
Now, I wrote this script as follows:
#!/usr/local/bin/node --harmony
var templates = require('./config/template.json'),
args = process.argv;
if (args.length < 4) {
console.log("Not enough arguments!");
}
else {
var type = args[2],
name = args[3];
if (type in templates) {
var tmpl = templates[type],
contents = make_output(tmpl["format"],name),
file_name = name + tmpl["extension"],
command = "touch " + file_name + " && echo -e '" + contents +
"' &> " + file_name + " && emacsclient " + file_name;
invoke(command);
}
else {
console.log("No template for %s", type);
}
}
//Helpers
//Invokes comm with args in the terminal, returns all output
//Does not play nice with command redirection
function invoke(comm) {
var exec = require('child_process').exec,
child = exec(comm,
function (error, stdout, stderr) {
if (error !== null) {
console.log(stderr);
}
});
}
//If template is a format string, processes it with x as the
//replacement. Otherwise, just evaluates.
//Limited to one replacement at most.
function make_output(template, x) {
if(/.*\%s.*/i.test(template)) {
var util = require('util');
return util.format(template,x);
}
else {
return template;
}
}
Basically, the command it ends up building is something like:
touch Joe.java && echo -e `bunch of template stuffs` &> Joe.java && emacsclient Joe.java
Now, the problem I am getting is that the above command relies on output redirection, which my invoke command doesn't deal with very well - specifically, everything executes, but I get an empty file! Is there a way I can change either invoke or what I'm constructing to be invoked to avoid this problem?
The issue is that Node's child_process.exec starts sh but you are using features that are peculiar to bash. The &> is interpreted as & > in sh (two operators: a control operator and a redirection operator) and echo -e will use sh's builtin implementation of echo, which does not understand -e.
It would probably be possible to work around the issues above but using the shell like you do is fragile. For instance if your template contains single quotes ('), these quotes may interfere with the single quotes you use in your command. A more robust way to do it would be to change the main part of your code to use fs.writeFileSync rather than using shell commands to write to your file:
var templates = require('./config/template.json'),
fs = require("fs"),
args = process.argv;
if (args.length < 4) {
console.log("Not enough arguments!");
}
else {
var type = args[2],
name = args[3];
if (type in templates) {
var tmpl = templates[type],
contents = make_output(tmpl["format"],name),
file_name = name + tmpl["extension"],
command = "emacsclient " + file_name;
fs.writeFileSync(file_name, contents);
invoke(command);
}
else {
console.log("No template for %s", type);
}
}
You'd also want to modify make_output to perform the transformations that echo -e would have done for you.
I am trying to write a newer watch module that uses the fs.watch method instead of the watchFile approach.
So far, it works beautifully, but only when I run it outside of mocha. I can't figure out why my unit test is throwing a tantrum, maybe someone here can?
Here is my class code:
/**
* requirements
*/
var fs, path, events;
fs = require('fs');
path = require('path');
events = require('events');
/**
* private
*/
var Monitor = function(directory, options) {
this.directory = directory;
this.options = options || {};
(this.options.lazy && this.empty()) || this.walk(this.directory);
this.watch(this.directory);
};
Monitor.prototype = new events.EventEmitter();
Monitor.prototype.watch = function(directory, stats) {
var stats = stats || {};
if (!this.directories[directory]) {
var w = fs.watch(directory, this.options, this.justlookatit.bind(this));
}
this.directories[directory] = { 'stats': stats, 'w': w };
};
Monitor.prototype.directories = function() {
if (!Object.keys(this.directories).length) {
this.walk(this.directory);
}
return this.directories;
};
Monitor.prototype.files = function() {
if (!Object.keys(this.files).length) {
this.walk(this.directory);
}
return this.files;
};
Monitor.prototype.unwatch = function() {
if (!Object.keys(this.directories).length) {
for (var dir in this.directories) {
dir.w.close();
}
}
};
Monitor.prototype.empty = function() {
this.unwatch();
this.files = {};
this.directories = {};
};
Monitor.prototype.walk = function(directory) {
var monitor = this;
this.empty();
fs.readdir(directory, function(err, files) {
if (err) return;
for (var file in files) {
var fullname = path.resolve(files[file]);
if (!monitor.options.filter || monitor.options.filter(fullname)) {
fs.stat(fullname, function(err, stats) {
if (err) return;
if (stats.isDirectory()) {
monitor.walk(fullname);
monitor.watch(fullname, stats);
} else {
monitor.files[fullname] = stats;
}
});
}
}
});
};
Monitor.prototype.justlookatit = function(action, file) {
var monitor = this;
var fullname = path.resolve(file);
if (this.options.filter && !this.options.filer(fullname)) return;
fs.exists(fullname, function(exists) {
if (exists) {
fs.stat(fullname, function(err, stats) {
if (stats.isDirectory()) {
monitor.watch(fullname, stats);
} else {
if (monitor.files[fullname]) {
if (stats.mtime.getTime() > monitor.files[fullname].mtime.getTime()) {
monitor.emit('modified', fullname, stats);
}
} else {
monitor.emit('added', fullname, stats);
}
monitor.files[fullname] = stats;
}
});
} else {
if (monitor.files[fullname]) {
delete monitor.files[fullname];
monitor.emit('deleted', fullname);
} else if (monitor.directories[fullname]) {
monitor.directories[fullname].w.close();
delete monitor.directories[fullname];
}
}
});
};
/**
* exports
*/
exports.start = function(directory, options) {
return new Monitor(directory, options);
};
Here is my Working external test code:
var watch = require("./watch.js");
var fs = require('fs');
monitor = watch.start(__dirname);
monitor.on('added', function(file, stats) {
console.log("Caught Added: " + file);
});
monitor.on('modified', function(file, stats) {
console.log("Caught Modified: " + file);
});
monitor.on('deleted', function(file) {
console.log("Caught deleted: " + file);
});
// try creating a file immediately
fs.openSync('v.md', 'w');
The first test file runs perfectly fine, and I've tried both openSync and open. Finally, here is a version of the same test code, wrapped in a mocha unit test which is timing out:
/**
* requirements
*/
var watch, Q, fs, path, mocha, chai, assert;
watch = require('../lib/watch.js');
Q = require('q');
fs = require('fs');
path = require('path');
mocha = require('mocha');
chai = require('chai');
assert = chai.assert;
/**
* variables
*/
var watch_directory = path.join(__dirname, './watch');
/**
* tests
*/
describe('test watch', function() {
it('should create a monitor and run callbacks after fs changes', function(done) {
// I had planned to implement promises that chained the three callbacks
// but couldn't get one of them working in general
var added = function(file, stats) {
console.log("added");
done();
};
var modified = function(file, stats) {
console.log("modified");
};
var deleted = function(file, stats) {
console.log("deleted");
};
// create our service
var monitor = watch.start(watch_directory);
// assert it is defined
assert.isDefined(monitor);
// establish a listener
monitor.on('added', added);
monitor.on('modified', modified);
monitor.on('deleted', deleted);
// here is a file name using the current date to prevent duplication during tests
var file = path.join(watch_directory, (new Date()).getTime() + '.md');
// let's create the file, then delete it
fs.open(file, 'w+', function(err, fileDescriptor) {
// this prints before console output from the watch.js's `justlookatit` method
console.log(err);
console.log("writing to file");
// we probably don't want to try closing the fileDescriptor if the open failed
if (err) return;
// close the file descriptor
fs.close(fileDescriptor, function() {
// delete the file we just created
// fs.unlink(file, function() { /* not a big deal */ });
});
});
// modify a known-existing test file
fs.open('test.md', 'w+', function() {/* we don't care about this */});
})
});
I checked with console.log(fullname) inside the justlookatit method on the watch code, and it spits out the correct file name, matching the one generated by the unit test.
However, it then proceeds to return false when I run fs.exists. As I undestand it, that means the file system is notifying me that a file exists before it exists, which doesn't make sense really. So I tried adding an additional delay by wrapping my fs.exists method in a setTimeout, and that didn't change the results. I have also tried using both openSync and existsSync, and that made no difference.
I'm stumped, does anyone have any ideas why the mocha code isn't working?
So, the solution was to go for a walk. I came back, looked at the code again and figured out the cause of the problem with mocha, and also identified many other bugs.
The problem was the lack of context. The justlookatit method does not have a context, and in the test.js scenario it is watching the current directory, while the mocha test is watching a sub-directory.
The path.resolve was receiving only the file name, not the directory, and therefore merged it with the default (executables) directory, so the level of test.js, or watch_test.js for mocha. It proceeded to fail to locate any of the files in the mocha test case because they were all one level below the executable.
I won't go into detail about all the other bugs, but I may come back and post the repository link when I get to a point that I want to push it online.
You're missing the callback return(done); at the end of your test. Unless you call that callback, Mocha will time out every time.
Which is the simplest way to compare a hash of a file without storing it in a database?
For example:
var filename = __dirname + '/../public/index.html';
var shasum = crypto.createHash('sha1');
var s = fs.ReadStream(filename);
s.on('data', function(d) {
shasum.update(d);
});
s.on('end', function() {
var d = shasum.digest('hex');
console.log(d + ' ' + filename);
fs.writeFile(__dirname + "/../public/log.txt", d.toString() + '\n', function(err) {
if(err) {
console.log(err);
} else {
console.log("The file was saved!");
}
});
});
The above code returns the hash of the HTML file. If I edit the file how can I know if it has been changed? In other words, how can I know if the hash has been changed?
Any suggestions?
Edited
Now the hash is being saved in the log file. How can I retrieve the hash from the file and match it with the new generated one? A code example would be awesome to give me a better understanding.
There is no difference with this question, but it isn't clear for me yet how to implement it.
If you're looking for changes on a file, then you can use one of Node's filesystem functions, fs.watch. This is how it's used:
fs.watch(filename, function (event, filename) {
//event is either 'rename' or 'change'
//filename is the name of the file which triggered the event
});
The watch function is however not very consistent, so you can use fs.watchFile as an alternative. fs.watchFile uses stat polling, so it's quite a bit slower than fs.watch, which detects file changes instantly.
Watching a file will return an instance of fs.FSWatcher, which has the events change and error. Calling .close will stop watching for changes on the file.
Here's an example relating to your code:
var filename = __dirname + '/../public/index.html';
var shasum = crypto.createHash('sha1');
var oldhash = null;
var s = fs.ReadStream(filename);
s.on('data', function(d) {
shasum.update(d);
});
s.on('end', function() {
var d = shasum.digest('hex');
console.log(d + ' ' + filename);
oldhash = d.toString();
fs.writeFile(__dirname + "/../public/log.txt", d.toString() + '\n', function(err) {
if(err) {
console.log(err);
}
else {
console.log("The file was saved!");
}
});
});
//watch the log for changes
fs.watch(__dirname + "/../public/log.txt", function (event, filename) {
//read the log contents
fs.readFile(__dirname + "/../public/log.txt", function (err, data) {
//match variable data with the old hash
if (data == oldhash) {
//do something
}
});
});
What's the difference between this question and the previous one you asked? If you're not wanting to store it in a database, then store it as a file. If you want to save the hash for multiple files, then maybe put them in a JSON object and write them out as a .json file so they're easy to read/write.
EDIT
Given what you added to your question, it should be pretty simple. You might write a function to do check and re-write:
function updateHash (name, html, callback) {
var sha = crypto.createHash('sha1');
sha.update(html);
var newHash = sha.digest('hex');
var hashFileName = name + '.sha';
fs.readFile(hashFileName, 'utf8', function (err, oldHash) {
var changed = true;
if (err)
console.log(err); // probably indicates the file doesn't exist, but you should consider doing better error handling
if (oldHash === newHash)
changed = false;
fs.writeFile(hashFileName, newHash, { encoding: 'utf8' }, function (err) {
callback(err, changed);
});
});
}
updateHash('index.html', "<html><head><title>...", function (err, isChanged) {
// do something with this information ?
console.log(isChanged);
});