argot-cli 0.4.0-beta → 0.4.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Option type.
3
+ * Defines the kind of value an option accepts and how it is parsed.
4
+ */
5
+ export type OptionType = 'flag' | 'text' | 'int' | 'float' | 'count' | 'list' | 'alias';
6
+ /**
7
+ * Alias target type.
8
+ * Represents the set of option types that an alias may reference.
9
+ * Aliases cannot target other aliases.
10
+ */
11
+ export type AliasType = 'flag' | 'text' | 'int' | 'float' | 'count' | 'list';
12
+ /**
13
+ * Flag option entry.
14
+ * A flag is a boolean option that is set to true when present.
15
+ */
16
+ export type FlagEntry = {
17
+ type: 'flag';
18
+ };
19
+ /**
20
+ * Text option entry.
21
+ * A text option accepts a string value. A default value may be
22
+ * defined for command-line options provided without an associated
23
+ * value.
24
+ */
25
+ export type TextEntry = {
26
+ type: 'text';
27
+ default?: string;
28
+ };
29
+ /**
30
+ * Integer option entry.
31
+ * An integer option accepts a numeric value. The value must be a
32
+ * valid integer and must be a safe integer. A default value may
33
+ * be defined for command-line options provided without an
34
+ * associated value.
35
+ */
36
+ export type IntEntry = {
37
+ type: 'int';
38
+ default?: number;
39
+ };
40
+ /**
41
+ * Float option entry.
42
+ * A float option takes any value that can be represented as a
43
+ * floating point number. Integer values are converted to floats.
44
+ */
45
+ export type FloatEntry = {
46
+ type: 'float';
47
+ default?: number;
48
+ };
49
+ /**
50
+ * Count option entry.
51
+ * A count option increments a numeric counter each time the option
52
+ * is provided.
53
+ */
54
+ export type CountEntry = {
55
+ type: 'count';
56
+ };
57
+ /**
58
+ * List option entry.
59
+ * A list option accepts a string value and splits it into multiple
60
+ * entries using a separator.
61
+ * The separator defaults to a comma if not specified.
62
+ */
63
+ export type ListEntry = {
64
+ type: 'list';
65
+ sep?: string;
66
+ };
67
+ /**
68
+ * Alias option entry.
69
+ * An alias forwards its value to another option.
70
+ * The target must reference a valid, non-alias option.
71
+ */
72
+ export type AliasEntry = {
73
+ type: 'alias';
74
+ target: string;
75
+ };
76
+ /**
77
+ * A configuration entry representing a single command-line option.
78
+ */
79
+ export type ConfigEntry = (FlagEntry | TextEntry | IntEntry | FloatEntry | CountEntry | ListEntry | AliasEntry);
80
+ /**
81
+ * An object holding option configuration entries.
82
+ */
83
+ export type ConfigEntries = Record<string, ConfigEntry>;
84
+ /**
85
+ * ParserConfig configuration flags that don't refer to a single
86
+ * command-line option.
87
+ */
88
+ export type ParserOptions = {
89
+ allowUnknown?: boolean;
90
+ parseParameters?: boolean;
91
+ aggregateErrors?: boolean;
92
+ };
93
+ export type StrictParserOptions = {
94
+ allowUnknown: boolean;
95
+ parseParameters: boolean;
96
+ aggregateErrors: boolean;
97
+ };
98
+ /**
99
+ * The input object for the ParserConfig constructor.
100
+ * Contains configuration for specific command-line options
101
+ * and general configuration options.
102
+ */
103
+ export type ParserConfigInput = {
104
+ options: ConfigEntries;
105
+ parser?: ParserOptions;
106
+ };
107
+ /**
108
+ * Option value.
109
+ * Represents the possible values returned for parsed options.
110
+ */
111
+ export type OptionValue = boolean | string | number | string[];
112
+ /**
113
+ * Result mapping.
114
+ * A map-like structure used to store parsed key-value pairs.
115
+ * Instances can be frozen to prevent further modifications.
116
+ */
117
+ declare abstract class ResultMapping<K, V> extends Map<K, V> {
118
+ protected isFrozen: boolean;
119
+ constructor(entries?: [K, V][]);
120
+ /**
121
+ * Sets a value for the given key.
122
+ * Throws if the mapping is frozen.
123
+ */
124
+ set(key: K, value: V): this;
125
+ /**
126
+ * Deletes a key from the mapping.
127
+ * Throws if the mapping is frozen.
128
+ */
129
+ delete(key: K): boolean;
130
+ /**
131
+ * Clears all entries from the mapping.
132
+ * Throws if the mapping is frozen.
133
+ */
134
+ clear(): void;
135
+ /**
136
+ * Freezes the mapping, preventing further modifications.
137
+ */
138
+ freeze(): void;
139
+ /**
140
+ * Serializes the mapping to a plain object.
141
+ */
142
+ toJSON(): Record<string, V>;
143
+ }
144
+ /**
145
+ * Result list.
146
+ * An array-like structure used to store ordered values.
147
+ * Only numeric indices and the length property may be modified.
148
+ * Assigning any other property will throw an error.
149
+ */
150
+ declare abstract class ResultList<T> extends Array<T> {
151
+ constructor(args?: T[]);
152
+ }
153
+ /**
154
+ * Parsed option values.
155
+ * Values are determined by the parser configuration. Repeated options
156
+ * either overwrite previous values or accumulate them, depending on
157
+ * their type.
158
+ */
159
+ export declare class Options extends ResultMapping<string, OptionValue> {
160
+ }
161
+ /**
162
+ * Parsed key/value assignments.
163
+ * Parameters are parsed from arguments of the form "key=value" and do
164
+ * not depend on the parser configuration.
165
+ */
166
+ export declare class Parameters extends ResultMapping<string, string> {
167
+ }
168
+ /**
169
+ * Positional arguments.
170
+ * Operands are arguments that are not parsed as options or parameters.
171
+ */
172
+ export declare class Operands extends ResultList<string> {
173
+ }
174
+ /**
175
+ * The result of parsing operations.
176
+ * - options: parsed option values
177
+ * - parameters: parsed named parameters
178
+ * - operands: positional arguments
179
+ */
180
+ export interface ParseResult {
181
+ options: Options;
182
+ parameters?: Parameters;
183
+ operands: Operands;
184
+ }
185
+ export {};
package/dist/types.js ADDED
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Result mapping.
3
+ * A map-like structure used to store parsed key-value pairs.
4
+ * Instances can be frozen to prevent further modifications.
5
+ */
6
+ class ResultMapping extends Map {
7
+ isFrozen = false;
8
+ constructor(entries) {
9
+ super(entries);
10
+ // Prevent property from showing up in console.log.
11
+ Object.defineProperty(this, 'isFrozen', {
12
+ enumerable: false,
13
+ });
14
+ // Prevent regular object property assignments.
15
+ Object.preventExtensions(this);
16
+ }
17
+ /**
18
+ * Sets a value for the given key.
19
+ * Throws if the mapping is frozen.
20
+ */
21
+ set(key, value) {
22
+ if (this.isFrozen) {
23
+ const name = this.constructor.name.toLowerCase();
24
+ throw new TypeError(`you cannot modify parsed ${name}`);
25
+ }
26
+ return super.set(key, value);
27
+ }
28
+ /**
29
+ * Deletes a key from the mapping.
30
+ * Throws if the mapping is frozen.
31
+ */
32
+ delete(key) {
33
+ if (this.isFrozen) {
34
+ const name = this.constructor.name.toLowerCase();
35
+ throw new TypeError(`you cannot delete parsed ${name}`);
36
+ }
37
+ return super.delete(key);
38
+ }
39
+ /**
40
+ * Clears all entries from the mapping.
41
+ * Throws if the mapping is frozen.
42
+ */
43
+ clear() {
44
+ if (this.isFrozen) {
45
+ const name = this.constructor.name.toLowerCase();
46
+ throw new TypeError(`you cannot delete parsed ${name}`);
47
+ }
48
+ super.clear();
49
+ }
50
+ /**
51
+ * Freezes the mapping, preventing further modifications.
52
+ */
53
+ freeze() {
54
+ this.isFrozen = true;
55
+ Object.freeze(this);
56
+ }
57
+ /**
58
+ * Serializes the mapping to a plain object.
59
+ */
60
+ toJSON() {
61
+ return Object.fromEntries(this.entries());
62
+ }
63
+ }
64
+ /**
65
+ * Result list.
66
+ * An array-like structure used to store ordered values.
67
+ * Only numeric indices and the length property may be modified.
68
+ * Assigning any other property will throw an error.
69
+ */
70
+ class ResultList extends Array {
71
+ constructor(args) {
72
+ const length = args?.length ?? 0;
73
+ super(length);
74
+ for (let i = 0; i < length; i++) {
75
+ this[i] = args[i];
76
+ }
77
+ return new Proxy(this, {
78
+ set(target, prop, value) {
79
+ if (prop === 'length') {
80
+ target[prop] = value;
81
+ return true;
82
+ }
83
+ else if (String(Number(prop)) === prop) {
84
+ target[Number(prop)] = value;
85
+ return true;
86
+ }
87
+ throw new TypeError(`Cannot add non-index property: ${String(prop)}`);
88
+ }
89
+ });
90
+ }
91
+ }
92
+ /**
93
+ * Parsed option values.
94
+ * Values are determined by the parser configuration. Repeated options
95
+ * either overwrite previous values or accumulate them, depending on
96
+ * their type.
97
+ */
98
+ export class Options extends ResultMapping {
99
+ }
100
+ /**
101
+ * Parsed key/value assignments.
102
+ * Parameters are parsed from arguments of the form "key=value" and do
103
+ * not depend on the parser configuration.
104
+ */
105
+ export class Parameters extends ResultMapping {
106
+ }
107
+ /**
108
+ * Positional arguments.
109
+ * Operands are arguments that are not parsed as options or parameters.
110
+ */
111
+ export class Operands extends ResultList {
112
+ }
@@ -0,0 +1,48 @@
1
+ import type { ConfigEntry, ConfigEntries } from './types.ts';
2
+ /**
3
+ * Parses a string as an integer.
4
+ * The value must consist only of an optional sign followed by digits.
5
+ * The resulting number must be safely representable by the runtime.
6
+ * Throws:
7
+ * - InvalidIntError if the value is not a valid integer string
8
+ * - UnsafeIntegerError if the parsed value cannot be safely represented
9
+ */
10
+ export declare function parseIntStrict(value: string): number;
11
+ /**
12
+ * Parses a string as a floating-point number.
13
+ * The value must consist only of:
14
+ * - an optional sign
15
+ * - digits
16
+ * - an optional fractional part
17
+ * - an optional exponent
18
+ *
19
+ * Whitespace, Infinity and NaN are not allowed.
20
+ *
21
+ * Throws:
22
+ * - InvalidFloatError if the value is not a valid float string
23
+ */
24
+ export declare function parseFloatStrict(value: string): number;
25
+ /**
26
+ * Validates a single configuration entry.
27
+ * The entry must be a labeled object containing at least the "option"
28
+ * and "type" properties. Additional properties are validated according
29
+ * to the entry type.
30
+ * Throws:
31
+ * - TypeError if the entry is not an object or contains invalid values
32
+ * - MissingOptionPropertyError if a required property is missing
33
+ * - InvalidOptionTypeError if the option type is not supported
34
+ */
35
+ export declare function validateEntry(name: string, entry: ConfigEntry): void;
36
+ /**
37
+ * Validates a set of configuration entries.
38
+ * Each entry is validated individually. Alias entries are validated
39
+ * after all entries have been processed to ensure that:
40
+ * - the target exists
41
+ * - the target is not itself an alias
42
+ * Throws:
43
+ * - Any error raised by validateEntry
44
+ * - AliasTargetNotFoundError if an alias target does not exist
45
+ * - InvalidAliasTargetError if an alias targets another alias
46
+ */
47
+ export declare function validateEntries(entries: ConfigEntries): void;
48
+ export declare function validateEntriesAggregate(entries: ConfigEntries): void;
package/dist/utils.js ADDED
@@ -0,0 +1,175 @@
1
+ import { AliasTargetNotFoundError, ConfigError, InvalidAliasTargetError, InvalidFloatError, InvalidIntError, InvalidOptionTypeError, MissingOptionPropertyError, MissingOptionTypeError, UnsafeIntegerError, } from './errors.js';
2
+ /**
3
+ * Parses a string as an integer.
4
+ * The value must consist only of an optional sign followed by digits.
5
+ * The resulting number must be safely representable by the runtime.
6
+ * Throws:
7
+ * - InvalidIntError if the value is not a valid integer string
8
+ * - UnsafeIntegerError if the parsed value cannot be safely represented
9
+ */
10
+ export function parseIntStrict(value) {
11
+ if (!/^(-|\+)?\d+$/.test(value)) {
12
+ throw new InvalidIntError(value);
13
+ }
14
+ const num = Number(value);
15
+ if (!Number.isSafeInteger(num)) {
16
+ throw new UnsafeIntegerError(num);
17
+ }
18
+ return num;
19
+ }
20
+ /**
21
+ * Parses a string as a floating-point number.
22
+ * The value must consist only of:
23
+ * - an optional sign
24
+ * - digits
25
+ * - an optional fractional part
26
+ * - an optional exponent
27
+ *
28
+ * Whitespace, Infinity and NaN are not allowed.
29
+ *
30
+ * Throws:
31
+ * - InvalidFloatError if the value is not a valid float string
32
+ */
33
+ export function parseFloatStrict(value) {
34
+ if (!/^(-|\+)?(\d+(\.\d+)?|\.\d+)([eE](-|\+)?\d+)?$/.test(value)) {
35
+ throw new InvalidFloatError(value);
36
+ }
37
+ const num = Number(value);
38
+ if (!Number.isFinite(num)) {
39
+ throw new InvalidFloatError(value);
40
+ }
41
+ return num;
42
+ }
43
+ /**
44
+ * Validates a single configuration entry.
45
+ * The entry must be a labeled object containing at least the "option"
46
+ * and "type" properties. Additional properties are validated according
47
+ * to the entry type.
48
+ * Throws:
49
+ * - TypeError if the entry is not an object or contains invalid values
50
+ * - MissingOptionPropertyError if a required property is missing
51
+ * - InvalidOptionTypeError if the option type is not supported
52
+ */
53
+ export function validateEntry(name, entry) {
54
+ if (entry == null || typeof entry !== 'object') {
55
+ throw new TypeError('option config entry must be an object');
56
+ }
57
+ else if (!Object.hasOwn(entry, 'type')) {
58
+ throw new MissingOptionTypeError(name);
59
+ }
60
+ const tag = entry.type;
61
+ switch (tag) {
62
+ case 'flag':
63
+ case 'count': {
64
+ /* these types have no extra mandatory values */
65
+ break;
66
+ }
67
+ case 'text': {
68
+ if (!Object.hasOwn(entry, 'default'))
69
+ break;
70
+ const { default: def } = entry;
71
+ if (typeof def !== 'string')
72
+ throw new TypeError('default value must be a string');
73
+ break;
74
+ }
75
+ case 'int': {
76
+ if (!Object.hasOwn(entry, 'default'))
77
+ break;
78
+ const { default: def } = entry;
79
+ if (typeof def !== 'number' || !Number.isInteger(def))
80
+ throw new TypeError('default value must be an integer');
81
+ break;
82
+ }
83
+ case 'float': {
84
+ if (!Object.hasOwn(entry, 'default'))
85
+ break;
86
+ const { default: def } = entry;
87
+ if (typeof def !== 'number' || !Number.isFinite(def))
88
+ throw new TypeError('default value must be a number');
89
+ break;
90
+ }
91
+ case 'list': {
92
+ if (!Object.hasOwn(entry, 'sep'))
93
+ break;
94
+ const { sep } = entry;
95
+ if (typeof sep !== 'string')
96
+ throw new TypeError('sep value must be a string');
97
+ break;
98
+ }
99
+ case 'alias': {
100
+ if (!Object.hasOwn(entry, 'target')) {
101
+ throw new MissingOptionPropertyError(name, 'target');
102
+ }
103
+ const { target } = entry;
104
+ if (typeof target !== 'string')
105
+ throw new TypeError('target value must be a string');
106
+ break;
107
+ }
108
+ default: {
109
+ throw new InvalidOptionTypeError(tag);
110
+ }
111
+ }
112
+ }
113
+ /**
114
+ * Validates a set of configuration entries.
115
+ * Each entry is validated individually. Alias entries are validated
116
+ * after all entries have been processed to ensure that:
117
+ * - the target exists
118
+ * - the target is not itself an alias
119
+ * Throws:
120
+ * - Any error raised by validateEntry
121
+ * - AliasTargetNotFoundError if an alias target does not exist
122
+ * - InvalidAliasTargetError if an alias targets another alias
123
+ */
124
+ export function validateEntries(entries) {
125
+ const aliases = [];
126
+ for (const [name, entry] of Object.entries(entries)) {
127
+ validateEntry(name, entry);
128
+ const tag = entry.type;
129
+ if (tag === 'alias') {
130
+ const { target } = entry;
131
+ aliases.push([name, target]);
132
+ }
133
+ }
134
+ for (const [name, target] of aliases) {
135
+ if (!Object.hasOwn(entries, target)) {
136
+ throw new AliasTargetNotFoundError(name, target);
137
+ }
138
+ const targetEntry = entries[target];
139
+ if (targetEntry.type === 'alias') {
140
+ throw new InvalidAliasTargetError(name, target);
141
+ }
142
+ }
143
+ }
144
+ export function validateEntriesAggregate(entries) {
145
+ const aliases = [];
146
+ const error = new ConfigError('parser configuration is not valid');
147
+ for (const [name, entry] of Object.entries(entries)) {
148
+ try {
149
+ validateEntry(name, entry);
150
+ }
151
+ catch (err) {
152
+ error.push(err);
153
+ continue;
154
+ }
155
+ const tag = entry.type;
156
+ if (tag === 'alias') {
157
+ const { target } = entry;
158
+ aliases.push([name, target]);
159
+ }
160
+ }
161
+ for (const [name, target] of aliases) {
162
+ if (!Object.hasOwn(entries, target)) {
163
+ error.push(new AliasTargetNotFoundError(name, target));
164
+ continue;
165
+ }
166
+ const targetEntry = entries[target];
167
+ if (targetEntry.type === 'alias') {
168
+ error.push(new InvalidAliasTargetError(name, target));
169
+ }
170
+ }
171
+ if (error.errors.length === 1)
172
+ throw error.errors[0];
173
+ else if (error.errors.length > 1)
174
+ throw error;
175
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "argot-cli",
3
- "version": "0.4.0-beta",
3
+ "version": "0.4.0-beta.1",
4
4
  "description": "ArgotCLI is a command-line argument parser with a simple configuration format.",
5
5
  "type": "module",
6
6
  "scripts": {