cli.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. #!/usr/bin/env node
  2. var fs = require('fs'),
  3. path = require('path'),
  4. commander = require('commander'),
  5. async = require('async'),
  6. glob = require('glob'),
  7. mkdirp = require('mkdirp'),
  8. typogr = require('./typogr');
  9. function isDirectory(pathString) {
  10. try {
  11. // Query the entry
  12. return fs.statSync(pathString).isDirectory();
  13. }
  14. catch (error) {
  15. // if file doesn't exist, statSync throws an error instead of just returning false
  16. return false;
  17. }
  18. }
  19. // path.dirname and path.basename don't always work right, so we have to create our own functions
  20. function dirname(pathString) {
  21. return pathString.substring(0, pathString.lastIndexOf('/') + 1);
  22. }
  23. function basename(pathString) {
  24. return pathString.substring(pathString.lastIndexOf('/') + 1);
  25. }
  26. function readRenderWrite(input, output, force) {
  27. input = path.normalize(input);
  28. output = path.normalize(output);
  29. if(isDirectory(input))
  30. input = input + '/**'; // add glob to get all files under directory
  31. var inputDirectory = dirname(input),
  32. inputGlob = basename(input),
  33. after,
  34. outputString;
  35. // process input
  36. glob(inputGlob, { cwd: inputDirectory }, function (error, files) {
  37. async.eachSeries(files, function (file, eachCallback) {
  38. // only process files; other types such as directories or links are ignored
  39. fs.stat(inputDirectory + '/' + file, function (error, stats) {
  40. if(stats.isFile()) {
  41. // read file
  42. fs.readFile(inputDirectory + '/' + file, 'utf8', function (error, before) {
  43. // process the html
  44. after = typogr.typogrify(before);
  45. // write file
  46. if(output === '/dev/stdout') // writeFile doesn't like /dev/stdout even though readFile works with /dev/stdin
  47. console.log(after);
  48. else {
  49. if(files.length > 1) {
  50. // multiple input files means we are dealing with a directory or a glob,
  51. // so make sure directory exists
  52. mkdirp.sync(output + '/' + dirname(file));
  53. outputString = path.normalize(output + '/' + file);
  54. }
  55. else // one input file means we are only writing one file
  56. outputString = output;
  57. if(force) {
  58. fs.writeFile(outputString, after, function (error) {
  59. eachCallback(error);
  60. });
  61. }
  62. else {
  63. confirmOverwrite(outputString, function (ok) {
  64. if(ok)
  65. fs.writeFile(outputString, after, function (error) {
  66. eachCallback(error);
  67. });
  68. });
  69. }
  70. }
  71. });
  72. }
  73. else
  74. eachCallback(null);
  75. });
  76. }, function (error) {
  77. if(error) throw error;
  78. });
  79. });
  80. }
  81. function confirmOverwrite(fileName, callback) {
  82. fs.stat(fileName, function(error, stats) {
  83. if(stats) {
  84. if(stats.isFile()) {
  85. commander.confirm('Confirm: overwrite file (' + fileName + ')? ', function(ok) {
  86. // work around bug in commander.js
  87. // see (https://github.com/visionmedia/commander.js/issues/109)
  88. // also see (https://github.com/visionmedia/commander.js/pull/133)
  89. process.stdin.pause();
  90. if(ok)
  91. callback(true);
  92. else {
  93. console.log(" Cancelling action, file wasn't modified.");
  94. callback(false);
  95. }
  96. });
  97. }
  98. else {
  99. // fileName is not of type 'file' (close our eyes and assume it's a directory)
  100. callback(true);
  101. }
  102. }
  103. else {
  104. // fileName doesn't exist
  105. callback(true);
  106. }
  107. });
  108. }
  109. function main() {
  110. // specify cli options
  111. commander
  112. .version('0.6.8')
  113. .usage('[options] [input] [output]')
  114. .option('-i, --inplace', 'Use single path as both input and output')
  115. .option('-f, --force', 'Do not prompt to verify file overwrites');
  116. // specify example help text
  117. commander.on('--help', function() {
  118. console.log(' reads input from stdin, individual files, directories, or globs');
  119. console.log(' writes ouput to stdout, individual files, or directories');
  120. console.log('');
  121. console.log(' Examples:');
  122. console.log('');
  123. console.log(' $ typogr inputFile.html outputFile.html');
  124. console.log(' $ typogr < inputFile.html > outputFile.html');
  125. console.log(' $ typogr -i singleFile.html');
  126. console.log(' $ typogr inputDirectory outputDirectory');
  127. console.log(' $ typogr inputDirectory/*.html outputDirectory');
  128. console.log('');
  129. });
  130. // parse arguments and generate object mappings in the *commander* object
  131. commander.parse(process.argv);
  132. switch (commander.args.length) {
  133. case 0:
  134. // get input from stdin and send output to stdout
  135. readRenderWrite('/dev/stdin', '/dev/stdout');
  136. break;
  137. case 1:
  138. if (commander.inplace) {
  139. // modify the file in place
  140. readRenderWrite(commander.args[0], commander.args[0], commander.force);
  141. }
  142. else {
  143. // get input from file specified and send output to stdout
  144. readRenderWrite(commander.args[0], '/dev/stdout');
  145. }
  146. break;
  147. case 2:
  148. // get input from first parameter and send output to second parameter
  149. readRenderWrite(commander.args[0], commander.args[1], commander.force);
  150. break;
  151. }
  152. }
  153. module.exports.main = main;