]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - bin/fncli.js
import terminus-font-4.48
[FreeBSD/FreeBSD.git] / bin / fncli.js
1 //
2 // Copyright (c) 2019 Dimitar Toshkov Zhekov <dimitar.zhekov@gmail.com>
3 //
4 // This program is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU General Public License as
6 // published by the Free Software Foundation; either version 2 of
7 // the License, or (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14
15 'use strict';
16
17
18 class Params {
19         constructor() {
20                 this.excstk = false;
21         }
22 }
23
24
25 class Options {
26         constructor(needArgs, helpText, versionText) {
27                 needArgs.forEach(name => {
28                         if (!name.match(/^(-[^-]|--[^=]+)$/)) {
29                                 throw new Error(`invalid option name "${name}"`);
30                         }
31                 });
32                 this.needArgs = needArgs;
33                 this.helpText = helpText;
34                 this.versionText = versionText;
35         }
36
37         posixlyCorrect() {  // eslint-disable-line class-methods-use-this
38                 return process.env['POSIXLY_CORRECT'] != null;
39         }
40
41         needsArg(name) {
42                 return this.needArgs.includes(name);
43         }
44
45         fallback(name, params) {
46                 if (name === '--excstk') {
47                         params.excstk = true;
48                 } else if (name === '--help' && this.helpText != null) {
49                         process.stdout.write(this.helpText);
50                         process.exit(0);
51                 } else if (name === '--version' && this.versionText != null) {
52                         process.stdout.write(this.versionText);
53                         process.exit(0);
54                 } else {
55                         let suffix = this.needsArg(name) ? ' (taking an argument?)' : '';
56
57                         suffix += (this.helpText != null) ? ', try --help' : '';
58                         throw new Error(`unknown option "${name}"${suffix}`);
59                 }
60         }
61
62         reader(args, skip = 2) {
63                 return new Options.Reader(this, args, skip);
64         }
65 }
66
67
68 Options.Reader = class {
69         constructor(options, args, skip) {
70                 this.options = options;
71                 this.args = args;
72                 this.skip = skip;
73         }
74
75         forEach(callback) {
76                 let optind;
77
78                 for (optind = this.skip; optind < this.args.length; optind++) {
79                         let arg = this.args[optind];
80
81                         if (arg === '-' || !arg.startsWith('-')) {
82                                 if (this.options.posixlyCorrect()) {
83                                         break;
84                                 }
85                                 callback(null, arg);
86                         } else if (arg === '--') {
87                                 optind++;
88                                 break;
89                         } else {
90                                 let name, value;
91
92                                 if (!arg.startsWith('--')) {
93                                         for (;;) {
94                                                 name = arg.substring(0, 2);
95                                                 value = (name !== arg) ? arg.substring(2) : null;
96
97                                                 if (this.options.needsArg(name) || value == null) {
98                                                         break;
99                                                 }
100                                                 callback(name, null);
101                                                 arg = '-' + value;
102                                         }
103                                 } else if (arg.indexOf('=') >= 3) {
104                                         name = arg.split('=', 1)[0];
105                                         if (!this.options.needsArg(name)) {
106                                                 throw new Error(`option "${name}" does not take an argument`);
107                                         }
108                                         value = arg.substring(name.length + 1);
109                                 } else {
110                                         name = arg;
111                                         value = null;
112                                 }
113
114                                 if (value === null && Number(this.options.needsArg(name)) > 0) {
115                                         if (++optind === this.args.length) {
116                                                 throw new Error(`option "${name}" requires an argument`);
117                                         }
118                                         value = this.args[optind];
119                                 }
120                                 callback(name, value);
121                         }
122                 }
123
124                 this.args.slice(optind).forEach(value => callback(null, value));
125         }
126 };
127
128 Object.defineProperty(Options, 'Reader', { 'enumerable': false });
129 Object.defineProperty(Options.Reader, 'name', { value: 'Reader' });
130
131
132 function start(programName, options, params, mainProgram) {  // eslint-disable-line consistent-return
133         let parsed = (params != null) ? params : new Params();
134
135         try {
136                 const version = process.version.match(/^v?(\d+)\.(\d+)/);
137
138                 if (version.length < 3) {
139                         throw new Error('unable to obtain node version');
140                 } else if ((parseInt(version[1]) * 1000 + parseInt(version[2])) < 6009) {
141                         throw new Error('node version 6.9.0 or later required');
142                 }
143
144                 if (params == null) {
145                         return mainProgram(options.reader(process.argv), name => options.fallback(name, parsed));
146                 } else {
147                         let nonopt = [];
148
149                         options.reader(process.argv).forEach((name, value) => {
150                                 if (name == null) {
151                                         nonopt.push(value);
152                                 } else {
153                                         options.parse(name, value, parsed);
154                                 }
155                         });
156                         return mainProgram(nonopt, parsed);
157                 }
158         } catch (e) {
159                 if (parsed.excstk) {
160                         throw e;
161                 } else {
162                         process.stderr.write(`${process.argv.length >= 2 ? process.argv[1] : programName}: ${e.message}\n`);
163                         process.exit(1);
164                 }
165         }
166 }
167
168
169 module.exports = Object.freeze({
170         Params,
171         Options,
172         start
173 });