]> git.wincent.com - wincent.git/commitdiff
feat: add --start-at-task support
authorGreg Hurrell <greg@hurrell.net>
Wed, 1 Apr 2020 22:06:15 +0000 (00:06 +0200)
committerGreg Hurrell <greg@hurrell.net>
Wed, 1 Apr 2020 22:06:15 +0000 (00:06 +0200)
This is very messy and could use a refactor, but it seems to work for
now.

1. Given an exact match, starts at that task.
2. Given a fuzzy match of one task, prompts for confirmation to run that
   task; if you hit "y" it runs it, and if you hit "n" the run aborts.
3. Given a fuzzy match of more than one task, shows a menu for you to
   pick from. Rejects invalid choices, but you can always escape by
   hitting CTRL-C.

src/Fig/globToRegExp.ts
src/chown.ts
src/escapeRegExpPattern.ts [new file with mode: 0644]
src/main.ts

index 42669e45ee85e10e7ed8eb420712ef73095d9878..4b7f21f3d8206bc6988c5a048efa3a6bd8d9bb29 100644 (file)
@@ -1,3 +1,5 @@
+import escapeRegExpPattern from '../escapeRegExpPattern';
+
 /**
  * Just supports simple globs ("*") for now.
  */
@@ -6,8 +8,3 @@ export default function globToRegExp(glob: string): RegExp {
 
   return new RegExp(pattern.replace(/\\\*/g, '[^/]+'));
 }
-
-function escapeRegExpPattern(pattern: string): string {
-  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
-  return pattern.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&');
-}
index 1a1598c8ab61590443e866b8ca80587c1b77d92e..0c8f4fea304d9956b217fc8c2d5018978f1ecb8a 100644 (file)
@@ -11,8 +11,9 @@ type Options = {
 export default async function chown(
   path: string,
   options: Options = {}
-): Promise<void> {
+): Promise<Error | null> {
   if (Context.attributes.platform === 'darwin') {
+    return null; // TODO finish
   } else {
     throw new Error('TODO: implement');
   }
diff --git a/src/escapeRegExpPattern.ts b/src/escapeRegExpPattern.ts
new file mode 100644 (file)
index 0000000..12519e8
--- /dev/null
@@ -0,0 +1,4 @@
+export default function escapeRegExpPattern(pattern: string): string {
+  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
+  return pattern.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&');
+}
index 498392068234c3fa47ed4ad138b7b76c4af2f7c7..1ec5513be965b00f2fab4ce1eb92c05e95a7eba6 100644 (file)
@@ -5,7 +5,9 @@ import ErrorWithMetadata from './ErrorWithMetadata';
 import Context from './Fig/Context';
 import {root} from './Fig';
 import {LOG_LEVEL, log, setLogLevel} from './console';
+import escapeRegExpPattern from './escapeRegExpPattern';
 import merge from './merge';
+import prompt from './prompt';
 import readAspect from './readAspect';
 import readProject from './readProject';
 import regExpFromString from './regExpFromString';
@@ -17,6 +19,16 @@ async function main() {
     throw new ErrorWithMetadata('Cannot run as root');
   }
 
+  const startAt = {
+    found: false,
+    fuzzy: undefined,
+    literal: '',
+  } as {
+    found: boolean;
+    fuzzy?: RegExp;
+    literal: string;
+  };
+
   let testsOnly = false;
 
   process.argv.forEach((arg) => {
@@ -28,6 +40,14 @@ async function main() {
       testsOnly = true;
     } else if (arg === '--help' || arg === '-h') {
       // TODO: print and exit
+    } else if (arg.startsWith('--start-at-task=')) {
+      startAt.literal = (arg.match(/^--start-at-task=(.*)/)?.[1] ?? '').trim();
+      startAt.fuzzy = new RegExp(
+        ['', ...startAt.literal.split(/\s+/).map(escapeRegExpPattern), ''].join(
+          '.*'
+        ),
+        'i'
+      );
     } else {
       // TODO: error for bad args
     }
@@ -81,6 +101,8 @@ async function main() {
   ];
 
   // Register tasks.
+  const candidateTasks = [];
+
   for (const aspect of aspects) {
     switch (aspect) {
       case 'launchd':
@@ -90,12 +112,69 @@ async function main() {
         require('../aspects/terminfo');
         break;
     }
+
+    // Check for an exact match of the starting task if `--start-at-task=` was
+    // supplied.
+    for (const [, name] of Context.tasks.get(aspect)) {
+      if (name === startAt.literal) {
+        startAt.found = true;
+      } else if (!startAt.found && startAt.fuzzy && startAt.fuzzy.test(name)) {
+        candidateTasks.push(name);
+      }
+    }
+  }
+
+  if (!startAt.found && candidateTasks.length === 1) {
+    log.notice(`Matching task found: ${candidateTasks[0]}`);
+
+    log();
+
+    const reply = await prompt('Start running at this task? [y/n]: ');
+
+    if (/^\s*y(?:e(?:s)?)?\s*$/i.test(reply)) {
+      startAt.found = true;
+      startAt.literal = candidateTasks[0];
+    } else {
+      throw new ErrorWithMetadata('User aborted');
+    }
+  } else if (!startAt.found && candidateTasks.length > 1) {
+    log.notice(`${candidateTasks.length} tasks found:\n`);
+
+    const width = candidateTasks.length.toString().length;
+
+    while (!startAt.found) {
+      candidateTasks.forEach((name, i) => {
+        log(`${(i + 1).toString().padStart(width)}: ${name}`);
+      });
+
+      log();
+
+      const reply = await prompt('Start at task number: ');
+
+      const choice = parseInt(reply.trim(), 10);
+
+      if (
+        Number.isNaN(choice) ||
+        choice < 1 ||
+        choice > candidateTasks.length
+      ) {
+        log.warn(
+          `Invalid choice ${JSON.stringify(
+            reply
+          )}; try again or press CTRL-C to abort.`
+        );
+
+        log();
+      } else {
+        startAt.found = true;
+        startAt.literal = candidateTasks[choice - 1];
+      }
+    }
   }
 
   const baseVariables = merge(profileVariables, platformVariables);
 
-  // Execute tasks. (Separate step so we can bail on configuration errors before
-  // touching filesystem).
+  // Execute tasks.
   try {
     for (const aspect of aspects) {
       const {description, variables: aspectVariables = {}} = await readAspect(
@@ -105,14 +184,17 @@ async function main() {
 
       const variables = merge(aspectVariables, baseVariables);
 
-      log.debug(`variables:\n\n${JSON.stringify(variables, null, 2)}\n`);
+      // log.debug(`variables:\n\n${JSON.stringify(variables, null, 2)}\n`);
 
       for (const [callback, name] of Context.tasks.get(aspect)) {
-        log.info(`task: ${name}`);
-
-        await Context.withContext({aspect, variables}, async () => {
-          await callback();
-        });
+        if (!startAt.found || name === startAt.literal) {
+          startAt.found = false;
+          log.info(`task: ${name}`);
+
+          await Context.withContext({aspect, variables}, async () => {
+            await callback();
+          });
+        }
       }
     }
   } finally {