Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 92x 92x 92x 92x 92x 1x 1x 1x 1x 1x 1x 1x 1x 1x 16x 14x 14x 2x 2x 16x 16x 16x 16x 16x 16x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1573x 1573x 1573x 1573x 1573x 1573x 1573x 1573x 1573x 1573x 1573x 1573x 1573x 1x 1x 1x 1x 1x 1x 1x 1x 1547x 1x 1x 1x 1x 1x 1x 1x 1x 1x 74x 3x 3x 71x 1x 1x 1x 1x 1x 1x 1x 1x 64x 21x 21x 43x 1x 1x 1x 1x 1x 1x 1x 9x 1x 1x 1x 1x 1x 1x 1x 1x 1x 9x 9x 9x 9x 1x 1x 1x 1x 1x 1x 1x 1x 1x 115x 7x 7x 1x 7x 6x 7x 7x 108x 1x 1x 1x 1x 1x 1x 1x 1x 112x 1x 1x 1x 1x 1x 1x 1x 1x 28x 1x 1x 1x 1x 1x 1x 1x 1x 80x 160x 160x 160x 80x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x | module.exports = (() => {
const tags = {
b: 'bold',
d: 'dim'
};
return {
/**
* Show an error message in the console
* @param {Error|string} error
* @param {boolean} dontExit
*/
error(error, dontExit = false) {
const message = error instanceof Error
? (process.env.DEBUG === 'true' ? error : this.replaceTags(error.message))
: this.replaceTags(error);
console.error(message);
!dontExit && process.env.KANBN_ENV !== 'test' && process.exit(1);
},
/**
* Show a hint about tasks whose dates don't match the column they're in. Hints are suppressed
* for machine-readable output and when the board sets verbose to false
* @param {object[]} drift Drift entries from kanbn.getDateDrift()
* @param {boolean} show False to suppress the hint
*/
showDateDriftHint(drift, show = true) {
if (!show || !drift.length) {
return;
}
const fixable = drift.filter(d => d.fixable).length;
const summary =
`${drift.length} ${drift.length === 1 ? 'task has' : 'tasks have'} dates that don't match ` +
`${drift.length === 1 ? 'its' : 'their'} column`;
console.log(this.replaceTags(
`\n{d}${summary}${fixable ? ' - run{d} {b}kanbn validate --fix{b}' : '{d}'}` +
(fixable ? ` {d}to fill in the missing ${fixable === 1 ? 'date' : 'dates'}{d}` : '')
));
},
/**
* Convert a string to simplified paramcase, e.g:
* PascalCase -> pascalcase
* Test Word -> test-word
* @param {string} s The string to convert
* @return {string} The converted string
*/
paramCase(s) {
return s
.replace(
/([A-Z]+(.))/g,
(_, separator, letter, offset) => (offset ? "-" + separator : separator).toLowerCase()
)
.split(/[\s!?.,@:;|\\/"'`£$%\^&*{}[\]()<>~#+\-=_¬]+/g)
.join('-')
.replace(/(^-|-$)/g, '')
// The word-boundary replacement above only matches an uppercase run that is followed by
// another character, so a trailing capital ("Task A" -> "task-A") and any non-ASCII capital
// ("Ünïcodé" -> "Ünïcodé") would otherwise survive. Board slugs use this function, so ids
// must be lowercase throughout.
.toLowerCase();
},
/**
* Get a task id from the task name
* @param {string} name The task name
* @return {string} The task id
*/
getTaskId(name) {
return this.paramCase(name);
},
/**
* Convert an argument into a string. If the argument is an array of strings, concatenate them or use the
* last element
* @param {string|string[]} arg An argument that might be a string or an array of strings
* @return {string} The argument value as a string
*/
strArg(arg, all = false) {
if (Array.isArray(arg)) {
return all ? arg.join(',') : arg.pop();
}
return arg;
},
/**
* Convert an argument into an array. If the argument is a string, return it as a single-element array
* @param {string|string[]} arg An argument that might be a string or an array of strings
* @return {string[]} The argument value as an array
*/
arrayArg(arg) {
if (Array.isArray(arg)) {
return arg;
}
return [arg];
},
/**
* Remove escape characters ('/' and '\') from the beginning of a string
* @param {string} s The string to trim
*/
trimLeftEscapeCharacters(s) {
return s.replace(/^[\\\/]+/, '');
},
/**
* Compare two dates using only the date part and ignoring time
* @param {Date} a
* @param {Date} b
* @return {boolean} True if the dates are the same
*/
compareDates(a, b) {
const aDate = new Date(a), bDate = new Date(b);
aDate.setHours(0, 0, 0, 0);
bDate.setHours(0, 0, 0, 0);
return aDate.getTime() == bDate.getTime();
},
/**
* If a is undefined, convert it to a number or string depending on the specified type
* @param {*} a
* @param {string} type
* @return {string|number}
*/
coerceUndefined(a, type) {
if (a === undefined) {
switch (type) {
case 'string':
return '';
default:
return 0;
}
}
return a;
},
/**
* Make a string bold
* @param {string} s The string to wrap
* @return {string} The updated string
*/
bold(s) {
return `\x1b[1m${s}\x1b[0m`;
},
/**
* Make a string dim
* @param {string} s The string to wrap
* @return {string} The updated string
*/
dim(s) {
return `\x1b[2m${s}\x1b[0m`;
},
/**
* Replace tags like {x}...{x} in a string
* @param {string} s The string in which to replace tags
* @return {string} The updated string
*/
replaceTags(s) {
for (tag in tags) {
const r = new RegExp(`\{${tag}\}([^{]+)\{${tag}\}`, 'g');
s = s.replace(r, (m, s) => this[tags[tag]](s));
}
return s;
},
/**
* Zip 2 arrays together, i.e. ([1, 2, 3], [a, b, c]) => [[1, a], [2, b], [3, c]]
* @param {any[]} a
* @param {any[]} b
* @return {any[]}
*/
zip: (a, b) => a.map((k, i) => [k, b[i]])
}
})();
|