]> git.wincent.com - wincent.git/commitdiff
refactor: add `arch`/`darwin`/`debian` helpers
authorGreg Hurrell <greg@hurrell.net>
Tue, 29 Mar 2022 13:50:39 +0000 (15:50 +0200)
committerGreg Hurrell <greg@hurrell.net>
Tue, 29 Mar 2022 13:50:39 +0000 (15:50 +0200)
As explained in the related issue, I don't know if these things belong
in the Fig core itself, so I'm adding a mechanism here for adding
arbitrary helper functions to a top-level "helpers.ts" file.

The three helpers I'm adding here allow me to DRY up the common pattern
of running specific tasks only on a specific platform. It works well for
this limited case; if we ever want an expressive way of saying "run only
on personal Darwin machines but not work Darwin machines", then we'll
need to look for something else, but at the moment there is no pressing
need.

Closes: https://github.com/wincent/wincent/issues/125
12 files changed:
aspects/apt/index.ts
aspects/aur/index.ts
aspects/dotfiles/index.ts
aspects/interception/index.ts
aspects/node/index.ts
aspects/nvim/index.ts
aspects/pacman/index.ts
aspects/systemd/index.ts
fig/dsl/task.ts
fig/index.ts
helpers.ts [new file with mode: 0644]
tsconfig.json

index 166a20825f76cffad6f20797b611d958e78aecbf..cdb95ae22ec7495f8fc78c567f316dd6f637f09b 100644 (file)
@@ -1,23 +1,8 @@
-import {
-  attributes,
-  command,
-  fail,
-  skip,
-  task as defineTask,
-  variable,
-} from 'fig';
+import {command, fail, helpers, skip, variable} from 'fig';
 
-function task(name: string, callback: () => Promise<void>) {
-  defineTask(name, async () => {
-    if (attributes.distribution === 'debian') {
-      await callback();
-    } else {
-      skip('not on Debian');
-    }
-  });
-}
+const {debian} = helpers;
 
-task('install packages', async () => {
+debian.task('install packages', async () => {
   for (const pkg of variable.strings('packages')) {
     const result = await command(
       'dpkg-query',
index 43b3dcca64676497aad5c109ca6f14da5e3587e1..c2a8bd6c2262a4303b8a3aab321b9bec7cb7a6cb 100644 (file)
@@ -1,27 +1,9 @@
-import {
-  attributes,
-  command,
-  file,
-  handler,
-  resource,
-  skip,
-  task as defineTask,
-  variable,
-} from 'fig';
+import {command, file, handler, helpers, resource, skip, variable} from 'fig';
 import {join} from 'path';
 
-// TODO: DRY this up; it is in three files now
-function task(name: string, callback: () => Promise<void>) {
-  defineTask(name, async () => {
-    if (attributes.distribution === 'arch') {
-      await callback();
-    } else {
-      skip('not on Arch Linux');
-    }
-  });
-}
+const {arch} = helpers;
 
-task('fetch yay', async () => {
+arch.task('fetch yay', async () => {
   // TODO: make a `git` operation? (if I need to do this in more than one
   // place; second place has arrived now, in the vim aspect.)
   await command('git', ['clone', 'https://aur.archlinux.org/yay.git/'], {
@@ -31,18 +13,18 @@ task('fetch yay', async () => {
   });
 });
 
-task('install yay', async () => {
+arch.task('install yay', async () => {
   await command('makepkg', ['-si', '--noconfirm'], {
     chdir: 'vendor/yay',
     creates: '/usr/bin/yay',
   });
 });
 
-task('install packages', async () => {
+arch.task('install packages', async () => {
   await command('yay', ['-S', '--noconfirm', ...variable.strings('packages')]);
 });
 
-task('create ~/.config/systemd/user', async () => {
+arch.task('create ~/.config/systemd/user', async () => {
   for (const directory of [
     '~/.config',
     '~/.config/systemd',
@@ -55,7 +37,7 @@ task('create ~/.config/systemd/user', async () => {
   }
 });
 
-task('install ~/.config/systemd/user/clipper.service', async () => {
+arch.task('install ~/.config/systemd/user/clipper.service', async () => {
   await file({
     notify: 'enable clipper.service',
     path: '~/.config/systemd/user/clipper.service',
@@ -64,7 +46,7 @@ task('install ~/.config/systemd/user/clipper.service', async () => {
   });
 });
 
-task('set up sensors', async () => {
+arch.task('set up sensors', async () => {
   for (const conf of [
     'etc/modprobe.d/it87.conf',
     'etc/modules-load.d/it87.conf',
@@ -79,7 +61,7 @@ task('set up sensors', async () => {
   }
 });
 
-task('set Microsoft Edge as default browser', async () => {
+arch.task('set Microsoft Edge as default browser', async () => {
   const result = await command('xdg-settings', [
     'check',
     'default-web-browser',
index 0af23028b3b6d41230b60d418be4ecd8c1cdc166..6478a4dfbb29ae4d02090e603d2cedcb0fabec95 100644 (file)
@@ -1,9 +1,9 @@
 import {
-  attributes,
   backup,
   command,
   fail,
   file,
+  helpers,
   log,
   path,
   prompt,
@@ -15,6 +15,8 @@ import {
   variables,
 } from 'fig';
 
+const {darwin} = helpers;
+
 variables(({hostHandle, identity}) => {
   return {
     gitHostSpecificInclude: `.gitconfig.d/${hostHandle}`,
@@ -107,19 +109,17 @@ task('create ~/code/.editorconfig', async () => {
   }
 });
 
-task('install glow.yml', async () => {
+darwin.task('install glow.yml', async () => {
   // On other platforms, Glow will read from ~/.config/glow/glow.yml.
-  if (attributes.platform === 'darwin') {
-    await file({
-      path: '~/Library/Preferences/glow',
-      state: 'directory',
-    });
-
-    await file({
-      force: true,
-      path: '~/Library/Preferences/glow/glow.yml',
-      src: path.aspect.join('files/Library/Preferences/glow/glow.yml'),
-      state: 'link',
-    });
-  }
+  await file({
+    path: '~/Library/Preferences/glow',
+    state: 'directory',
+  });
+
+  await file({
+    force: true,
+    path: '~/Library/Preferences/glow/glow.yml',
+    src: path.aspect.join('files/Library/Preferences/glow/glow.yml'),
+    state: 'link',
+  });
 });
index 4ea423f68975793764e5b5d6d0e23ef95b5a5dda..31e883ebf82d5c8ea95e72b8d5551916fe5bb9c3 100644 (file)
@@ -1,32 +1,15 @@
-import {
-  attributes,
-  command,
-  file,
-  handler,
-  resource,
-  skip,
-  task as defineTask,
-  template,
-} from 'fig';
+import {command, file, handler, helpers, resource, template} from 'fig';
 
-function task(name: string, callback: () => Promise<void>) {
-  defineTask(name, async () => {
-    if (attributes.distribution === 'arch') {
-      await callback();
-    } else {
-      skip('not on Arch Linux');
-    }
-  });
-}
+const {arch} = helpers;
 
-task('build mac2linux', async () => {
+arch.task('build mac2linux', async () => {
   const chdir = resource.support();
 
   await command('cmake', ['--build', '.'], {chdir});
   await command('make', [], {chdir});
 });
 
-task('install mac2linux', async () => {
+arch.task('install mac2linux', async () => {
   const chdir = resource.support();
 
   await command('cmake', ['--install', '.', '--prefix', '/usr'], {
@@ -36,7 +19,7 @@ task('install mac2linux', async () => {
   });
 });
 
-task('create /etc/interception', async () => {
+arch.task('create /etc/interception', async () => {
   await file({
     path: '/etc/interception',
     state: 'directory',
@@ -44,7 +27,7 @@ task('create /etc/interception', async () => {
   });
 });
 
-task('create /etc/interception/dual-function-keys.yaml', async () => {
+arch.task('create /etc/interception/dual-function-keys.yaml', async () => {
   await template({
     notify: 'enable udevmon',
     path: '/etc/interception/dual-function-keys.yaml',
@@ -53,7 +36,7 @@ task('create /etc/interception/dual-function-keys.yaml', async () => {
   });
 });
 
-task('create /etc/interception/udevmon.yaml', async () => {
+arch.task('create /etc/interception/udevmon.yaml', async () => {
   await template({
     notify: 'enable udevmon',
     path: '/etc/interception/udevmon.yaml',
@@ -66,7 +49,7 @@ task('create /etc/interception/udevmon.yaml', async () => {
 // Interception Tools, but it _is_ related to the keyboard and udev, so we
 // put it here. Depends on scripts installed by the dotfiles aspect, so the
 // separation of concerns is unclear.
-task('create /etc/udev/rules.d/50-realforce-layout.rules', async () => {
+arch.task('create /etc/udev/rules.d/50-realforce-layout.rules', async () => {
   await template({
     notify: 'reload udevadm',
     path: '/etc/udev/rules.d/50-realforce-layout.rules',
index 0d577a522110a8247e07e20d5ea0e10daa7c8aa5..cb0a508e0bceefbcefa45514288cb00e3ce45138 100644 (file)
@@ -1,4 +1,6 @@
-import {attributes, command, file, path, skip, task} from 'fig';
+import {command, file, helpers, path, skip, task} from 'fig';
+
+const {darwin} = helpers;
 
 const NODE_VERSION = '16.13.2';
 
@@ -10,10 +12,8 @@ task('make ~/n', async () => {
   await file({path: '~/n', state: 'directory'});
 });
 
-task('hide ~/n', async () => {
-  if (attributes.platform === 'darwin') {
-    await command('chflags', ['hidden', '~/n']);
-  }
+darwin.task('hide ~/n', async () => {
+  await command('chflags', ['hidden', '~/n']);
 });
 
 task(`install Node.js v${NODE_VERSION}`, async () => {
index ca1f8691a0670a7e1d9a3094e4e32ea40e3e5eb9..76a7adc29254146d2d921d104c1b98dccaaea0d5 100644 (file)
@@ -4,6 +4,7 @@ import {
   command,
   fetch,
   file,
+  helpers,
   path,
   resource,
   skip,
@@ -11,19 +12,7 @@ import {
   variable,
 } from 'fig';
 
-type Callback = Parameters<typeof task>[1];
-
-const debian = {
-  task(description: string, callback: Callback) {
-    task(description, async () => {
-      if (attributes.distribution === 'debian') {
-        await callback();
-      } else {
-        skip('not on Debian');
-      }
-    });
-  },
-};
+const {debian} = helpers;
 
 task('make directories', async () => {
   // Some overlap with "dotfiles" aspect here.
index 8b1640f930d4d73e8b66846ac978a063cf3aae9f..9c14349c39f99db606796f62f1ebab90ef8f2770 100644 (file)
@@ -3,24 +3,15 @@ import {
   command,
   file,
   handler,
+  helpers,
   line,
   path,
-  skip,
-  task as defineTask,
   variable,
 } from 'fig';
 
-function task(name: string, callback: () => Promise<void>) {
-  defineTask(name, async () => {
-    if (attributes.distribution === 'arch') {
-      await callback();
-    } else {
-      skip('not on Arch Linux');
-    }
-  });
-}
+const {arch} = helpers;
 
-task('copy /etc/pacman.conf', async () => {
+arch.task('copy /etc/pacman.conf', async () => {
   await file({
     path: '/etc/pacman.conf',
     src: path.aspect.join('files', 'etc/pacman.conf'),
@@ -29,11 +20,11 @@ task('copy /etc/pacman.conf', async () => {
   });
 });
 
-task('refresh package databases', async () => {
+arch.task('refresh package databases', async () => {
   await command('pacman', ['-Syy'], {sudo: true});
 });
 
-task('install packages', async () => {
+arch.task('install packages', async () => {
   // TODO: make this check rather than running unconditionally?
   await command(
     'pacman',
@@ -44,12 +35,12 @@ task('install packages', async () => {
   );
 });
 
-task('run updatedb', async () => {
+arch.task('run updatedb', async () => {
   await command('updatedb', [], {sudo: true});
 });
 
 // Tweaks: should be moved into separate aspects.
-task('configure faillock.conf', async () => {
+arch.task('configure faillock.conf', async () => {
   await line({
     path: '/etc/security/faillock.conf',
     regexp: /^\s*#?\s*deny\s*=/,
@@ -69,7 +60,7 @@ task('configure faillock.conf', async () => {
 // TODO: `export N_PREFIX=~`
 // TODO: run `n ??.??.??`
 
-task('create suspend hook', async () => {
+arch.task('create suspend hook', async () => {
   await file({
     notify: 'enable suspend hook',
     path: '/etc/systemd/system/suspend@.service',
index fa54a29b3e3110564c5481b6b8452d8028fd1c06..d6ac34d886ed54ff9a2849d32f8b7183cc2b5ace 100644 (file)
@@ -1,26 +1,8 @@
-import {
-  attributes,
-  command,
-  file,
-  handler,
-  path,
-  skip,
-  task as defineTask,
-  variable,
-} from 'fig';
+import {command, file, handler, helpers, path, skip, variable} from 'fig';
 
-// TODO: need to come up with a better pattern for arch-specific stuff
-function task(name: string, callback: () => Promise<void>) {
-  defineTask(name, async () => {
-    if (attributes.distribution === 'arch') {
-      await callback();
-    } else {
-      skip('not on Arch Linux');
-    }
-  });
-}
+const {arch} = helpers;
 
-task('set up hostname', async () => {
+arch.task('set up hostname', async () => {
   // Note that "hostname" is the variable configured in the aspect.json, which
   // overwrites the "hostname" that comes in from the Attributes class (via
   // Node's `os.hostname()`).
@@ -37,7 +19,7 @@ task('set up hostname', async () => {
   }
 });
 
-task('create ~/.config/systemd/user', async () => {
+arch.task('create ~/.config/systemd/user', async () => {
   // TODO: I am doing something similar with a `for` loop in the "aur" aspect;
   // maybe I should add `recurse: true` support to the `file` DSL.
   await file({path: '~/.config', state: 'directory'});
@@ -45,18 +27,21 @@ task('create ~/.config/systemd/user', async () => {
   await file({path: '~/.config/systemd/user', state: 'directory'});
 });
 
-task('set up ~/.config/systemd/user/pulseaudio-null-sink.service', async () => {
-  const unit = '.config/systemd/user/pulseaudio-null-sink.service';
-  await file({
-    force: true,
-    notify: ['systemd daemon-reload', 'enable pulseaudio-null-sink.service'],
-    path: path.home.join(unit),
-    src: path.aspect.join('files', unit),
-    state: 'link',
-  });
-});
+arch.task(
+  'set up ~/.config/systemd/user/pulseaudio-null-sink.service',
+  async () => {
+    const unit = '.config/systemd/user/pulseaudio-null-sink.service';
+    await file({
+      force: true,
+      notify: ['systemd daemon-reload', 'enable pulseaudio-null-sink.service'],
+      path: path.home.join(unit),
+      src: path.aspect.join('files', unit),
+      state: 'link',
+    });
+  }
+);
 
-task('set up ~/.config/systemd/user/ssh-agent.service', async () => {
+arch.task('set up ~/.config/systemd/user/ssh-agent.service', async () => {
   const unit = '.config/systemd/user/ssh-agent.service';
   await file({
     force: true,
index e90cb5607ce3c083658445a72d9bb51bafd462d6..ed6b55d0238b9ffa5da80e60200dc8b1ba31454d 100644 (file)
@@ -2,9 +2,11 @@ import Context from '../Context.js';
 import getCaller from '../getCaller.js';
 import getAspectFromCaller from '../getAspectFromCaller.js';
 
-export default function task(name: string, callback: () => Promise<void>) {
-  const caller = getCaller();
-
+export default function task(
+  name: string,
+  callback: () => Promise<void>,
+  caller: string = getCaller()
+) {
   const aspect = getAspectFromCaller(caller);
 
   Context.tasks.register(aspect, callback, `${aspect} | ${name}`);
index ad7150a69bcf129c2a7f4e6aa9a49bba371d48c8..7ca77f2ded55d2fe6d10b275a34ee0ef4a57991a 100644 (file)
@@ -33,3 +33,7 @@ export type {Path} from './path.js';
 export {log} from './console.js';
 export {default as path} from './path.js';
 export {default as prompt} from './prompt.js';
+
+// Re-export project-local helpers.
+
+export * as helpers from '../helpers.js';
diff --git a/helpers.ts b/helpers.ts
new file mode 100644 (file)
index 0000000..5cdefeb
--- /dev/null
@@ -0,0 +1,58 @@
+import {attributes, skip, task} from 'fig';
+import getCaller from 'fig/getCaller.js';
+
+type Callback = Parameters<typeof task>[1];
+
+/**
+ * @file
+ *
+ * Project-local helpers.
+ */
+
+export const arch = {
+  task(description: string, callback: Callback) {
+    task(
+      description,
+      async () => {
+        if (attributes.distribution === 'arch') {
+          await callback();
+        } else {
+          skip('not on Arch Linux');
+        }
+      },
+      getCaller()
+    );
+  },
+};
+
+export const darwin = {
+  task(description: string, callback: Callback) {
+    task(
+      description,
+      async () => {
+        if (attributes.platform === 'darwin') {
+          await callback();
+        } else {
+          skip('not on Darwin');
+        }
+      },
+      getCaller()
+    );
+  },
+};
+
+export const debian = {
+  task(description: string, callback: Callback) {
+    task(
+      description,
+      async () => {
+        if (attributes.distribution === 'debian') {
+          await callback();
+        } else {
+          skip('not on Debian');
+        }
+      },
+      getCaller()
+    );
+  },
+};
index 11e7f9821eddd68fd351bb9eb0e5ec5cfe5f8314..3d2ff516fe7bd1235cff27222642b517de90ea44 100644 (file)
@@ -17,5 +17,5 @@
     "target": "ES2019"
   },
   "exclude": ["aspects/interception/support/CMakeFiles/**", "node_modules"],
-  "include": ["aspects/**/*.ts", "fig/**/*.ts", "variables.ts"]
+  "include": ["aspects/**/*.ts", "fig/**/*.ts", "helpers.ts", "variables.ts"]
 }