1 | const path = require('path');
|
2 | const Rule = require('./Rule');
|
3 | const VAR = require('./VAR');
|
4 |
|
5 |
|
6 |
|
7 | function Match(option, rules) {
|
8 | this.option = option;
|
9 | this.rules = [];
|
10 | this.init(rules);
|
11 | }
|
12 |
|
13 | Match.prototype.init = function (release) {
|
14 | release = release || [];
|
15 | if (!Array.isArray(release)) {
|
16 | switch (typeof release) {
|
17 | case 'string':
|
18 | case 'object':
|
19 | release = [release];
|
20 | break;
|
21 | default:
|
22 | throw new Error('Invalid release rule');
|
23 | break;
|
24 | }
|
25 | }
|
26 | const option = this.option;
|
27 | const symbol = option.symbol;
|
28 | let rules = [];
|
29 | release.forEach(rule => {
|
30 | const ruleType = typeof rule;
|
31 | if (ruleType === 'string') {
|
32 | rules.push(rule);
|
33 | } else if (Array.isArray(rule)) {
|
34 | if (rule.every(r => typeof r === 'string')) {
|
35 | throw new Error(`Invalid rule: array rule elements can only be a string`);
|
36 | }
|
37 | rules = rules.concat(rule);
|
38 | } else if (ruleType === 'object') {
|
39 | Object.keys(rule).map(key => {
|
40 | const value = rule[key];
|
41 | if (typeof value !== 'boolean') {
|
42 | key = key
|
43 | .replace(new RegExp(
|
44 | `^(\\${symbol.negation}?)\\${symbol.module}`
|
45 | ), `$1${symbol.alias}`)
|
46 | .replace(new RegExp(
|
47 | `^(\\${symbol.negation}?)(\\${symbol.regexp}|\\${symbol.match})`
|
48 | ), `$1/$2`)
|
49 | }
|
50 | const item = {};
|
51 | item[key] = value;
|
52 | rules = rules.concat(this.stringifyRules(item));
|
53 | });
|
54 | } else {
|
55 | throw new Error(`Invalid rule type: ${ruleType}`);
|
56 | }
|
57 | });
|
58 | rules.forEach(rule => {
|
59 | if (!rule) {
|
60 | throw new Error('empty rule');
|
61 | }
|
62 | if (
|
63 | rule.charAt(0) === symbol.module
|
64 | || (
|
65 | rule.charAt(0) === symbol.negation
|
66 | && rule.charAt(1) === symbol.module
|
67 | )
|
68 | ) {
|
69 | const isModuleNegation = rule.charAt(0) === symbol.negation;
|
70 | const moduleName = rule.replace(new RegExp(`^\\${symbol.negation}`), '').slice(1);
|
71 | const moduleRule = option.module[moduleName];
|
72 | if (!moduleRule) {
|
73 | throw new Error(`not exist module ${moduleName}`)
|
74 | }
|
75 | const moduleMatch = new Match(option, moduleRule);
|
76 | moduleMatch.rules.forEach(rule => {
|
77 | isModuleNegation && rule.setNegation(true)
|
78 | this.rules.push(rule);
|
79 | });
|
80 | } else {
|
81 | const relation = rule.indexOf(symbol.relation);
|
82 | let base = '';
|
83 | let children = rule;
|
84 | if (relation > -1) {
|
85 | base = rule.slice(0, relation);
|
86 | children = rule.slice(relation + 1);
|
87 | }
|
88 | children.split(symbol.separation).forEach(child => {
|
89 | let prefix = base;
|
90 | if (prefix && child.charAt(0) === symbol.negation) {
|
91 | prefix = symbol.negation + prefix.replace(new RegExp(`^\\${symbol.negation}`), '');
|
92 | child = child.slice(1);
|
93 | }
|
94 | this.rules.push(new Rule(path.join(prefix, child), option));
|
95 | });
|
96 | }
|
97 | });
|
98 | };
|
99 |
|
100 | Match.prototype.test = function (path) {
|
101 | const matchMode = this.option.match;
|
102 | let release = false;
|
103 | let matched = false;
|
104 | this.rules.forEach(rule => {
|
105 | const stat = rule.test(path);
|
106 | if (stat > -1) {
|
107 | if (matched) {
|
108 | if (matchMode === VAR.MATCH_MODE.WARN) {
|
109 | console.warn(`path[${path}] match repeat by rule[${rule.toString()}]`);
|
110 | } else if (matchMode === VAR.MATCH_MODE.STRICT) {
|
111 | throw new Error(`path[${path}] must match once, repeat by rule[${rule.toString()}]`);
|
112 | }
|
113 | }
|
114 | matched = true;
|
115 | release = !!stat;
|
116 | }
|
117 | });
|
118 | return matched
|
119 | ? release
|
120 | ? 1 : 0
|
121 | : -1;
|
122 | };
|
123 |
|
124 | Match.prototype.stringifyRules = function(rule, base) {
|
125 | const option = this.option;
|
126 | const symbol = option.symbol;
|
127 |
|
128 | const prefix = base || '';
|
129 | const prefixNonNegation = prefix.replace(new RegExp(`^\\${symbol.negation}`), '');
|
130 |
|
131 | let rules = [];
|
132 | Object.keys(rule).forEach(key => {
|
133 | const keyPrefix = key.charAt(0) === symbol.negation
|
134 | ? prefixNonNegation
|
135 | ? path.join(symbol.negation + prefixNonNegation, key.slice(1))
|
136 | : (symbol.negation + key.slice(1))
|
137 | : path.join(prefix, key);
|
138 | const keyPrefixNonNegation = keyPrefix.replace(new RegExp(`^\\${symbol.negation}`), '');
|
139 |
|
140 | const value = rule[key];
|
141 | const valueType = typeof value;
|
142 | if (valueType === 'string' || Array.isArray(value)) {
|
143 | (Array.isArray(value) ? value : [value]).forEach(subRule => {
|
144 | if (typeof subRule !== 'string' || subRule.length === 0) {
|
145 | throw new Error(`Invalid rule: array rule elements can only be a string`);
|
146 | }
|
147 | rules.push(
|
148 | subRule.charAt(0) === symbol.negation
|
149 | ? path.join(symbol.negation + keyPrefixNonNegation, subRule.slice(1))
|
150 | : path.join(keyPrefix, subRule)
|
151 | );
|
152 | });
|
153 | } else if (valueType === 'object') {
|
154 | rules = rules.concat(this.stringifyRules(value, keyPrefix));
|
155 | } else if (valueType === 'boolean') {
|
156 | rules.push(value ? keyPrefix : symbol.negation + keyPrefixNonNegation);
|
157 | } else {
|
158 | throw new Error('Invalid rule type');
|
159 | }
|
160 | });
|
161 | return rules;
|
162 | };
|
163 |
|
164 | Match.prototype.toString = function() {
|
165 | return this.rules.map(rule => rule.toString()).join('\n')
|
166 | };
|
167 |
|
168 |
|
169 | exports = module.exports = Match;
|