]> git.wincent.com - wincent.git/commitdiff
feat: get terminfo aspect fully working
authorGreg Hurrell <greg@hurrell.net>
Sun, 29 Mar 2020 16:55:13 +0000 (18:55 +0200)
committerGreg Hurrell <greg@hurrell.net>
Sun, 29 Mar 2020 17:21:06 +0000 (19:21 +0200)
Still lots of refactoring to do, as evidenced by the comments, but this
is the first "working" version.

bin/common
src/Fig/Context.ts
src/Fig/operations/command.ts
src/Fig/operations/file.ts
src/Fig/status.ts [new file with mode: 0644]
src/console/index.ts
src/spawn.ts [new file with mode: 0644]

index 3ff44cf75f09f66691221e8702b6aeb08bbd89a4..2c0a3b2dbd8f85f5afafed85a2a0ffc833642d9b 100755 (executable)
@@ -22,6 +22,7 @@ export PATH="$BIN_DIR":$PATH
 
 # https://stackoverflow.com/a/5947802/2103996
 BOLD='\033[0;1m'
+GREEN='\033[1;32m'
 PURPLE='\033[1;35m'
 RED='\033[1;31m'
 RESET='\033[0m'
@@ -29,16 +30,20 @@ YELLOW='\033[1;33m'
 
 log_debug() {
   if [[ -n $DEBUG ]]; then
-    echo -e "${PURPLE}[debug]${RESET} $*" > /dev/stderr
+    echo -e "${PURPLE}[debug]  ${RESET} $*" > /dev/stderr
   fi
 }
 
 log_error() {
-  echo -e "${RED}[error]${RESET} $*" > /dev/stderr
+  echo -e "${RED}[error]  ${RESET} $*" > /dev/stderr
 }
 
 log_info() {
-  echo -e "${BOLD}[info]${RESET} $*" > /dev/stderr
+  echo -e "${BOLD}[info]   ${RESET} $*" > /dev/stderr
+}
+
+log_notice() {
+  echo -e "${GREEN}[notice] ${RESET} $*" > /dev/stderr
 }
 
 log_warn() {
index 0359aed5e2c507b57fc4ff361900734a7f71bb97..c5d772cced893588acda2942286508bd1c238bd4 100644 (file)
@@ -1,21 +1,71 @@
 import * as assert from 'assert';
 
 import Attributes from '../Attributes';
+import * as status from './status';
 
 import type {Aspect} from '../types/Project';
 
 /**
- * Here's some more global state (and a companion to
- * TaskRegistry). Necessary in order to keep our "aspect" DSL as
- * lightweight/implicit as possible.
+ * Try to keep nasty global state all together in one place.
+ *
+ * TODO: move global state out of TaskRegisty
+ *
+ * Global state helps keep our "aspect" DSL as lightweight/implicit as
+ * possible.
  */
 class Context {
   #attributes: Attributes;
+
+  // TODO: decide how to deal with "recap"; ansible prints something like this:
+  //
+  // PLAY RECAP
+  // ok=16 changed=7 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
+  #counts: {
+    changed: number;
+    failed: number;
+    ok: number;
+    skipped: number;
+  };
+
   #currentAspect?: Aspect;
+
   #currentVariables?: Variables;
 
   constructor() {
     this.#attributes = new Attributes();
+
+    this.#counts = {
+      changed: 0,
+      failed: 0,
+      ok: 0,
+      skipped: 0,
+    };
+  }
+
+  informChanged(message: string) {
+    this.#counts.changed++;
+
+    status.changed(message);
+  }
+
+  informFailed(message: string) {
+    this.#counts.failed++;
+
+    status.failed(message);
+
+    // TODO throw!
+  }
+
+  informOk(message: string) {
+    this.#counts.ok++;
+
+    status.ok(message);
+  }
+
+  informSkipped(message: string) {
+    this.#counts.skipped++;
+
+    status.skipped(message);
   }
 
   withContext(
index 60d7793d667e85d76eedd7497fb82c3fc264431a..7227730a35d15ecb301e075ceaf8cd6a3c6ebbba 100644 (file)
@@ -1,6 +1,17 @@
-import {log} from '../../console';
+import spawn from '../../spawn';
+import expand from '../../expand';
+import Context from '../Context';
 
-// TODO (probably) implement autoexpand of ~
+/**
+ * Implements basic shell expansion (of ~).
+ */
 export default function command(executable: string, ...args: Array<string>) {
-  log(`command: ${[executable, ...args].join(' ')}`);
+  try {
+    spawn(expand(executable), ...args.map(expand));
+    // TODO: decide whether to log full command here
+    Context.informChanged(`command \`${[executable, ...args].join(' ')}\``);
+  } catch (error) {
+    // TODO: add proper error message here, maybe metadata too
+    Context.informFailed('something went wrong');
+  }
 }
index 0cbb85e696b2b84ee2a259d1204106c2d617e9f8..ce1bcd211eb39a78e4ad1a62723e105e71b5c496 100644 (file)
@@ -2,6 +2,7 @@ import * as fs from 'fs';
 
 import {log} from '../../console';
 import expand from '../../expand';
+import Context from '../Context';
 
 // TODO decide whether we want a separate "directory" operation
 // TODO: implement auto-expand of ~
@@ -18,8 +19,6 @@ export default function file({
   state: 'directory' | 'file' | 'link' | 'touch';
   force?: boolean;
 }) {
-  log(`file: ${path} -> ${state}`);
-
   if (state === 'directory') {
     directory(path);
   }
@@ -29,13 +28,13 @@ function directory(path: string) {
   const target = expand(path);
 
   // TODO: find out if ansible replaces regular file with dir or just errors?
+  // TODO: actually throw for errors
   if (fs.existsSync(target)) {
     try {
       const stat = fs.statSync(target);
 
       if (stat.isDirectory()) {
-        // TODO: log "ok: ..."
-        log.info(`ok: directory ${path}`);
+        Context.informOk(`directory ${path}`);
       } else {
         log.error(`${path} already exists but is not a directory`);
       }
@@ -43,14 +42,7 @@ function directory(path: string) {
       log.error(`failed to stat: ${path}`);
     }
   } else {
-    // TODO: decide on who to log this stuff in a standard way
-    // ok: ...
-    // changed: ...
-    // skipping: ...
-    // at end:
-    // "PLAY RECAP"
-    // ok=16 changed=7 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
-    log.notice(`changed: directory ${path}`);
+    Context.informChanged(`directory ${path}`);
     fs.mkdirSync(target, {recursive: true});
   }
 }
diff --git a/src/Fig/status.ts b/src/Fig/status.ts
new file mode 100644 (file)
index 0000000..3c5ac1a
--- /dev/null
@@ -0,0 +1,20 @@
+import {log} from '../console';
+
+// TODO: ansible also has: unreachable, rescued, ignored
+// decide whether we need any of those.
+
+export function changed(message: string) {
+  log.notice(`changed: ${message}`);
+}
+
+export function failed(message: string) {
+  log.error(`failed: ${message}`);
+}
+
+export function ok(message: string) {
+  log.info(`ok: ${message}`);
+}
+
+export function skipped(message: string) {
+  log.info(`skipped: ${message}`);
+}
index 9a23daa689dbc6309537e231ee7b1dccba27242e..3a6fe76f5929520e6208bb09be463eb03316ce88 100644 (file)
@@ -21,6 +21,18 @@ export {COLORS};
 
 const {bold, green, purple, red, yellow} = COLORS;
 
+const PREFIXES = ['debug', 'error', 'info', 'notice', 'warning'];
+
+const PREFIX_LENGTH = PREFIXES.reduce((acc, prefix) => {
+  return Math.max(`[${prefix}] `.length, acc);
+}, 0);
+
+const PREFIX_MAP = Object.fromEntries(
+  PREFIXES.map((prefix) => {
+    return [prefix, `[${prefix}] `.padEnd(PREFIX_LENGTH)];
+  })
+);
+
 export function clear() {
   return new Promise((resolve) => {
     clearLine(process.stderr, 0, () => {
@@ -30,15 +42,15 @@ export function clear() {
 }
 
 function debug(message: string) {
-  log(purple.bold`[debug]` + ` ${message}`);
+  log(purple.bold`${PREFIX_MAP.debug}` + message);
 }
 
 function error(message: string) {
-  log(red.bold`[error]` + ` ${message}`);
+  log(red.bold`${PREFIX_MAP.error}` + message);
 }
 
 function info(message: string) {
-  log(bold`[info]` + ` ${message}`);
+  log(bold`${PREFIX_MAP.info}` + message);
 }
 
 export function log(...args: Array<any>) {
@@ -47,7 +59,7 @@ export function log(...args: Array<any>) {
 }
 
 function notice(message: string) {
-  log(green.bold`[notice]` + ` ${message}`);
+  log(green.bold`${PREFIX_MAP.notice}` + message);
 }
 
 export function print(...args: Array<any>) {
@@ -69,7 +81,7 @@ export function print(...args: Array<any>) {
 }
 
 function warn(message: string) {
-  log(yellow.bold`[warning]` + ` ${message}`);
+  log(yellow.bold`${PREFIX_MAP.warning}` + message);
 }
 
 log.clear = clear;
diff --git a/src/spawn.ts b/src/spawn.ts
new file mode 100644 (file)
index 0000000..6879273
--- /dev/null
@@ -0,0 +1,28 @@
+import {spawnSync} from 'child_process';
+
+import {log} from './console';
+
+export default function spawn(command: string, ...args: Array<string>) {
+  const {error, signal, status, stderr, stdout} = spawnSync(command, args, {
+    stdio: ['inherit', 'pipe', 'inherit'],
+  });
+
+  if (!error) {
+  } else {
+    const description = [command, ...args].join(' ');
+
+    if (error) {
+      log.error(`command ${description} encountered error: ${error}`);
+    } else if (signal) {
+      log.error(`command ${description} exited due to signal ${signal}`);
+    } else if (status) {
+      log.error(`command ${description} exited with status ${status}`);
+    }
+
+    log.debug(`\nstdout:\n\n${stdout}`);
+    log.debug(`\nstderr:\n\n${stderr}`);
+
+    // Want a subclass here so I can extract message
+    throw new Error('temporary error until I make a subclass');
+  }
+}