Browse files

Sed mode for jspatch

Presently, sed mode is the best way to use this tool, because the patch file
format is dumb.
  • Loading branch information...
1 parent 0379cbe commit 759c7c1d862591cd4b936cafac8a0054342d2f3d Ryan Patterson committed Jan 11, 2012
Showing with 113 additions and 46 deletions.
  1. +37 −0 README.mdown
  2. +76 −46 jspatch
View
37 README.mdown
@@ -44,13 +44,50 @@ matched variable.
Jsgrep allows you to search object initializations partially, which enables
easily drilling into the structure of JavaScript classes.
+
+## jspatch
+
+Jsgrep allows you to identify source code that resembles a certain pattern.
+Jspatch is a tool to take that information and programmatically modify the
+original source code.
+
+**Replace setTimeout with window.setTimeout**
+
+ $ ./jspatch -e 's/setTimeout( A, B )/window.setTimeout( A, B )/' tests/jquery.js
+ --- tests/jquery.js 2012-01-10 14:51:36.000000000 -0800
+ +++ /tmp/jspatch.jquery.js 2012-01-11 17:13:56.000000000 -0800
+ @@ -420,7 +420,7 @@
+ if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) {
+ // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+ if ( !document.body ) {
+ - return setTimeout( jQuery.ready, 1 );
+ + return window.setTimeout( jQuery.ready, 1 );
+ }
+
+ // Remember that the DOM is ready
+ @@ -1987,7 +1987,7 @@
+ ( src === "mark" || !jQuery._data(elem, markDataKey) ) ) {
+ // Give room for hard-coded callbacks to fire first
+ // and eventually mark/queue something else on the element
+ - setTimeout( function() {
+ + window.setTimeout( function() {
+ if ( !jQuery._data( elem, queueDataKey ) &&
+ !jQuery._data( elem, markDataKey ) ) {
+ jQuery.removeData( elem, deferDataKey, true );
+
+Sed mode allows you to quickly perform simple substitutions on the source code.
+
## TODO
* Consider a metavar that matches but doesn't save, to prevent ambiguities.
* Consider support for --where/--eval and/or not-patterns
* Support for most statements in patterns
* Formally testing the pattern matchers
+jspatch:
+
+* Make the substitution work at the tokenizer level rather than the AST level.
+
## Contributors
Jsgrep was written by Ryan Patterson at Facebook. Inspiration for this project
View
122 jspatch
@@ -12,6 +12,7 @@ var config = {
paths: [ ],
recursive: false,
patchFile: false,
+ sedMode: false,
asJavascript: false
};
@@ -27,14 +28,15 @@ function usage(hasError) {
console.log("Options:");
console.log(" -f, --patch-script=FILE Path to patch script.");
console.log(" -J, --javascript Interpret PATCHFILE as a match script.");
+ console.log(" -e, --expression=SCRIPT Sed mode. SCRIPT is in the form of s/A/B/");
console.log(" -r, --recursive Scan directories for JavaScript files.");
process.exit(0);
}
(function parseArgs() {
var getopt = require('node-getopt');
var parser = new getopt.BasicParser(
- 'f:(match-script)J(javascript)r(recursive)_(help)',
+ 'f:(match-script)J(javascript)e:(expression)r(recursive)_(help)',
process.argv);
while ((option = parser.getopt()) !== undefined) {
@@ -45,6 +47,9 @@ function usage(hasError) {
case 'J':
config.asJavascript = true;
break;
+ case 'e':
+ config.sedMode = option.optarg;
+ break;
case 'r':
config.recursive = true;
break;
@@ -57,7 +62,7 @@ function usage(hasError) {
config.paths = process.argv.slice(parser.optind());
- if (!config.patchFile) {
+ if (!config.patchFile && !config.sedMode) {
if (config.paths.length > 0) {
config.patchFile = config.paths.shift();
} else {
@@ -73,60 +78,74 @@ var matchFn;
function processPatch(patchSource) {
var lines = patchSource.split('\n');
- var inReplacement = false;
- var topPattern = [];
- var find = [];
- var replace = [];
-
- for (var i = 0; i < lines.length; i++) {
- if (lines[i][0] == '-') {
- if (!inReplacement) {
- if (find.length > 0) {
- // XXX: due to the way I compile the patch, each replacement would be
- // independent of one another, which would be confusing.
- console.error('jspatch: Only 1 replacement is permitted per chunk.');
- process.exit(1);
- }
- inReplacement = true;
- topPattern.push('A_jspatchAutovar');
- } else {
- topPattern.push('');
- }
- find.push(lines[i].substr(1));
+ var i = 0;
+ var chunks = [];
- } else if (lines[i][0] == '+') {
- if (!inReplacement) {
- console.error(
- 'jspatch: Insertions are not yet supported, only replacements.');
- process.exit(1);
- }
- topPattern.push('');
- replace.push(lines[i].substr(1));
+ function processChunk() {
+ var chunk = { top: [], find: [], replace: [] };
+ var inReplacement = false;
- } else {
- if (inReplacement) {
- if (replace.length == 0) {
+ for(; i < lines.length; i++) {
+ if (lines[i] == '---') {
+ break;
+ } else if (lines[i][0] == '-') {
+ if (!inReplacement) {
+ if (chunk.find.length > 0) {
+ // XXX: due to the way I compile the patch, each replacement would be
+ // independent of one another, which would be confusing.
+ console.error('jspatch: Only 1 replacement is permitted per chunk.');
+ process.exit(1);
+ }
+ inReplacement = true;
+ chunk.top.push('A_jspatchAutovar');
+ }
+ chunk.find.push(lines[i].substr(1));
+
+ } else if (lines[i][0] == '+') {
+ if (!inReplacement) {
console.error(
- 'jspatch: Removals are not yet supported, only replacements.');
+ 'jspatch: Insertions are not yet supported, only replacements.');
process.exit(1);
}
- inReplacement = false;
+ chunk.replace.push(lines[i].substr(1));
+
+ } else {
+ if (inReplacement) {
+ if (chunk.replace.length == 0) {
+ console.error(
+ 'jspatch: Removals are not yet supported, only replacements.');
+ process.exit(1);
+ }
+ inReplacement = false;
+ }
+ chunk.top.push(lines[i]);
}
- topPattern.push(lines[i]);
}
+
+ chunk.top = chunk.top.join('\n');
+ chunk.find = chunk.find.join('\n');
+ chunk.replace = chunk.replace.join('\n');
+
+ chunks.push(chunk);
}
- topPattern = topPattern.join('\n');
- find = find.join('\n');
- replace = replace.join('\n');
+ while (i < lines.length) {
+ processChunk();
+ }
- return function(jsgrep) {
- jsgrep.find(topPattern, function(vars) {
- vars.A_jspatchAutovar.match(find, function(vars) {
- vars.node.replaceWith(replace);
+ function generateMatchFn(chunks) {
+ return function(jsgrep) {
+ _.each(chunks, function(chunk) {
+ jsgrep.find(chunk.top, function(vars) {
+ vars.A_jspatchAutovar.match(chunk.find, function(vars) {
+ vars.node.replaceWith(chunk.replace);
+ });
+ });
});
- });
- };
+ };
+ }
+
+ return generateMatchFn(chunks);
}
function generateDiff(oldFilename, newFile, callback) {
@@ -246,7 +265,18 @@ function jspatch_loop() {
}
}
-if (config.asJavascript) {
+if (config.sedMode) {
+ // XXX hurr hurr can't sed mode division, paths in string literals, blah blah
+ var sedMode = config.sedMode.split('/');
+ if (sedMode.length != 4 || sedMode[0] != 's' || sedMode[3] != '') {
+ console.error('jspatch: Pattern for sed mode must be in the form of ' +
+ '"s/search/replace/".');
+ process.exit(1);
+ }
+ matchFn = function(jsgrep) {
+ jsgrep.replaceAll(sedMode[1], sedMode[2]);
+ };
+} else if (config.asJavascript) {
matchFn = config.patchFile;
} else {
var patchSource = fs.readFileSync(config.patchFile).toString();

0 comments on commit 759c7c1

Please sign in to comment.