]> git.wincent.com - wincent.git/blob - src/getOptions.ts
745ce62214213aa4fefd01775cef3197dd87e2b7
[wincent.git] / src / getOptions.ts
1 import {promises as fs} from 'fs';
2 import * as path from 'path';
3
4 import {root} from './Fig';
5 import {COLORS, LOG_LEVEL, log, setLogLevel} from './console';
6 import dedent from './dedent';
7 import ErrorWithMetadata from './ErrorWithMetadata';
8 import escapeRegExpPattern from './escapeRegExpPattern';
9 import readAspect from './readAspect';
10 import {assertAspect} from './types/Project';
11
12 import type {LogLevel} from './console';
13 import type {Aspect} from './types/Project';
14
15 type Options = {
16     focused: Set<Aspect>;
17     logLevel: LogLevel;
18     startAt: {
19         found: boolean;
20         literal: string;
21         fuzzy?: RegExp;
22     };
23     testsOnly: boolean;
24 };
25
26 const {bold} = COLORS;
27
28 export default async function getOptions(
29     args: Array<string>
30 ): Promise<Options> {
31     const options: Options = {
32         focused: new Set(),
33         logLevel: LOG_LEVEL.INFO,
34         startAt: {
35             found: false,
36             literal: '',
37         },
38         testsOnly: false,
39     };
40
41     const directory = path.join(root, 'aspects');
42
43     const entries = await fs.readdir(directory, {withFileTypes: true});
44
45     const aspects: Array<[string, string]> = [];
46
47     for (const entry of entries) {
48         if (entry.isDirectory()) {
49             const name = entry.name;
50
51             const {description} = await readAspect(
52                 path.join(directory, name, 'aspect.json')
53             );
54
55             aspects.push([name, description]);
56         }
57     }
58
59     for (const arg of args) {
60         if (arg === '--debug' || arg === '-d') {
61             options.logLevel = LOG_LEVEL.DEBUG;
62         } else if (arg === '--quiet' || arg === '-q') {
63             options.logLevel = LOG_LEVEL.NOTICE;
64         } else if (arg === '--test' || arg === '-t') {
65             options.testsOnly = true;
66         } else if (arg === '--help' || arg === '-h') {
67             await printUsage(aspects);
68             throw new ErrorWithMetadata('aborting');
69         } else if (arg.startsWith('--start-at-task=')) {
70             options.startAt.literal = (
71                 arg.match(/^--start-at-task=(.*)/)?.[1] ?? ''
72             ).trim();
73             options.startAt.fuzzy = new RegExp(
74                 [
75                     '',
76                     ...options.startAt.literal
77                         .split(/\s+/)
78                         .map(escapeRegExpPattern),
79                     '',
80                 ].join('.*'),
81                 'i'
82             );
83         } else if (arg.startsWith('-')) {
84             throw new ErrorWithMetadata(
85                 `unrecognized argument ${JSON.stringify(
86                     arg
87                 )} - pass "--help" to see allowed options`
88             );
89         } else {
90             try {
91                 assertAspect(arg);
92                 options.focused.add(arg);
93             } catch {
94                 throw new ErrorWithMetadata(
95                     `unrecognized aspect ${JSON.stringify(
96                         arg
97                     )} - pass "--help" to see full list`
98                 );
99             }
100         }
101     }
102
103     return options;
104 }
105
106 async function printUsage(aspects: Array<[string, string]>) {
107     // TODO: actually implement all the switches mentioned here
108     log(
109         dedent`
110
111       ./install [options] [aspects...]
112
113       ${bold`Options:`}
114
115         -c/--check # not yet implemented
116         -d/--debug
117         -f/--force # not yet implemented
118         -h/--help
119         -q/--quiet
120         -t/--test
121         -v/--verbose (repeat up to four times for more verbosity) # not yet implemented
122         --start-at-task='aspect | task' # TODO: maybe make -s short variant
123         --step # not yet implemented
124
125       ${bold`Aspects:`}
126     `
127     );
128
129     for (const [aspect, description] of aspects) {
130         log(`  ${aspect}`);
131         log(`    ${description}`);
132     }
133
134     log();
135 }