From 74740bc6144b8bfc9f01051672989ab133f65e1d Mon Sep 17 00:00:00 2001 From: Felix Wu Date: Tue, 5 Jun 2018 14:42:56 +0200 Subject: [PATCH 01/18] add node-chokidar for watching files --- package.json | 1 + yarn.lock | 56 +++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 93575cc..3ee68a1 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "dependencies": { "cac": "^5.0.10", "chalk": "^2.4.1", + "chokidar": "^2.0.3", "cross-spawn": "^6.0.5", "fancy-log": "^1.3.2", "joycon": "^1.0.4", diff --git a/yarn.lock b/yarn.lock index c61f5c4..28bc212 100644 --- a/yarn.lock +++ b/yarn.lock @@ -153,6 +153,13 @@ anymatch@^1.3.0: micromatch "^2.1.5" normalize-path "^2.0.0" +anymatch@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" + dependencies: + micromatch "^3.1.4" + normalize-path "^2.1.1" + app-root-path@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.0.1.tgz#cd62dcf8e4fd5a417efc664d2e5b10653c651b46" @@ -693,7 +700,7 @@ braces@^1.8.2: preserve "^0.2.0" repeat-element "^1.1.2" -braces@^2.3.1: +braces@^2.3.0, braces@^2.3.1: version "2.3.2" resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" dependencies: @@ -860,6 +867,24 @@ chokidar@^1.4.2: optionalDependencies: fsevents "^1.0.0" +chokidar@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.3.tgz#dcbd4f6cbb2a55b4799ba8a840ac527e5f4b1176" + dependencies: + anymatch "^2.0.0" + async-each "^1.0.0" + braces "^2.3.0" + glob-parent "^3.1.0" + inherits "^2.0.1" + is-binary-path "^1.0.0" + is-glob "^4.0.0" + normalize-path "^2.1.1" + path-is-absolute "^1.0.0" + readdirp "^2.0.0" + upath "^1.0.0" + optionalDependencies: + fsevents "^1.1.2" + chownr@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181" @@ -1654,7 +1679,7 @@ fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" -fsevents@^1.0.0: +fsevents@^1.0.0, fsevents@^1.1.2: version "1.2.4" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.4.tgz#f41dcb1af2582af3692da36fc55cbd8e1041c426" dependencies: @@ -1727,6 +1752,13 @@ glob-parent@^2.0.0: dependencies: is-glob "^2.0.0" +glob-parent@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" + dependencies: + is-glob "^3.1.0" + path-dirname "^1.0.0" + glob@^7.0.3, glob@^7.0.5, glob@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" @@ -2110,7 +2142,7 @@ is-extglob@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" -is-extglob@^2.1.1: +is-extglob@^2.1.0, is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -2140,6 +2172,12 @@ is-glob@^2.0.0, is-glob@^2.0.1: dependencies: is-extglob "^1.0.0" +is-glob@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" + dependencies: + is-extglob "^2.1.0" + is-glob@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0" @@ -2715,7 +2753,7 @@ micromatch@^2.1.5: parse-glob "^3.0.4" regex-cache "^0.4.2" -micromatch@^3.1.10, micromatch@^3.1.8: +micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.8: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" dependencies: @@ -2872,7 +2910,7 @@ normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" -normalize-path@^2.0.0, normalize-path@^2.0.1: +normalize-path@^2.0.0, normalize-path@^2.0.1, normalize-path@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" dependencies: @@ -3108,6 +3146,10 @@ pascalcase@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" +path-dirname@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" + path-exists@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" @@ -4151,6 +4193,10 @@ unzip-response@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97" +upath@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd" + update-notifier@^2.3.0: version "2.5.0" resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.5.0.tgz#d0744593e13f161e406acb1d9408b72cad08aff6" From 57c649149056e42fd2a0f1cb7c04f13a225afdea Mon Sep 17 00:00:00 2001 From: Felix Wu Date: Tue, 5 Jun 2018 14:53:57 +0200 Subject: [PATCH 02/18] add FileWatcher class for watch command --- lib/FileWatcher.js | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 lib/FileWatcher.js diff --git a/lib/FileWatcher.js b/lib/FileWatcher.js new file mode 100644 index 0000000..04f13bc --- /dev/null +++ b/lib/FileWatcher.js @@ -0,0 +1,34 @@ +const chokidar = require('chokidar') +const logger = require('./logger') + +class FileWatcher { + constructor() { + this.watcher = chokidar.watch('dir', { + ignored: /(^|[/\\])\../, + persistent: true + }) + this.watcher + .on('add', path => logger.log(`File ${path} has been added`)) + .on('change', path => logger.log(`File ${path} has been changed`)) + .on('unlink', path => logger.log(`File ${path} has been removed`)) + .on('addDir', path => logger.log(`Directory ${path} has been added`)) + .on('unlinkDir', path => logger.log(`Directory ${path} has been removed`)) + .on('error', error => logger.log(`Watcher error: ${error}`)) + .on('ready', () => logger.log('Initial scan complete. Ready for changes')) + } + + watch(file) { + this.watcher.add(file) + console.log(this.watcher.getWatched()) + } + + unwatch(file) { + this.watcher.unwatch(file) + } + + close() { + this.watcher.close() + } +} + +module.exports = FileWatcher From ee1bdeb4629b9219387ad33f3a9286a831fd554d Mon Sep 17 00:00:00 2001 From: Felix Wu Date: Tue, 5 Jun 2018 15:04:28 +0200 Subject: [PATCH 03/18] adapt command parsing to new command regex --- lib/parseMarkdown.js | 49 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/lib/parseMarkdown.js b/lib/parseMarkdown.js index 793b677..83b1f9a 100644 --- a/lib/parseMarkdown.js +++ b/lib/parseMarkdown.js @@ -28,22 +28,52 @@ const isCommandReTemplate = and( capture(or('after', 'before')), 'this' ].join(space), - capture(and(space, 'in', space, 'parallel')), - LAZY, + capture( + or( + and(space, 'and', space, 'watch', space, capture(extra(ANY))), + and(space, 'in', space, 'parallel') + ) + ) + LAZY, + capture(and(space, 'and', space, 'watch', space, capture(extra(ANY)))) + LAZY, wildcard(SPACE) ) +const isParallelGroup = regexGroupString => + /(.*?)in parallel/.test(regexGroupString) +const isWatchGroup = regexGroupString => + /(.*?)and watch(.*?)/.test(regexGroupString) + const isCommandRe = new RegExp(isCommandReTemplate, flags.INSENSITIVE) const isCommand = v => isCommandRe.test(v) && isCommandRe.exec(v)[1].includes('`') const parseCommand = v => { - const [, command, when, inParallel] = isCommandRe.exec(v) + const regexGroups = isCommandRe + .exec(v) + .filter(regGroup => typeof regGroup !== undefined && regGroup !== undefined) + const [, command, when, parallelOrWatch] = regexGroups + let inParallel = false, // eslint-disable-line one-var + watchTargets = null + + // check if third regex group is 'in parallel' or 'and watch' + if (isParallelGroup(parallelOrWatch)) { + // in case it's parallel, also check for 'and watch' + inParallel = true + // if the forelast group is 'and watch', + // set the watchTargets to the last regex group + watchTargets = + isWatchGroup(isCommandRe.exec(v)[isCommandRe.exec(v).length - 2]) && + isCommandRe.exec(v)[isCommandRe.exec(v).length - 1].includes('`') + ? isCommandRe.exec(v)[isCommandRe.exec(v).length - 1] + : null + } else if (isWatchGroup(parallelOrWatch)) { + watchTargets = isCommandRe.exec(v)[isCommandRe.exec(v).length - 1] + } const taskNames = command.match(/`([^`]+)`/g).map(v => /`(.+)`/.exec(v)[1]) - return { taskNames, when, - inParallel: Boolean(inParallel) + inParallel: inParallel, + watchTargets } } @@ -140,10 +170,13 @@ module.exports = (content, { section, filepath } = {}) => { .filter(p => { const isCommandBool = isCommand(p) if (isCommandBool) { - const { taskNames, when, inParallel } = parseCommand(p) + const { taskNames, when, inParallel, watchTargets } = parseCommand( + p + ) task[when].push({ taskNames, - inParallel + inParallel, + watchTargets }) } return !isCommandBool @@ -163,7 +196,7 @@ module.exports = (content, { section, filepath } = {}) => { tasks.push(task) } } - + console.log(tasks) return { filepath, tasks From 47db52ee77f3b18ee4b921e70dc86ee87e5d694e Mon Sep 17 00:00:00 2001 From: Felix Wu Date: Tue, 5 Jun 2018 15:04:44 +0200 Subject: [PATCH 04/18] outsource MaidRunner --- lib/MaidRunner.js | 159 ++++++++++++++++++++++++++++++++++++++++++++++ lib/index.js | 156 +-------------------------------------------- 2 files changed, 160 insertions(+), 155 deletions(-) create mode 100644 lib/MaidRunner.js diff --git a/lib/MaidRunner.js b/lib/MaidRunner.js new file mode 100644 index 0000000..48a33ca --- /dev/null +++ b/lib/MaidRunner.js @@ -0,0 +1,159 @@ +const path = require('path') +const chalk = require('chalk') +const logger = require('./logger') +const readMaidFile = require('./readMaidFile') +const MaidError = require('./MaidError') +const runCLICommand = require('./runCLICommand') +const FileWatcher = require('./FileWatcher') + +class MaidRunner { + constructor(opts = {}) { + this.maidfile = readMaidFile(opts.section) + logger.setOptions({ quiet: opts.quiet }) + this.watcher = new FileWatcher() + + if (!this.maidfile) { + throw new MaidError('No maidfile was found. Stop.') + } + this.maidfile.tasks.forEach(task => this.watcher.watch(task.watchTargets)) + } + + async runTasks(taskNames, inParallel) { + if (!taskNames || taskNames.length === 0) return + + if (inParallel) { + await Promise.all( + taskNames.map(taskName => { + return this.runTask(taskName) + }) + ) + } else { + for (const taskName of taskNames) { + await this.runTask(taskName) + } + } + } + + async runFile(taskName) { + await this.runTask('beforeAll', false) + await this.runTask(taskName) + await this.runTask('afterAll', false) + } + + async runTask(taskName, throwWhenNoMatchedTask = true) { + const task = + taskName && + this.maidfile && + this.maidfile.tasks.find(task => task.name === taskName) + + if (!task) { + if (throwWhenNoMatchedTask) { + throw new MaidError(`No task called "${taskName}" was found. Stop.`) + } else { + return + } + } + + await this.runTaskHooks(task, 'before') + + const start = Date.now() + + logger.log(`Starting '${chalk.cyan(task.name)}'...`) + await new Promise((resolve, reject) => { + const handleError = err => { + throw new MaidError(`Task '${task.name}' failed.\n${err.stack}`) + } + if (checkTypes(task, ['sh', 'bash'])) { + return runCLICommand({ task, resolve, reject }) + } + if (checkTypes(task, ['py', 'python'])) { + return runCLICommand({ type: 'python', task, resolve, reject }) + } + if (checkTypes(task, ['js', 'javascript'])) { + let res + try { + res = require('require-from-string')( + task.script, + this.maidfile.filepath + ) + } catch (err) { + return handleError(err) + } + res = res.default || res + return resolve( + typeof res === 'function' + ? Promise.resolve(res()).catch(handleError) + : res + ) + } + + return resolve() + }) + + logger.log( + `Finished '${chalk.cyan(task.name)}' ${chalk.magenta( + `after ${Date.now() - start} ms` + )}...` + ) + await this.runTaskHooks(task, 'after') + } + + async runTaskHooks(task, when) { + const prefix = when === 'before' ? 'pre' : 'post' + const tasks = this.maidfile.tasks.filter(({ name }) => { + return name === `${prefix}${task.name}` + }) + await this.runTasks(tasks.map(task => task.name)) + for (const item of task[when]) { + const { taskNames, inParallel } = item + await this.runTasks(taskNames, inParallel) + } + } + + getHelp(patterns) { + const mm = require('micromatch') + + patterns = [].concat(patterns) + const tasks = + patterns.length > 0 + ? this.maidfile.tasks.filter(task => { + return mm.some(task.name, patterns) + }) + : this.maidfile.tasks + + if (tasks.length === 0) { + throw new MaidError( + `No tasks for pattern "${patterns.join(' ')}" was found. Stop.` + ) + } + + console.log( + `\n ${chalk.magenta.bold( + `Task${tasks.length > 1 ? 's' : ''} in ${path.relative( + process.cwd(), + this.maidfile.filepath + )}:` + )}\n\n` + + tasks + .map( + task => + ` ${chalk.bold(task.name)}\n${chalk.dim( + task.description + ? task.description + .split('\n') + .map(v => ` ${v.trim()}`) + .join('\n') + : ' No description' + )}` + ) + .join('\n\n') + + '\n' + ) + } +} + +function checkTypes(task, types) { + return types.some(type => type === task.type) +} + +module.exports = MaidRunner diff --git a/lib/index.js b/lib/index.js index a733d16..cbe02b3 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,156 +1,2 @@ -const path = require('path') -const chalk = require('chalk') -const logger = require('./logger') -const readMaidFile = require('./readMaidFile') -const MaidError = require('./MaidError') -const runCLICommand = require('./runCLICommand') - -class Maid { - constructor(opts = {}) { - this.maidfile = readMaidFile(opts.section) - logger.setOptions({ quiet: opts.quiet }) - - if (!this.maidfile) { - throw new MaidError('No maidfile was found. Stop.') - } - } - - async runTasks(taskNames, inParallel) { - if (!taskNames || taskNames.length === 0) return - - if (inParallel) { - await Promise.all( - taskNames.map(taskName => { - return this.runTask(taskName) - }) - ) - } else { - for (const taskName of taskNames) { - await this.runTask(taskName) - } - } - } - - async runFile(taskName) { - await this.runTask('beforeAll', false) - await this.runTask(taskName) - await this.runTask('afterAll', false) - } - - async runTask(taskName, throwWhenNoMatchedTask = true) { - const task = - taskName && - this.maidfile && - this.maidfile.tasks.find(task => task.name === taskName) - - if (!task) { - if (throwWhenNoMatchedTask) { - throw new MaidError(`No task called "${taskName}" was found. Stop.`) - } else { - return - } - } - - await this.runTaskHooks(task, 'before') - - const start = Date.now() - - logger.log(`Starting '${chalk.cyan(task.name)}'...`) - await new Promise((resolve, reject) => { - const handleError = err => { - throw new MaidError(`Task '${task.name}' failed.\n${err.stack}`) - } - if (checkTypes(task, ['sh', 'bash'])) { - return runCLICommand({ task, resolve, reject }) - } - if (checkTypes(task, ['py', 'python'])) { - return runCLICommand({ type: 'python', task, resolve, reject }) - } - if (checkTypes(task, ['js', 'javascript'])) { - let res - try { - res = require('require-from-string')( - task.script, - this.maidfile.filepath - ) - } catch (err) { - return handleError(err) - } - res = res.default || res - return resolve( - typeof res === 'function' - ? Promise.resolve(res()).catch(handleError) - : res - ) - } - - return resolve() - }) - - logger.log( - `Finished '${chalk.cyan(task.name)}' ${chalk.magenta( - `after ${Date.now() - start} ms` - )}...` - ) - await this.runTaskHooks(task, 'after') - } - - async runTaskHooks(task, when) { - const prefix = when === 'before' ? 'pre' : 'post' - const tasks = this.maidfile.tasks.filter(({ name }) => { - return name === `${prefix}${task.name}` - }) - await this.runTasks(tasks.map(task => task.name)) - for (const item of task[when]) { - const { taskNames, inParallel } = item - await this.runTasks(taskNames, inParallel) - } - } - - getHelp(patterns) { - const mm = require('micromatch') - - patterns = [].concat(patterns) - const tasks = - patterns.length > 0 - ? this.maidfile.tasks.filter(task => { - return mm.some(task.name, patterns) - }) - : this.maidfile.tasks - - if (tasks.length === 0) { - throw new MaidError( - `No tasks for pattern "${patterns.join(' ')}" was found. Stop.` - ) - } - - console.log( - `\n ${chalk.magenta.bold( - `Task${tasks.length > 1 ? 's' : ''} in ${path.relative( - process.cwd(), - this.maidfile.filepath - )}:` - )}\n\n` + - tasks - .map( - task => - ` ${chalk.bold(task.name)}\n${chalk.dim( - task.description - ? task.description - .split('\n') - .map(v => ` ${v.trim()}`) - .join('\n') - : ' No description' - )}` - ) - .join('\n\n') + - '\n' - ) - } -} - -function checkTypes(task, types) { - return types.some(type => type === task.type) -} - +const Maid = require('./MaidRunner') module.exports = opts => new Maid(opts) From e8aae679a509bd5a6d38c64009b27a5e92f31316 Mon Sep 17 00:00:00 2001 From: Felix Wu Date: Tue, 5 Jun 2018 19:28:38 +0200 Subject: [PATCH 05/18] on file change, get the task that is watching that file and run it --- lib/FileWatcher.js | 43 ++++++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/lib/FileWatcher.js b/lib/FileWatcher.js index 04f13bc..e2c9fad 100644 --- a/lib/FileWatcher.js +++ b/lib/FileWatcher.js @@ -2,24 +2,45 @@ const chokidar = require('chokidar') const logger = require('./logger') class FileWatcher { - constructor() { - this.watcher = chokidar.watch('dir', { + constructor(runner) { + this.watcher = chokidar.watch(['file, dir, glob'], { ignored: /(^|[/\\])\../, - persistent: true + persistent: true, + ignoreInitial: true }) + + this.watchedTasks = {} + this.watcher - .on('add', path => logger.log(`File ${path} has been added`)) - .on('change', path => logger.log(`File ${path} has been changed`)) - .on('unlink', path => logger.log(`File ${path} has been removed`)) - .on('addDir', path => logger.log(`Directory ${path} has been added`)) - .on('unlinkDir', path => logger.log(`Directory ${path} has been removed`)) + .on('add', path => runner.runFile(this.getTaskName(path))) + .on('change', path => runner.runFile(this.getTaskName(path))) + .on('unlink', path => runner.runFile(this.getTaskName(path))) .on('error', error => logger.log(`Watcher error: ${error}`)) - .on('ready', () => logger.log('Initial scan complete. Ready for changes')) } - watch(file) { + getTaskName(path) { + let foundTask = null + Object.keys(this.watchedTasks).forEach(taskName => { + if (this.watchedTasks[taskName] === path) { + foundTask = taskName + } else { + let pathRegex = this.watchedTasks[taskName].replace('*', '(.*?)') + pathRegex = + pathRegex.charAt(pathRegex.length - 1) === '/' + ? pathRegex.concat('(.*?)') + : pathRegex + if (new RegExp(pathRegex).test(path)) { + foundTask = taskName + } + } + }) + + return foundTask + } + + watch(file, taskName) { this.watcher.add(file) - console.log(this.watcher.getWatched()) + this.watchedTasks[taskName] = file } unwatch(file) { From 40c09de5743517d39eedda9025665547ae943b22 Mon Sep 17 00:00:00 2001 From: Felix Wu Date: Tue, 5 Jun 2018 19:29:13 +0200 Subject: [PATCH 06/18] pass the tasks name to watch() --- lib/MaidRunner.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/MaidRunner.js b/lib/MaidRunner.js index 48a33ca..5deb224 100644 --- a/lib/MaidRunner.js +++ b/lib/MaidRunner.js @@ -10,12 +10,11 @@ class MaidRunner { constructor(opts = {}) { this.maidfile = readMaidFile(opts.section) logger.setOptions({ quiet: opts.quiet }) - this.watcher = new FileWatcher() + this.watcher = new FileWatcher(this) if (!this.maidfile) { throw new MaidError('No maidfile was found. Stop.') } - this.maidfile.tasks.forEach(task => this.watcher.watch(task.watchTargets)) } async runTasks(taskNames, inParallel) { @@ -105,7 +104,8 @@ class MaidRunner { }) await this.runTasks(tasks.map(task => task.name)) for (const item of task[when]) { - const { taskNames, inParallel } = item + const { taskNames, inParallel, watchTargets } = item + this.watcher.watch(watchTargets, taskNames) await this.runTasks(taskNames, inParallel) } } From 37077634c8bf9b37115cf7578b6d4009fa5d06bc Mon Sep 17 00:00:00 2001 From: Felix Wu Date: Tue, 5 Jun 2018 19:29:42 +0200 Subject: [PATCH 07/18] use the regexGroups array for better readability --- lib/parseMarkdown.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/parseMarkdown.js b/lib/parseMarkdown.js index 83b1f9a..2e62cad 100644 --- a/lib/parseMarkdown.js +++ b/lib/parseMarkdown.js @@ -46,6 +46,7 @@ const isWatchGroup = regexGroupString => const isCommandRe = new RegExp(isCommandReTemplate, flags.INSENSITIVE) const isCommand = v => isCommandRe.test(v) && isCommandRe.exec(v)[1].includes('`') + const parseCommand = v => { const regexGroups = isCommandRe .exec(v) @@ -61,13 +62,14 @@ const parseCommand = v => { // if the forelast group is 'and watch', // set the watchTargets to the last regex group watchTargets = - isWatchGroup(isCommandRe.exec(v)[isCommandRe.exec(v).length - 2]) && - isCommandRe.exec(v)[isCommandRe.exec(v).length - 1].includes('`') - ? isCommandRe.exec(v)[isCommandRe.exec(v).length - 1] + isWatchGroup(regexGroups[regexGroups.length - 2]) && + regexGroups[regexGroups.length - 1].includes('`') + ? regexGroups[regexGroups.length - 1].replace(/`/g, '') : null } else if (isWatchGroup(parallelOrWatch)) { - watchTargets = isCommandRe.exec(v)[isCommandRe.exec(v).length - 1] + watchTargets = regexGroups[regexGroups.length - 1].replace(/`/g, '') } + const taskNames = command.match(/`([^`]+)`/g).map(v => /`(.+)`/.exec(v)[1]) return { taskNames, @@ -196,7 +198,6 @@ module.exports = (content, { section, filepath } = {}) => { tasks.push(task) } } - console.log(tasks) return { filepath, tasks From b923deaab90c45daa77f3ab063d59ce4f9e979ca Mon Sep 17 00:00:00 2001 From: Felix Wu Date: Tue, 5 Jun 2018 20:28:49 +0200 Subject: [PATCH 08/18] only init watcher on first file, only log&add file if not already watching --- lib/MaidRunner.js | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/lib/MaidRunner.js b/lib/MaidRunner.js index 1cb64d2..eb124f6 100644 --- a/lib/MaidRunner.js +++ b/lib/MaidRunner.js @@ -12,11 +12,12 @@ class MaidRunner { constructor(opts = {}) { this.maidfile = readMaidFile(opts.section) logger.setOptions({ quiet: opts.quiet }) - this.watcher = new FileWatcher(this) if (!this.maidfile) { throw new MaidError('No maidfile was found. Stop.') } + this.firstTask = true + this.alreadyWatching = {} } async runTasks(taskNames, inParallel) { @@ -104,7 +105,24 @@ class MaidRunner { await this.runTasks(tasks.map(task => task.name)) for (const item of task[when]) { const { taskNames, inParallel, watchTargets } = item - this.watcher.watch(watchTargets, taskNames) + // if this is the overall first task, init the watcher + if (watchTargets) { + if (this.firstTask) { + FileWatcher.init(this) + this.firstTask = false + } + // log watching on first run of each task + if (!this.alreadyWatching[taskNames]) { + logger.log( + `'${chalk.cyan(taskNames)}' is watching ${chalk.magenta( + watchTargets + )}...` + ) + FileWatcher.watch(watchTargets, taskNames) + this.alreadyWatching[taskNames] = true + } + } + await this.runTasks(taskNames, inParallel) } } From 68db54a4380cb75068107f9f1ac7f9127b024c9d Mon Sep 17 00:00:00 2001 From: Felix Wu Date: Tue, 5 Jun 2018 20:29:05 +0200 Subject: [PATCH 09/18] turn FileWatcher into Singleton --- lib/FileWatcher.js | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/lib/FileWatcher.js b/lib/FileWatcher.js index e2c9fad..83cfd85 100644 --- a/lib/FileWatcher.js +++ b/lib/FileWatcher.js @@ -2,16 +2,22 @@ const chokidar = require('chokidar') const logger = require('./logger') class FileWatcher { - constructor(runner) { - this.watcher = chokidar.watch(['file, dir, glob'], { - ignored: /(^|[/\\])\../, - persistent: true, - ignoreInitial: true - }) - + constructor() { + this.runner = [] + this.watcher = [] this.watchedTasks = {} + } - this.watcher + init(runner) { + this.watcher.push( + chokidar.watch(['file, dir, glob'], { + ignored: /(^|[/\\])\../, + persistent: true, + ignoreInitial: true + }) + ) + + this.watcher[0] .on('add', path => runner.runFile(this.getTaskName(path))) .on('change', path => runner.runFile(this.getTaskName(path))) .on('unlink', path => runner.runFile(this.getTaskName(path))) @@ -39,7 +45,7 @@ class FileWatcher { } watch(file, taskName) { - this.watcher.add(file) + this.watcher[0].add(file) this.watchedTasks[taskName] = file } @@ -52,4 +58,8 @@ class FileWatcher { } } -module.exports = FileWatcher +// Singleton +const instance = new FileWatcher() +Object.freeze(instance) + +module.exports = instance From d7ed3c6367a6d0e42e9e5d2dc879144b215d9bbb Mon Sep 17 00:00:00 2001 From: Felix Wu Date: Tue, 5 Jun 2018 20:35:55 +0200 Subject: [PATCH 10/18] adapt unwatch and close to singleton --- lib/FileWatcher.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/FileWatcher.js b/lib/FileWatcher.js index 83cfd85..1deb3de 100644 --- a/lib/FileWatcher.js +++ b/lib/FileWatcher.js @@ -50,11 +50,11 @@ class FileWatcher { } unwatch(file) { - this.watcher.unwatch(file) + this.watcher[0].unwatch(file) } close() { - this.watcher.close() + this.watcher[0].close() } } From e943ca6ff04c871e67eb7438503f5b0085eaf721 Mon Sep 17 00:00:00 2001 From: Felix Wu Date: Tue, 5 Jun 2018 23:21:38 +0200 Subject: [PATCH 11/18] regenerate snapshots --- test/snapshots/parseMarkdown.test.js.md | 5 +++-- test/snapshots/parseMarkdown.test.js.snap | Bin 820 -> 841 bytes 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/test/snapshots/parseMarkdown.test.js.md b/test/snapshots/parseMarkdown.test.js.md index f47dd9f..33c6dcf 100644 --- a/test/snapshots/parseMarkdown.test.js.md +++ b/test/snapshots/parseMarkdown.test.js.md @@ -46,6 +46,7 @@ Generated by [AVA](https://ava.li). taskNames: [ 'goodbye', ], + watchTargets: null, }, ], before: [], @@ -69,7 +70,7 @@ Generated by [AVA](https://ava.li). type: 'sh', }, ], - + } ## use readme @@ -85,4 +86,4 @@ Generated by [AVA](https://ava.li). name: 'dev', }, ], - } \ No newline at end of file + } diff --git a/test/snapshots/parseMarkdown.test.js.snap b/test/snapshots/parseMarkdown.test.js.snap index 08ca79ccf6606b78504e1633f71c5979d47049b7..797659817e3bbc3b7d1ae3dee0669027d2afa5a9 100644 GIT binary patch literal 841 zcmV-P1GfA@RzVBElD#}n`fvFWmWuS#p34th*<6L7Wj-C20Z7m^zkPriO z=u%iZ!2*P>NH73@KoJrPAOLp@yZ7F`_wK#VqX;1p z%{+WH`1$hSbLMO#7l-C@82P^!5<+kG-swK|?9rvMuHh>?r~7*_mv%)FI@$C7)>!9< zw{suQzI$1&+{0Y@G=`9X6mE|dEbfQrLBMIi7OZq)c{rUTI>UHnnMfprujdhNMOZ+% z9n^%aSVUOr#L;onB1MfETp+h2+=lT4xT-5em`3xIpEM8R8-Tm@lOlbtt9e+cuR1z9 z_&G6tP8{R=)hJ=L>_4&ZM%f3s9{?N&n1F5pp~HY_A&`LhLjquZ4YcL~3xFqpWx#e3 zJct47Xju9(&yR?!^3%o{ak+SAl~_)bamOWrYPDnt)m^j5OvkRtgzIS3FsWLVtFS6j zl2MtXQq5Bh;-%v8wB_jeiIT$z-?wd|6G}DLQ*CET)of6AiN?SzSE5X{%sf$xnyY19 zt!RLKe+eA3BvmH~*yv87Xpw1^I_i;RL7j3?YFf!10=;%PIkl{CS zE_9-Vn;^cuhH0wu+Cn|pNClot00o9&2VmsaqXR)pJQNp)_RK6^PtR=PAJYt(tbNv- T4g;ud?6$uFD}p)`8%>4|tNVjqhL00000000x+ zRnKb^K@^^u-Rw50txeT~9(3@~R0=ux)5h^I6ZzoM{)-~2v51Q~XGw;25^WMz+-i{!I zBsBf_<-q651J6BYJG(eIlgB9dJ&+N4z3*Pvsb^0vjqV(}vU{qp2Mg&(6`_+o@9vCt zzJD|G;q2QNmFhz*q_^V;5u}R!SjEx-7!LtX1GZwd6DvcRJkvdlSEk8il6*an@Fs)_ z!tG#2wqXfjxf8dHnHDQ)o*@KEJHl-kkAthaLCn*5fs2#oKztK$zkX7z*L5`?llrWq zqeGk%7w04}epHJRW=p|{12^h^$o(MT1i%Dz5rmEarbs9O>4yYh_YJU`1Iz=S0+s+f zB=8`GcDF>OuZaAZv?@Ps%ww()Pp=Xy88+s)EOc9~S;lqOEP1A5FU!a*8Zk^xYjO&+ zGG|%pIh1QYHJG1HBr=wx7sktu$AsRtna()ZT%X#`B-Ly%cbVqFwtSg;)G`ZxJV6aDbiY1FpQSQ*AsUn?pP0zG*uq=Xmn|p3q58~tLQ1G&?6N&G<)h{t zC!6uZHldu_m~J?9*|d2!+z85zR3YF+sj=SWaJ{PyYPGpK-dvrCAQKi8`XtpU-wh2S z4H%L`H6L3G9(rUO{|v9aOV&<0UB3akd60q42Bx zSN-ZLpaOV>*V3=PfRz;VD8OF88NdZVCEV?lpZBW;5=o{iSqGU=)2>kXyImn3-4(k3 zt*-Dp%^<}~FNOI4 From 6f4b30127fd902d8f82a955b7a5945727a7c61f9 Mon Sep 17 00:00:00 2001 From: Felix Wu Date: Wed, 6 Jun 2018 16:54:12 +0200 Subject: [PATCH 12/18] run all tasks subsequently in case a task array gets passed --- lib/FileWatcher.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/FileWatcher.js b/lib/FileWatcher.js index 1deb3de..da47723 100644 --- a/lib/FileWatcher.js +++ b/lib/FileWatcher.js @@ -18,13 +18,19 @@ class FileWatcher { ) this.watcher[0] - .on('add', path => runner.runFile(this.getTaskName(path))) - .on('change', path => runner.runFile(this.getTaskName(path))) - .on('unlink', path => runner.runFile(this.getTaskName(path))) + .on('add', path => + this.getTaskNames(path).forEach(taskName => runner.runFile(taskName)) + ) + .on('change', path => + this.getTaskNames(path).forEach(taskName => runner.runFile(taskName)) + ) + .on('unlink', path => + this.getTaskNames(path).forEach(taskName => runner.runFile(taskName)) + ) .on('error', error => logger.log(`Watcher error: ${error}`)) } - getTaskName(path) { + getTaskNames(path) { let foundTask = null Object.keys(this.watchedTasks).forEach(taskName => { if (this.watchedTasks[taskName] === path) { @@ -41,7 +47,7 @@ class FileWatcher { } }) - return foundTask + return foundTask.split(',') } watch(file, taskName) { From b784d7c11abaac89cfd4e8b810cfc66750052683 Mon Sep 17 00:00:00 2001 From: Felix Wu Date: Wed, 6 Jun 2018 19:45:28 +0200 Subject: [PATCH 13/18] auto-fix commands without 'when' by defaulting to 'before this' --- lib/parseMarkdown.js | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/lib/parseMarkdown.js b/lib/parseMarkdown.js index c786fe8..98ee21d 100644 --- a/lib/parseMarkdown.js +++ b/lib/parseMarkdown.js @@ -24,7 +24,8 @@ const isCommandReTemplate = and( START, // ^ wildcard(SPACE), [ - ['Runs' + LAZY, 'tasks' + LAZY].join(space), + 'Runs' + LAZY, + 'tasks' + LAZY, capture(extra(ANY)), capture(or('after', 'before')), 'this' @@ -40,25 +41,36 @@ const isCommandReTemplate = and( ) const isParallelGroup = regexGroupString => - Boolean(regexGroupString.match(/in\s+parallel/i)) + /in\s+parallel/i.test(regexGroupString) const isWatchGroup = regexGroupString => /(.*?)and watch(.*?)/.test(regexGroupString) const MAID_TASKS_SECTION = whole([''].join(space)) const isCommandRe = new RegExp(isCommandReTemplate, flags.INSENSITIVE) const commandsRe = new RegExp(/`([^`]+)`/g) -const isCommand = v => - Boolean(v.match(isCommandRe)) && Boolean(v.match(commandsRe)) + +// add default before this' if no 'when' is given to match default regexGroups +const fixCommand = v => + isCommandRe.test(v) && commandsRe.test(v) + ? v + : v + .split('and watch')[0] + .trim() + .replace(/.+(`(.+?)`)$/, s => `${s} before this`) + + (v.split('and watch')[1] ? ' and watch' + v.split('and watch')[1] : '') + +const isCommand = v => isCommandRe.test(v) && commandsRe.test(v) const parseCommand = v => { - const when = (v.match(/before|after/i) || ['before'])[0] + const when = v.match(/before|after/i)[0] // filter out undefined regex groups const regexGroups = isCommandRe .exec(v) .filter(regGroup => typeof regGroup !== undefined && regGroup !== undefined) + const [, , , parallelOrWatch] = regexGroups - const taskNames = v.match(commandsRe).map(v => /`(.+)`/.exec(v)[1]) + const taskNames = regexGroups[1].split(',').map(v => /`(.+)`/.exec(v)[1]) let inParallel = false, // eslint-disable-line one-var watchTargets = null @@ -176,6 +188,7 @@ module.exports = (content, { section, filepath } = {}) => { // Except for special commands task.description = paragraphs .filter(p => { + p = fixCommand(p) const isCommandBool = isCommand(p) if (isCommandBool) { const { taskNames, when, inParallel, watchTargets } = parseCommand( From 710d83fa32afc4ea036c5f5e8809dba25c608c62 Mon Sep 17 00:00:00 2001 From: Felix Wu Date: Wed, 6 Jun 2018 23:20:18 +0200 Subject: [PATCH 14/18] include edge cases in fixCommand, don't append anything if it's just ending on the task name --- lib/parseMarkdown.js | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/lib/parseMarkdown.js b/lib/parseMarkdown.js index 98ee21d..d17336b 100644 --- a/lib/parseMarkdown.js +++ b/lib/parseMarkdown.js @@ -56,7 +56,21 @@ const fixCommand = v => : v .split('and watch')[0] .trim() - .replace(/.+(`(.+?)`)$/, s => `${s} before this`) + + .replace( + /(.+)(`(.+?)`)(.+)?$/, + (s, $1, $2) => + `${$1}${$2} before this${ + s.split(/(.+)(`(.+?)`)(.+)$/).filter(g => g.trim() !== '')[ + s.split(/(.+)(`(.+?)`)(.+)$/).filter(g => g.trim() !== '') + .length - 1 + ] === s + ? '' + : s.split(/(.+)(`(.+?)`)(.+)$/).filter(g => g.trim() !== '')[ + s.split(/(.+)(`(.+?)`)(.+)$/).filter(g => g.trim() !== '') + .length - 1 + ] + }` + ) + (v.split('and watch')[1] ? ' and watch' + v.split('and watch')[1] : '') const isCommand = v => isCommandRe.test(v) && commandsRe.test(v) @@ -67,7 +81,6 @@ const parseCommand = v => { const regexGroups = isCommandRe .exec(v) .filter(regGroup => typeof regGroup !== undefined && regGroup !== undefined) - const [, , , parallelOrWatch] = regexGroups const taskNames = regexGroups[1].split(',').map(v => /`(.+)`/.exec(v)[1]) @@ -83,12 +96,23 @@ const parseCommand = v => { watchTargets = isWatchGroup(regexGroups[regexGroups.length - 2]) && regexGroups[regexGroups.length - 1].includes('`') - ? regexGroups[regexGroups.length - 1].replace(/`/g, '') + ? regexGroups[regexGroups.length - 1] + .replace(/`/g, '') + .split(',') + .map(target => target.trim()) : null + if (watchTargets !== null && watchTargets.length === 1) + watchTargets = watchTargets[0] } else if (isWatchGroup(parallelOrWatch)) { - watchTargets = regexGroups[regexGroups.length - 1].replace(/`/g, '') + watchTargets = regexGroups[regexGroups.length - 1] + .replace(/`/g, '') + .split(',') + .map(target => target.trim()) + if (watchTargets.length === 1) watchTargets = watchTargets[0] } + inParallel = /(.+?)in\s+parallel(.+?)?/i.test(v) + return { taskNames, when, @@ -225,3 +249,4 @@ module.exports = (content, { section, filepath } = {}) => { module.exports.isCommand = isCommand module.exports.parseCommand = parseCommand +module.exports.fixCommand = fixCommand From a5d3f7445ecc82024f22e0c86db6e2471fc0d0d2 Mon Sep 17 00:00:00 2001 From: Felix Wu Date: Wed, 6 Jun 2018 23:20:49 +0200 Subject: [PATCH 15/18] trying to fix the tests --- package.json | 11 +++- test/parseMarkdown.test.js | 63 ++++++++++++++-------- test/snapshots/parseMarkdown.test.js.md | 14 ++--- test/snapshots/parseMarkdown.test.js.snap | Bin 841 -> 753 bytes 4 files changed, 55 insertions(+), 33 deletions(-) diff --git a/package.json b/package.json index dfdf83d..5dffb17 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ ], "scripts": { "maid": "node bin/cli", - "test": "yarn maid lint && yarn maid test" + "test": "yarn maid lint && yarn maid test", + "ava": "ava test/parseMarkdown.test.js" }, "author": "egoist <0x142857@gmail.com>", "license": "MIT", @@ -53,6 +54,14 @@ "no-await-in-loop": "off" } }, + "ava": { + "cache": false, + "failFast": false, + "failWithoutAssertions": false, + "require": [ + "./lib/parseMarkdown" + ] + }, "husky": { "hooks": { "pre-commit": "lint-staged" diff --git a/test/parseMarkdown.test.js b/test/parseMarkdown.test.js index 69ba44e..231d7b4 100644 --- a/test/parseMarkdown.test.js +++ b/test/parseMarkdown.test.js @@ -1,6 +1,17 @@ const test = require('ava') const parseMarkdown = require('../lib/parseMarkdown') -const { isCommand, parseCommand } = parseMarkdown +const { isCommand, parseCommand, fixCommand } = parseMarkdown + +test('isCmd', t => { + t.false(isCommand('Run task')) + t.true(isCommand(fixCommand('Run task `blah`'))) + t.is(isCommand(fixCommand('run Task `blah`')), false) + t.is(isCommand(fixCommand('Runs task `blah`')), true) + t.is(isCommand(fixCommand('Run tasks `blah` and `blah`')), true) + t.is(isCommand(fixCommand('Runs tasks `blah` and `blah`')), true) + t.is(isCommand(fixCommand('Run tasks `blah` and `blah` in parallel')), false) + t.is(isCommand(fixCommand('Run task `blah` after this in parallel')), false) +}) test('simple', t => { const res = parseMarkdown(` @@ -32,38 +43,57 @@ echo goodbye }) test('parseCommand', t => { - t.deepEqual(parseCommand('run task `blah`'), { - taskNames: ['blah'], + t.deepEqual(parseCommand(fixCommand('run task `foo`')), { + taskNames: ['foo'], when: 'before', + watchTargets: null, inParallel: false }) t.deepEqual( - parseCommand('Runs task `blah` in parallel after this task has completed'), + parseCommand('Runs task `foo` in parallel after this task has completed'), { - taskNames: ['blah'], + taskNames: ['foo'], when: 'after', + watchTargets: null, inParallel: true } ) - t.deepEqual(parseCommand('run task `blah` in parallel'), { - taskNames: ['blah'], + t.deepEqual(parseCommand(fixCommand('run task `foo` in parallel')), { + taskNames: ['foo'], when: 'before', + watchTargets: null, inParallel: true }) - t.deepEqual(parseCommand('run tasks `blah`, `bleh`, and `blu`'), { - taskNames: ['blah', 'bleh', 'blu'], + t.deepEqual(parseCommand(fixCommand('run tasks `foo`, `bleh`, and `blu`')), { + taskNames: ['foo', 'bleh', 'blu'], when: 'before', + watchTargets: null, inParallel: false }) t.deepEqual( - parseCommand('run tasks `blah`, `bleh`, and `blu` after this in parallel'), + parseCommand( + 'run tasks `foo`, `bleh`, and `blu` after this and watch `lib/`' + ), + { + taskNames: ['foo', 'bleh', 'blu'], + when: 'after', + watchTargets: 'lib/', + inParallel: false + } + ) + + t.deepEqual( + parseCommand( + 'run tasks `foo`, `bleh`, and `blu` after this in parallel and watch `src/*.js`, `*.json`' + ), { - taskNames: ['blah', 'bleh', 'blu'], + taskNames: ['foo', 'bleh', 'blu'], when: 'after', + watchTargets: ['src/*.js', '*.json'], inParallel: true } ) @@ -123,14 +153,3 @@ MIT t.snapshot(res) }) - -test('isCommand', t => { - t.false(isCommand('Run task')) - t.true(isCommand('Run task `blah`')) - t.true(isCommand('run Task `blah`')) - t.true(isCommand('Runs task `blah`')) - t.true(isCommand('Run tasks `blah` and `blah`')) - t.true(isCommand('Runs tasks `blah` and `blah`')) - t.true(isCommand('Run tasks `blah` and `blah` in parallel')) - t.true(isCommand('Run task `blah` after this in parallel')) -}) diff --git a/test/snapshots/parseMarkdown.test.js.md b/test/snapshots/parseMarkdown.test.js.md index 33c6dcf..dbbc7a1 100644 --- a/test/snapshots/parseMarkdown.test.js.md +++ b/test/snapshots/parseMarkdown.test.js.md @@ -40,21 +40,15 @@ Generated by [AVA](https://ava.li). filepath: undefined, tasks: [ { - after: [ - { - inParallel: false, - taskNames: [ - 'goodbye', - ], - watchTargets: null, - }, - ], + after: [], before: [], description: `This script is used to say hey.␊ ␊ blockquote is unnecessary now and treated just like paragraph.␊ ␊ - But it's very complex so I'm writing some instructions.`, + But it's very complex so I'm writing some instructions.␊ + ␊ + Run task `goodbye` after this.`, name: 'hey', script: `console.log('key')␊ `, diff --git a/test/snapshots/parseMarkdown.test.js.snap b/test/snapshots/parseMarkdown.test.js.snap index 797659817e3bbc3b7d1ae3dee0669027d2afa5a9..890dddb88f982b8615aeacdc9732811490b171de 100644 GIT binary patch literal 753 zcmVLTI^haZaw00000000B+ zR!wLVK@^_1+w7*Xt!?}PJ?P-UR0=uxbJ0_am3q)(D2QUEo6RJ<`PoT#CT$l4MJOKB zgO}pPlf9_*rr1lpc=RNQhzAdXphZLxL=e%po20aBSWDE4nD8+(?|0t5`DS;F5TcT+ zOD{&gUK)8O`8Ia(_*@Z)|AP)fUhliJ`_z+%v*SBPFYlTe8h}7Lq7!m*;Qg)fL7vVa zPXb`EzDt~a_DAr;==-tugNPFd8*xM-B#*eRgbGl9DgeD7pw$xMDdHVstBTPOc{Sv% zwJKi=>YG(p)o0`-a|7}GDzTPjdG4~%)7p?@!gTGbw7JueDT`#xvIVWnDb8ww<*4LT z7#_8lmrf+IWp0)xYh1EG?>Nk4LKv<`9X?GB2hCk(NOUXKgrsG=#AwxUje=`bE%YC% zVPKgObc&&m$t%?|o1uc!qp1p=c5P`p1zc7zyd$JrGyVMubj;Ko>i3UM6gbaiJvKpu za;d~Er$fbcI7Wpv7b@;Nq}S>tHkX@B%)!enUFL=UREc@1J&ABc=~Y)EDn+rX1&>;{ z;8i;<0L^0lKdK(R@c^Q0>Sm_JX~Q(S8e!paTtkD7Fb&qKW6^3oS{+}j{--X0D~JWe zOIXhZ@C~h0B>r~9Uc?#11;j%5`iQsy9xE+R-?Gxi=_8ReD*as=b+)Ea|9={_O(XUP zXoP~!%<$mhW5el6F6hX)mW)#7QG9zHY=VG8_nMoHE#vqBp(7b~A~L_GPM7~(;cvx- jmS^S~v~y-Q2CtnQn`*oOBguefzt`;_mxmlDObGx0+{tQ+ literal 841 zcmV-P1GfA@RzVBElD#}n`fvFWmWuS#p34th*<6L7Wj-C20Z7m^zkPriO z=u%iZ!2*P>NH73@KoJrPAOLp@yZ7F`_wK#VqX;1p z%{+WH`1$hSbLMO#7l-C@82P^!5<+kG-swK|?9rvMuHh>?r~7*_mv%)FI@$C7)>!9< zw{suQzI$1&+{0Y@G=`9X6mE|dEbfQrLBMIi7OZq)c{rUTI>UHnnMfprujdhNMOZ+% z9n^%aSVUOr#L;onB1MfETp+h2+=lT4xT-5em`3xIpEM8R8-Tm@lOlbtt9e+cuR1z9 z_&G6tP8{R=)hJ=L>_4&ZM%f3s9{?N&n1F5pp~HY_A&`LhLjquZ4YcL~3xFqpWx#e3 zJct47Xju9(&yR?!^3%o{ak+SAl~_)bamOWrYPDnt)m^j5OvkRtgzIS3FsWLVtFS6j zl2MtXQq5Bh;-%v8wB_jeiIT$z-?wd|6G}DLQ*CET)of6AiN?SzSE5X{%sf$xnyY19 zt!RLKe+eA3BvmH~*yv87Xpw1^I_i;RL7j3?YFf!10=;%PIkl{CS zE_9-Vn;^cuhH0wu+Cn|pNClot00o9&2VmsaqXR)pJQNp)_RK6^PtR=PAJYt(tbNv- T4g;ud?6$uFD Date: Wed, 6 Jun 2018 23:33:29 +0200 Subject: [PATCH 16/18] make tasks execution on watch action asynchronous --- lib/FileWatcher.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/FileWatcher.js b/lib/FileWatcher.js index da47723..e262ba8 100644 --- a/lib/FileWatcher.js +++ b/lib/FileWatcher.js @@ -1,6 +1,11 @@ const chokidar = require('chokidar') const logger = require('./logger') +Array.prototype.asyncForEach = async function(callback) { // eslint-disable-line + for (let index = 0; index < this.length; index++) { + await callback(this[index], index, this) + } +} class FileWatcher { constructor() { this.runner = [] @@ -19,13 +24,19 @@ class FileWatcher { this.watcher[0] .on('add', path => - this.getTaskNames(path).forEach(taskName => runner.runFile(taskName)) + this.getTaskNames(path).forEach(async taskName => { + await runner.runFile(taskName) + }) ) .on('change', path => - this.getTaskNames(path).forEach(taskName => runner.runFile(taskName)) + this.getTaskNames(path).forEach(async taskName => { + await runner.runFile(taskName) + }) ) .on('unlink', path => - this.getTaskNames(path).forEach(taskName => runner.runFile(taskName)) + this.getTaskNames(path).forEach(async taskName => { + await runner.runFile(taskName) + }) ) .on('error', error => logger.log(`Watcher error: ${error}`)) } From 0604f209fa4925ddfdb482849185fbdcbe99a3e2 Mon Sep 17 00:00:00 2001 From: Felix Wu Date: Wed, 6 Jun 2018 23:37:21 +0200 Subject: [PATCH 17/18] use custom asyncForEach --- lib/FileWatcher.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/FileWatcher.js b/lib/FileWatcher.js index e262ba8..772134d 100644 --- a/lib/FileWatcher.js +++ b/lib/FileWatcher.js @@ -24,17 +24,17 @@ class FileWatcher { this.watcher[0] .on('add', path => - this.getTaskNames(path).forEach(async taskName => { + this.getTaskNames(path).asyncForEach(async taskName => { await runner.runFile(taskName) }) ) .on('change', path => - this.getTaskNames(path).forEach(async taskName => { + this.getTaskNames(path).asyncForEach(async taskName => { await runner.runFile(taskName) }) ) .on('unlink', path => - this.getTaskNames(path).forEach(async taskName => { + this.getTaskNames(path).asyncForEach(async taskName => { await runner.runFile(taskName) }) ) From af47299470bc80a3aeeb493b430617c94a193476 Mon Sep 17 00:00:00 2001 From: Felix Wu Date: Sun, 21 Oct 2018 21:57:08 +0200 Subject: [PATCH 18/18] CRC --- lib/FileWatcher.js | 1 + lib/parseMarkdown.js | 25 ++++++++++--------------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/lib/FileWatcher.js b/lib/FileWatcher.js index 772134d..2043167 100644 --- a/lib/FileWatcher.js +++ b/lib/FileWatcher.js @@ -6,6 +6,7 @@ Array.prototype.asyncForEach = async function(callback) { // eslint-disable-line await callback(this[index], index, this) } } + class FileWatcher { constructor() { this.runner = [] diff --git a/lib/parseMarkdown.js b/lib/parseMarkdown.js index f556aad..5f79c1a 100644 --- a/lib/parseMarkdown.js +++ b/lib/parseMarkdown.js @@ -56,21 +56,16 @@ const fixCommand = v => : v .split('and watch')[0] .trim() - .replace( - /(.+)(`(.+?)`)(.+)?$/, - (s, $1, $2) => - `${$1}${$2} before this${ - s.split(/(.+)(`(.+?)`)(.+)$/).filter(g => g.trim() !== '')[ - s.split(/(.+)(`(.+?)`)(.+)$/).filter(g => g.trim() !== '') - .length - 1 - ] === s - ? '' - : s.split(/(.+)(`(.+?)`)(.+)$/).filter(g => g.trim() !== '')[ - s.split(/(.+)(`(.+?)`)(.+)$/).filter(g => g.trim() !== '') - .length - 1 - ] - }` - ) + + .replace(/(.+)(`(.+?)`)(.+)?$/, (s, $1, $2) => { + let regexMatch = s + .split(/(.+)(`(.+?)`)(.+)$/) + .filter(g => g.trim() !== '') + return `${$1}${$2} before this${ + regexMatch[regexMatch.length - 1] === s + ? '' + : regexMatch[regexMatch.length - 1] + }` + }) + (v.split('and watch')[1] ? ' and watch' + v.split('and watch')[1] : '') const isCommand = v => isCommandRe.test(v) && commandsRe.test(v)