]> git.wincent.com - wincent.git/commitdiff
feat: merge variables
authorGreg Hurrell <greg@hurrell.net>
Sat, 28 Mar 2020 00:29:22 +0000 (01:29 +0100)
committerGreg Hurrell <greg@hurrell.net>
Sat, 28 Mar 2020 00:31:16 +0000 (01:31 +0100)
Precedence (from least to most) is:

1. Aspect variables (ie. effectively these are defaults).
2. Profile variables (eg. "personal", "work").
2. Platform variables (eg. "darwin", "linux").

Trying to be analagous to Ansible's precedence:

1. Role defaults
2. Inventory/Group vars
3. Playbook vars

Via: https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html

src/Fig/task.ts
src/__tests__/merge-test.ts [new file with mode: 0644]
src/main.ts
src/merge.ts [new file with mode: 0644]

index 1a3db323a3b1120d9feb98c0b19a3935d849cd51..06ce4033c443bb77a676830a32f153c239a5e18c 100644 (file)
@@ -21,11 +21,8 @@ export default function task(name: string, callback: (Fig: Fig) => void) {
   }
 
   // TODO: use `caller` to make namespaced task name.
-  // TODO: read "aspect.json" from aspect
-  console.log(`caller: ${aspect}`);
 
-  // TODO: make this less verbose?; need to explicitly recreate this object here
-  // in order to avoid a circular dependency
+  // Create a new `Fig` object here to avoid circular dependency.
   callback({
     command,
     file,
diff --git a/src/__tests__/merge-test.ts b/src/__tests__/merge-test.ts
new file mode 100644 (file)
index 0000000..199d472
--- /dev/null
@@ -0,0 +1,40 @@
+import {expect, test} from '../test/harness';
+import merge from '../merge';
+
+test('merge() returns an single object', () => {
+  expect(merge({example: 'obj'})).toEqual({example: 'obj'});
+});
+
+test('merge() merges two objects with non-overlapping keys', () => {
+  expect(merge({example: 'obj'}, {more: 'stuff'})).toEqual({
+    example: 'obj',
+    more: 'stuff',
+  });
+});
+
+test('merge() merges two objects with overlapping keys', () => {
+  expect(
+    merge({example: 'obj', more: 'things'}, {more: 'stuff', and: true})
+  ).toEqual({example: 'obj', more: 'stuff', and: true});
+});
+
+test('merge() overwrites arrays', () => {
+  expect(merge({list: [1, 2, 3]}, {list: ['a', 'b', 'c', 'd']})).toEqual({
+    list: ['a', 'b', 'c', 'd'],
+  });
+});
+
+test('merge() deep-merges objects', () => {
+  expect(
+    merge(
+      {thing: true, nested: {prop: 'value'}},
+      {thing: true, nested: {other: false}}
+    )
+  ).toEqual({
+    thing: true,
+    nested: {
+      prop: 'value',
+      other: false,
+    },
+  });
+});
index c0a51d33500570fd658fe48b96f93135b46eec94..2c1ea9a9f92dff905d430a06bf83789e033a20c1 100644 (file)
@@ -4,6 +4,7 @@ import * as path from 'path';
 import Attributes from './Attributes';
 import {root} from './Fig';
 import {log} from './console';
+import merge from './merge';
 import readAspect from './readAspect';
 import readProject from './readProject';
 import regExpFromString from './regExpFromString';
@@ -16,9 +17,9 @@ log.debug(JSON.stringify(process.argv, null, 2));
 
 async function main() {
   log.info('Running tests');
+
   await test();
 
-  // Determine profile.
   const project = await readProject(path.join(root, 'project.json'));
 
   const hostname = os.hostname();
@@ -34,22 +35,31 @@ async function main() {
 
   log.info(`Profile: ${profile || 'n/a'}`);
 
+  const profileVariables: {[key: string]: JSONValue} = profile
+    ? profiles[profile]!.variables ?? {}
+    : {};
+
   const attributes = new Attributes();
 
   const platform = await attributes.getPlatform();
 
   log.info(`Platform: ${platform}`);
 
-  const platforms = project.platforms;
+  const {aspects, variables: platformVariables = {}} = project.platforms[
+    platform
+  ];
 
-  const {aspects} = project.platforms[platform];
+  const baseVariables = merge(profileVariables, platformVariables);
 
   for (const aspect of aspects) {
-    const {description, variables} = await readAspect(
+    const {description, variables: aspectVariables = {}} = await readAspect(
       path.join(root, 'aspects', aspect, 'aspect.json')
     );
     log.info(`${aspect}: ${description}`);
-    console.log(variables);
+
+    const mergedVariables = merge(aspectVariables, baseVariables);
+
+    log.debug(`variables:\n\n${JSON.stringify(mergedVariables, null, 2)}\n`);
 
     switch (aspect) {
       case 'terminfo':
diff --git a/src/merge.ts b/src/merge.ts
new file mode 100644 (file)
index 0000000..5f96583
--- /dev/null
@@ -0,0 +1,40 @@
+type Variables = {
+  [key: string]: JSONValue;
+};
+
+export default function merge(
+  variables: Variables,
+  ...rest: Array<Variables>
+): Variables {
+  if (!rest.length) {
+    return variables;
+  } else if (rest.length === 1) {
+    return mergeObjects(variables, rest[0]);
+  } else {
+    const last = rest.pop()!;
+    const penultimate = rest.pop()!;
+
+    return merge(variables, ...rest.concat(merge(penultimate, last)));
+  }
+}
+
+function mergeObjects(target: Variables, source: Variables): Variables {
+  const output: Variables = {...target};
+
+  Object.entries(source).forEach(([key, value]) => {
+    if (
+      value &&
+      typeof value === 'object' &&
+      !Array.isArray(value) &&
+      target[key] &&
+      typeof target[key] === 'object' &&
+      !Array.isArray(target[key])
+    ) {
+      output[key] = mergeObjects(target[key] as Variables, value as Variables);
+    } else {
+      output[key] = value;
+    }
+  });
+
+  return output;
+}