chore(nvim): update Corpus plug-in
[wincent.git] / helpers.ts
1 import {attributes, variable} from 'fig';
2
3 /**
4  * @file
5  *
6  * Project-local helpers.
7  */
8
9 /**
10  * Returns `true` if `conditions` apply.
11  *
12  * Note that although it is possible to specify multiple conditions with AND
13  * and OR semantics (as described in `when()` below), the `is()` function is
14  * probably best reserved for simple (non-compound) cases only; eg:
15  *
16  *    if (is('darwin')) {
17  *      // Do something on Darwin only...
18  *    }
19  *
20  * because using it for compound conditionals:
21  *
22  *    if (is(['codespaces', 'work'])) {
23  *      // ...
24  *    }
25  *
26  * is likely to be _less_ readable than spelling out the underlying expressions
27  * with explicit `||` and `&&` operators; eg:
28  *
29  *    if (profile === 'codespaces' || profile === 'work') {
30  *      // ...
31  *    }
32  *
33  */
34 export function is(...conditions: Array<Array<string> | string>): boolean {
35   return when(...conditions)() === true;
36 }
37
38 /**
39  * Returns a function that will return `true` if `conditions` apply, or a string
40  * explaining why they do not apply. Mainly intended as a convenience for
41  * defining conditional tasks:
42  *
43  *    task('do this thing', when('darwin'), async () => {
44  *      // Only on Darwin... When not on Darwin, task will be skipped
45  *      //  with the message "unsatisfied condition: (darwin)".
46  *    });
47  *
48  * `conditions` is an array, and its entries must be either strings or nested
49  * arrays of strings. Top-level conditions must be true using AND semantics.
50  * Nested conditions employ OR semantics.
51  *
52  * That is, if `conditions` is `[['arch', 'debian'], 'wincent']`, the semantics
53  * are equivalent to "(arch OR debian) AND (wincent)", which is incidentally
54  * also in the string that is returned if the conditions are not met:
55  *
56  *    unsatisfied condition: (arch OR debian) AND (wincent)
57  */
58 export function when(
59   ...conditions: Array<Array<string> | string>
60 ): () => true | string {
61   return () => {
62     if (
63       conditions.every((condition) =>
64         Array.isArray(condition)
65           ? condition.some(checkCondition)
66           : checkCondition(condition)
67       )
68     ) {
69       return true;
70     }
71
72     const description = conditions
73       .map((condition) => {
74         return `(${
75           Array.isArray(condition) ? condition.join(' OR ') : condition
76         })`;
77       })
78       .join(' AND ');
79
80     return `unsatisfied condition: ${description}`;
81   };
82 }
83
84 /**
85  * Provides a uniform interface for checking conditionals identified by a label.
86  *
87  * @internal
88  */
89 function checkCondition(label: string): boolean {
90   switch (label) {
91     case 'arch':
92       return attributes.distribution === 'arch';
93     case 'arm64':
94       return attributes.arch === 'arm64';
95     case 'codespaces':
96       return variable('hostHandle') === 'codespaces';
97     case 'darwin':
98       return attributes.platform === 'darwin';
99     case 'debian':
100       return attributes.distribution === 'debian';
101     case 'linux':
102       return attributes.platform === 'linux';
103     case 'personal':
104       return variable('profile') === 'personal';
105     case 'wincent':
106       return variable('identity') === 'wincent';
107     default:
108       throw new Error(
109         `checkCondition(): Unknown condition label ${JSON.stringify(label)}`
110       );
111   }
112 }