Last active
August 29, 2015 14:27
-
-
Save tooolbox/bbd1771c86f6b5567250 to your computer and use it in GitHub Desktop.
NodeJS for KF7 CSS Processing
This file contains hidden or 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 node | |
// The above "shebang" tells the bash terminal to run this script as a Node.js script. | |
// Import NodeJS packages | |
var argv = require('yargs').argv; // for processing command-line arguments | |
var fs = require("fs"); // filesystem | |
var wrench = require('wrench'); // for deep recursive copying | |
var css = require('css'); // for parsing CSS into an AST | |
var walk = require('rework-walk'); // for walking ASTs generated by the css module | |
var cheerio = require('cheerio'); // jquery-like access to an HTML document | |
// Define an "ends with" method that is useful later. | |
if (typeof String.prototype.endsWith !== 'function') { | |
String.prototype.endsWith = function(suffix) { | |
return this.indexOf(suffix, this.length - suffix.length) !== -1; | |
}; | |
} | |
// Fetches all files in a folder, recursively | |
function filesRecursive(dir) { | |
var results = []; | |
var list = fs.readdirSync(dir); | |
list.forEach(function(file) { | |
file = dir + '/' + file; | |
var stat = fs.statSync(file); | |
if (stat && stat.isDirectory()) { | |
results = results.concat(walk(file)); | |
} else { | |
results.push(file); | |
} | |
}); | |
return results; | |
} | |
// Same as filesRecursive, but filters for files that end with an ext, such as ".css" | |
function filesRecursiveWithExt(dir, ext){ | |
var results = walk(dir); | |
return results.reduce(function(filteredContents, filename){ | |
if (filename.endsWith(ext)) { | |
filteredContents.push(filename); | |
} | |
return filteredContents; | |
}, []); | |
} | |
// Convenience function: | |
// reads a file, calls a function that you give it, | |
// and then writes back to the file whatever your function returns | |
function readWrite(filePath, callback){ | |
var content = ''; | |
var stats = fs.statSync(filePath); | |
if (!stats.isDirectory()) { | |
content = fs.readFileSync(filePath).toString(); | |
} | |
if (callback) { | |
content = callback(content, stats); | |
if (!stats.isDirectory() && content) { | |
fs.writeFileSync(filePath, content); | |
} | |
} else { | |
return content; | |
} | |
} | |
///////////////////// | |
// MAIN SCRIPT STARTS | |
///////////////////// | |
// Get the path to the epub directory, as an argument | |
var targetDirectory = argv._[0]; | |
if (!targetDirectory) { | |
console.log("Please specify the directory of the epub you want to work with."); | |
} | |
var scriptDir = path.dirname(require.main.filename); // current directory of the main script | |
var resolvedTargetDir = path.resolve(targetDirectory); | |
// Clone the directory so we don't taint the original | |
var newDirectory = resolvedTargetDir + '_kf7'; | |
wrench.copyDirSyncRecursive(resolvedTargetDir, newDirectory, { | |
forceDelete: true // overwrites any "_kf7" directory that's already there | |
}); | |
// Simplify our CSS rules | |
var complexSelectorMap = {}; | |
var cssFiles = filesRecursiveWithExt(newDirectory, "css"); | |
cssFiles.forEach(function(filename){ | |
readWrite(filename, function(content, stats){ | |
if (!content) return; | |
// Parse the CSS into an AST that can be walked | |
var ast = css.parse(content); | |
// Walk the AST | |
walk(ast.stylesheet, function(rule, node){ | |
if (!rule.selectors) return; | |
var remove = []; | |
var add = []; | |
rule.selectors.forEach(function(sel, idx){ | |
// If the selector contains a space, it's too complex for Kindlegen | |
var parts = sel.split(" "); | |
if (parts.length > 1) { | |
// Create a simplified version of the selector | |
var newSel = '.' + parts.join('-').replace('#', '-id-').replace('.', '-clz-').replace('+', '-adj-').replace('~', '-pre-').replace('[', '-lbr-').replace(']', '-rbr-').replace(/-{2,}/, '-'); | |
// Add it to our list to add to this rule | |
add.push(newSel); | |
// Map the complex selector to the simplified version (for later adjustments we do in the markup) | |
complexSelectorMap[sel] = newSel; | |
// Add this selector to our list to remove | |
remove.push(idx); | |
} | |
}); | |
// Remove the complex selectors, if any. | |
// Note that we go backwards through the list, otherwise our indexes will be messed up. | |
for (var i = remove.length - 1; i >= 0; i--) { | |
rule.selectors.splice(remove[i], 1); | |
}; | |
// Add the simplified selectors, if any | |
rule.selectors = rule.selectors.concat(add); | |
}); | |
// Stringify the modified AST and return it, so it gets written back to the file | |
return css.stringify(ast); | |
}); | |
}); | |
// Now convert everything in the markup to the simpler selectors, as it were | |
var compoundClassMap = {}; | |
var htmlFiles = filesRecursiveWithExt(newDirectory, "html"); // will include "xhtml" | |
htmlFiles.forEach(function(filename){ | |
readWrite(filename, function(content, stats){ | |
if (!content) return; | |
// Load up everything into Cheerio! | |
var $ = cheerio.load(content, { | |
xmlMode: true | |
}); | |
// Find everything that the complex selectors applied to, and stick the simpler class on them | |
Object.keys(complexSelectorMap).forEach(function(key) { | |
$(key).addClass(complexSelectorMap[key]); // ...Well, that was easy. | |
}); | |
return $.xml(); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
More fully fledged version: https://github.com/allscribe/allscribe