diff --git a/.npmignore b/.npmignore
index 0bbc5b50e..8cefadb96 100644
--- a/.npmignore
+++ b/.npmignore
@@ -1,3 +1,5 @@
node_modules
coverage
-npm-debug.log
\ No newline at end of file
+npm-debug.log
+src
+test
\ No newline at end of file
diff --git a/CHANGE.md b/CHANGE.md
index 5fce98cf8..b64df674e 100644
--- a/CHANGE.md
+++ b/CHANGE.md
@@ -1,16 +1,17 @@
HTMLHint change log
====================
-## ver 0.9.10 (2015-10-9)
+## ver 0.9.10 (2015-10-10)
add:
1. attr-unsafe-chars(rule): show unsafe code in message
+2. support glob pattern for cli
fix:
1. title-require(rule): report error when `
test`
-1. title-require(rule): report error when ``
+2. title-require(rule): report error when ``
## ver 0.9.9 (2015-10-9)
diff --git a/README.md b/README.md
index 2cdcae2af..0d52507be 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,9 @@ Quick start
npm install htmlhint -g
htmlhint -V
htmlhint --help
- htmlhint test.html
+ htmlhint www
+ htmlhint www/test.html
+ htmlhint www/**/*.xhtml
2. results
diff --git a/bin/htmlhint b/bin/htmlhint
index 8f7f4c1be..e2662ddc1 100755
--- a/bin/htmlhint
+++ b/bin/htmlhint
@@ -5,6 +5,8 @@ var fs = require('fs');
var path = require('path');
var stripJsonComments = require('strip-json-comments');
var async = require('async');
+var glob = require("glob");
+var parseGlob = require('parse-glob');
var HTMLHint = require("../index").HTMLHint;
var pkg = require('../package.json');
@@ -14,8 +16,8 @@ require('colors');
function map(val) {
var objMap = {};
val.split(',').forEach(function(item){
- var arrItem = item.split(/\s*=\s*/);
- objMap[arrItem[0]] = arrItem[1]?arrItem[1]:true;
+ var arrItem = item.split(/\s*=\s*/);
+ objMap[arrItem[0]] = arrItem[1]?arrItem[1]:true;
});
return objMap;
}
@@ -23,19 +25,25 @@ function map(val) {
program.on('--help', function(){
console.log(' Examples:');
console.log('');
+ console.log(' htmlhint');
+ console.log(' htmlhint www');
+ console.log(' htmlhint www/test.html');
+ console.log(' htmlhint www/**/*.xhtml');
console.log(' htmlhint --list');
console.log(' htmlhint --rules tag-pair,id-class-value=underline test.html');
console.log(' htmlhint --config .htmlhintrc test.html');
+ console.log(' htmlhint --ignore **/build/**,**/test/**');
console.log('');
});
program
.version(pkg.version)
- .usage(' [options]')
+ .usage(' [options]')
.option('-l, --list', 'show all of the rules available.')
.option('-c, --config ', 'custom configuration file.')
.option('-r, --rules ', 'set all of the rules available.', map)
.option('-j, --json', 'output messages as raw JSON')
+ .option('-i, --ignore ', 'Add pattern to exclude matches')
.parse(process.argv);
if(program.list){
@@ -45,18 +53,19 @@ if(program.list){
var arrTargets = program.args;
if(arrTargets.length === 0){
- arrTargets.push(process.cwd());
+ arrTargets.push('.');
}
hintTargets(arrTargets, {
ruleset: program.rules,
- json: program.json
+ json: program.json,
+ ignore: program.ignore
});
// list all rules
function listRules(){
var rules = HTMLHint.rules;
- var rule;
+ var rule;
console.log(' All rules:');
console.log(' ==================================================');
for (var id in rules){
@@ -67,7 +76,9 @@ function listRules(){
function hintTargets(arrTargets, options){
var allFileCount = 0;
+ var allHintFileCount = 0;
var allHintCount = 0;
+ var startTime = new Date().getTime();
// json mode
var json = options.json;
@@ -78,28 +89,27 @@ function hintTargets(arrTargets, options){
}
var arrTasks = [];
arrTargets.forEach(function(target){
- target = path.resolve(target);
- if(fs.existsSync(target)){
- arrTasks.push(function(next){
- hintAllFiles(target, options, function(result){
- allFileCount += result.targetFileCount;
- allHintCount += result.targetHintCount;
- arrJson = arrJson.concat(result.arrJson);
- next();
- });
+ arrTasks.push(function(next){
+ hintAllFiles(target, options, function(result){
+ allFileCount += result.targetFileCount;
+ allHintFileCount += result.targetHintFileCount;
+ allHintCount += result.targetHintCount;
+ arrJson = arrJson.concat(result.arrJson);
+ next();
});
- }
+ });
});
async.series(arrTasks, function(){
if(json){
console.log(JSON.stringify(arrJson));
}
else{
+ var spendTime = new Date().getTime() - startTime;
if(allHintCount > 0){
- console.log('%d errors in %d files'.red, allHintCount, allFileCount);
+ console.log('Scan %d files, found %d errors in %d files (%d ms)'.red, allFileCount, allHintCount, allHintFileCount, spendTime);
}
else{
- console.log('Done, without errors.'.green);
+ console.log('Scan %d files, without errors (%d ms).'.green, allFileCount, spendTime);
}
}
process.exit(allHintCount > 0 ? 1: 0);
@@ -108,8 +118,12 @@ function hintTargets(arrTargets, options){
// hint all files
function hintAllFiles(target, options, onFinised){
+ var globInfo = getGlobInfo(target);
+ globInfo.ignore = options.ignore;
+
// hint count
var targetFileCount = 0;
+ var targetHintFileCount = 0;
var targetHintCount = 0;
// json mode
@@ -119,7 +133,7 @@ function hintAllFiles(target, options, onFinised){
// init ruleset
var ruleset = options.ruleset;
if(ruleset === undefined){
- ruleset = getConfig(program.config, target, json);
+ ruleset = getConfig(program.config, globInfo.base, json);
}
// hint queue
@@ -131,7 +145,7 @@ function hintAllFiles(target, options, onFinised){
arrJson.push({'file': filepath, 'messages': messages});
}
else{
- console.log(' '+path.relative(process.cwd(), filepath).white);
+ console.log(' '+filepath.white);
messages.forEach(function(hint){
var leftWindow = 40;
var rightWindow = leftWindow + 20;
@@ -166,28 +180,22 @@ function hintAllFiles(target, options, onFinised){
});
console.log('');
}
- targetFileCount ++;
+ targetHintFileCount ++;
targetHintCount += hintCount;
}
+ targetFileCount ++;
setImmediate(next);
}, 10);
// start hint
var isWalkDone = false;
var isHintDone = true;
- var stats = fs.statSync(target);
- if(stats.isDirectory()){
- walkPath(target, function onPath(filepath){
- isHintDone = false;
- hintQueue.push(filepath);
- }, function onFinised(){
- isWalkDone = true;
- checkAllHinted();
- });
- }
- else{
+ walkPath(globInfo, function(filepath){
+ isHintDone = false;
+ hintQueue.push(filepath);
+ }, function(){
isWalkDone = true;
- hintQueue.push(target);
- }
+ checkAllHinted();
+ });
hintQueue.drain = function() {
isHintDone = true;
checkAllHinted();
@@ -196,6 +204,7 @@ function hintAllFiles(target, options, onFinised){
if(isWalkDone && isHintDone){
onFinised({
targetFileCount: targetFileCount,
+ targetHintFileCount: targetHintFileCount,
targetHintCount: targetHintCount,
arrJson: arrJson
});
@@ -203,20 +212,53 @@ function hintAllFiles(target, options, onFinised){
}
}
+// split target to base & glob
+function getGlobInfo(target){
+ // fix windows sep
+ target = target.replace(/\\/g, '/');
+ var globInfo = parseGlob(target);
+ var base = globInfo.base;
+ base += /\/$/.test(base) ? '' : '/';
+ var pattern = globInfo.glob;
+ var globPath = globInfo.path;
+ var defaultGlob = '*.{htm,html}';
+ if(globInfo.is.glob === true){
+ // no basename
+ if(globPath.basename === ''){
+ pattern += defaultGlob;
+ }
+ }
+ else{
+ // no basename
+ if(globPath.basename === ''){
+ pattern += '**/' + defaultGlob;
+ }
+ // detect directory
+ else if(fs.existsSync(target) && fs.statSync(target).isDirectory()){
+ base += globPath.basename + '/';
+ pattern = '**/' + defaultGlob;
+ }
+ }
+ return {
+ base: base,
+ pattern: pattern
+ };
+}
+
// search and load config
-function getConfig(configFile, target, json){
- if(configFile === undefined){
+function getConfig(configFile, base, json){
+ if(configFile === undefined && fs.existsSync(base)){
// find default config file in parent directory
- if(fs.statSync(target).isDirectory() === false){
- target = path.dirname(target);
+ if(fs.statSync(base).isDirectory() === false){
+ base = path.dirname(base);
}
- while(target){
- var tmpConfigFile = path.resolve(target+path.sep, '.htmlhintrc');
+ while(base){
+ var tmpConfigFile = path.resolve(base+path.sep, '.htmlhintrc');
if(fs.existsSync(tmpConfigFile)){
configFile = tmpConfigFile;
break;
}
- target = target.substring(0,target.lastIndexOf(path.sep));
+ base = base.substring(0,base.lastIndexOf(path.sep));
}
}
@@ -236,37 +278,29 @@ function getConfig(configFile, target, json){
}
// walk path
-function walkPath(dir, callback, onFinish) {
- fs.readdir(dir, function (err, files) {
- var arrTasks = [];
- files.forEach(function(file){
- arrTasks.push(function(next){
- var pathname = path.join(dir, file);
- fs.stat(pathname, function (err, stats) {
- if(stats){
- if (stats.isDirectory()) {
- if(/^(\.svn|\.git|\.build|node_modules)$/i.test(file) === false){
- walkPath(pathname, callback, next);
- }
- else{
- next();
- }
- } else {
- if(/\.html?$/i.test(file)){
- callback(path.normalize(pathname));
- }
- next();
- }
- }
- else{
- next();
- }
- });
- });
- });
- async.series(arrTasks, function(){
- onFinish && onFinish();
+function walkPath(globInfo, callback, onFinish) {
+ var base = globInfo.base;
+ var pattern = globInfo.pattern;
+ var ignore = globInfo.ignore;
+ var arrIgnores = ['**/node_modules/**'];
+ if(ignore){
+ ignore.split(',').forEach(function(pattern){
+ arrIgnores.push(pattern);
});
+ }
+ var walk = glob(pattern, {
+ 'cwd': base,
+ 'dot': false,
+ 'ignore': arrIgnores,
+ 'nodir': true,
+ 'strict': false,
+ 'silent': true
+ },function() {
+ onFinish();
+ });
+ walk.on('match', function(file){
+ base = base.replace(/^.\//, '');
+ callback(base + file);
});
}
diff --git a/package.json b/package.json
index 3581a8103..36b6a3703 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,9 @@
"colors": "1.0.3",
"commander": "2.6.0",
"csslint": "0.10.0",
+ "glob": "5.0.15",
"jshint": "2.8.0",
+ "parse-glob": "3.0.4",
"strip-json-comments": "1.0.4"
},
"devDependencies": {