@sectui/operations 0.0.0-stage → 0.1.0

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.
Files changed (147) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +50 -2
  3. package/dist/args.d.ts +8 -0
  4. package/dist/args.js +47 -0
  5. package/dist/args.js.map +1 -0
  6. package/dist/cli-flags.d.ts +7 -0
  7. package/dist/cli-flags.js +28 -0
  8. package/dist/cli-flags.js.map +1 -0
  9. package/dist/cli.d.ts +2 -0
  10. package/dist/cli.js +185 -0
  11. package/dist/cli.js.map +1 -0
  12. package/dist/completions.d.ts +3 -0
  13. package/dist/completions.js +113 -0
  14. package/dist/completions.js.map +1 -0
  15. package/dist/display.d.ts +4 -0
  16. package/dist/display.js +29 -0
  17. package/dist/display.js.map +1 -0
  18. package/dist/errors.d.ts +6 -0
  19. package/dist/errors.js +7 -0
  20. package/dist/errors.js.map +1 -0
  21. package/dist/index.d.ts +4 -0
  22. package/dist/index.js +5 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/operation.d.ts +64 -0
  25. package/dist/operation.js +5 -0
  26. package/dist/operation.js.map +1 -0
  27. package/dist/operations/base/alphabets.d.ts +28 -0
  28. package/dist/operations/base/alphabets.js +27 -0
  29. package/dist/operations/base/alphabets.js.map +1 -0
  30. package/dist/operations/base/base32.d.ts +33 -0
  31. package/dist/operations/base/base32.js +85 -0
  32. package/dist/operations/base/base32.js.map +1 -0
  33. package/dist/operations/base/base58.d.ts +34 -0
  34. package/dist/operations/base/base58.js +43 -0
  35. package/dist/operations/base/base58.js.map +1 -0
  36. package/dist/operations/base/base62.d.ts +28 -0
  37. package/dist/operations/base/base62.js +43 -0
  38. package/dist/operations/base/base62.js.map +1 -0
  39. package/dist/operations/base/base64.d.ts +19 -0
  40. package/dist/operations/base/base64.js +72 -0
  41. package/dist/operations/base/base64.js.map +1 -0
  42. package/dist/operations/base/base64url.d.ts +15 -0
  43. package/dist/operations/base/base64url.js +44 -0
  44. package/dist/operations/base/base64url.js.map +1 -0
  45. package/dist/operations/base/big-number.d.ts +6 -0
  46. package/dist/operations/base/big-number.js +54 -0
  47. package/dist/operations/base/big-number.js.map +1 -0
  48. package/dist/operations/color/hex-to-rgb.d.ts +14 -0
  49. package/dist/operations/color/hex-to-rgb.js +41 -0
  50. package/dist/operations/color/hex-to-rgb.js.map +1 -0
  51. package/dist/operations/data/json-format.d.ts +20 -0
  52. package/dist/operations/data/json-format.js +33 -0
  53. package/dist/operations/data/json-format.js.map +1 -0
  54. package/dist/operations/data/json.d.ts +20 -0
  55. package/dist/operations/data/json.js +59 -0
  56. package/dist/operations/data/json.js.map +1 -0
  57. package/dist/operations/data/yaml.d.ts +21 -0
  58. package/dist/operations/data/yaml.js +54 -0
  59. package/dist/operations/data/yaml.js.map +1 -0
  60. package/dist/operations/extract/extract.d.ts +55 -0
  61. package/dist/operations/extract/extract.js +129 -0
  62. package/dist/operations/extract/extract.js.map +1 -0
  63. package/dist/operations/hash/bcrypt.d.ts +16 -0
  64. package/dist/operations/hash/bcrypt.js +45 -0
  65. package/dist/operations/hash/bcrypt.js.map +1 -0
  66. package/dist/operations/hash/hashes.d.ts +10 -0
  67. package/dist/operations/hash/hashes.js +29 -0
  68. package/dist/operations/hash/hashes.js.map +1 -0
  69. package/dist/operations/hex/from-hex.d.ts +1 -0
  70. package/dist/operations/hex/from-hex.js +31 -0
  71. package/dist/operations/hex/from-hex.js.map +1 -0
  72. package/dist/operations/hex/to-hex.d.ts +31 -0
  73. package/dist/operations/hex/to-hex.js +44 -0
  74. package/dist/operations/hex/to-hex.js.map +1 -0
  75. package/dist/operations/html/entities.d.ts +2 -0
  76. package/dist/operations/html/entities.js +69 -0
  77. package/dist/operations/html/entities.js.map +1 -0
  78. package/dist/operations/html/html-decode.d.ts +1 -0
  79. package/dist/operations/html/html-decode.js +36 -0
  80. package/dist/operations/html/html-decode.js.map +1 -0
  81. package/dist/operations/html/html-encode.d.ts +8 -0
  82. package/dist/operations/html/html-encode.js +30 -0
  83. package/dist/operations/html/html-encode.js.map +1 -0
  84. package/dist/operations/text/case.d.ts +14 -0
  85. package/dist/operations/text/case.js +85 -0
  86. package/dist/operations/text/case.js.map +1 -0
  87. package/dist/operations/text/count.d.ts +19 -0
  88. package/dist/operations/text/count.js +59 -0
  89. package/dist/operations/text/count.js.map +1 -0
  90. package/dist/operations/text/line-operations.d.ts +48 -0
  91. package/dist/operations/text/line-operations.js +134 -0
  92. package/dist/operations/text/line-operations.js.map +1 -0
  93. package/dist/operations/text/lines.d.ts +13 -0
  94. package/dist/operations/text/lines.js +22 -0
  95. package/dist/operations/text/lines.js.map +1 -0
  96. package/dist/operations/text/text-operations.d.ts +37 -0
  97. package/dist/operations/text/text-operations.js +79 -0
  98. package/dist/operations/text/text-operations.js.map +1 -0
  99. package/dist/operations/url/url-decode.d.ts +8 -0
  100. package/dist/operations/url/url-decode.js +56 -0
  101. package/dist/operations/url/url-decode.js.map +1 -0
  102. package/dist/operations/url/url-encode.d.ts +8 -0
  103. package/dist/operations/url/url-encode.js +37 -0
  104. package/dist/operations/url/url-encode.js.map +1 -0
  105. package/dist/registry.d.ts +395 -0
  106. package/dist/registry.js +131 -0
  107. package/dist/registry.js.map +1 -0
  108. package/dist/template.d.ts +23 -0
  109. package/dist/template.js +198 -0
  110. package/dist/template.js.map +1 -0
  111. package/package.json +67 -4
  112. package/src/args.ts +47 -0
  113. package/src/cli-flags.ts +29 -0
  114. package/src/cli.ts +189 -0
  115. package/src/completions.ts +129 -0
  116. package/src/display.ts +29 -0
  117. package/src/errors.ts +6 -0
  118. package/src/index.ts +4 -0
  119. package/src/operation.ts +62 -0
  120. package/src/operations/base/alphabets.ts +33 -0
  121. package/src/operations/base/base32.ts +84 -0
  122. package/src/operations/base/base58.ts +45 -0
  123. package/src/operations/base/base62.ts +44 -0
  124. package/src/operations/base/base64.ts +76 -0
  125. package/src/operations/base/base64url.ts +45 -0
  126. package/src/operations/base/big-number.ts +54 -0
  127. package/src/operations/color/hex-to-rgb.ts +42 -0
  128. package/src/operations/data/json-format.ts +33 -0
  129. package/src/operations/data/json.ts +53 -0
  130. package/src/operations/data/yaml.ts +56 -0
  131. package/src/operations/extract/extract.ts +135 -0
  132. package/src/operations/hash/bcrypt.ts +44 -0
  133. package/src/operations/hash/hashes.ts +64 -0
  134. package/src/operations/hex/from-hex.ts +31 -0
  135. package/src/operations/hex/to-hex.ts +46 -0
  136. package/src/operations/html/entities.ts +71 -0
  137. package/src/operations/html/html-decode.ts +36 -0
  138. package/src/operations/html/html-encode.ts +31 -0
  139. package/src/operations/text/case.ts +140 -0
  140. package/src/operations/text/count.ts +61 -0
  141. package/src/operations/text/line-operations.ts +144 -0
  142. package/src/operations/text/lines.ts +22 -0
  143. package/src/operations/text/text-operations.ts +85 -0
  144. package/src/operations/url/url-decode.ts +55 -0
  145. package/src/operations/url/url-encode.ts +38 -0
  146. package/src/registry.ts +152 -0
  147. package/src/template.ts +214 -0
@@ -0,0 +1,152 @@
1
+ import { checkArg } from "./args.ts";
2
+ import { OperationError } from "./errors.ts";
3
+ import type { ArgDefs, ArgValues, Operation } from "./operation.ts";
4
+ import { fromBase32, toBase32 } from "./operations/base/base32.ts";
5
+ import { fromBase58, toBase58 } from "./operations/base/base58.ts";
6
+ import { fromBase62, toBase62 } from "./operations/base/base62.ts";
7
+ import { fromBase64, toBase64 } from "./operations/base/base64.ts";
8
+ import { fromBase64url, toBase64url } from "./operations/base/base64url.ts";
9
+ import { hexToRgb } from "./operations/color/hex-to-rgb.ts";
10
+ import { jsonFormat } from "./operations/data/json-format.ts";
11
+ import { jsonToYaml, yamlToJson } from "./operations/data/yaml.ts";
12
+ import { extractEmails, extractIps, extractUrls } from "./operations/extract/extract.ts";
13
+ import { bcrypt } from "./operations/hash/bcrypt.ts";
14
+ import { md5, sha1, sha224, sha256, sha384, sha3_224, sha3_256, sha3_384, sha3_512, sha512 } from "./operations/hash/hashes.ts";
15
+ import { fromHex } from "./operations/hex/from-hex.ts";
16
+ import { toHex } from "./operations/hex/to-hex.ts";
17
+ import { htmlDecode } from "./operations/html/html-decode.ts";
18
+ import { htmlEncode } from "./operations/html/html-encode.ts";
19
+ import {
20
+ toCamelCase,
21
+ toKebabCase,
22
+ toLowerCase,
23
+ toPascalCase,
24
+ toSlug,
25
+ toSnakeCase,
26
+ toTitleCase,
27
+ toUpperCase,
28
+ } from "./operations/text/case.ts";
29
+ import { countChars, countLines, countWords } from "./operations/text/count.ts";
30
+ import { addLineNumbers, reverseLines, shuffleLines, sortLines, uniqueLines } from "./operations/text/line-operations.ts";
31
+ import { escapeQuotes, removeNewlines, removeWhitespace, reverseText } from "./operations/text/text-operations.ts";
32
+ import { urlDecode } from "./operations/url/url-decode.ts";
33
+ import { urlEncode } from "./operations/url/url-encode.ts";
34
+
35
+ /** Every new operation must be registered here. */
36
+ export const operations = [
37
+ // Encoding
38
+ toBase64,
39
+ fromBase64,
40
+ toBase64url,
41
+ fromBase64url,
42
+ toBase32,
43
+ fromBase32,
44
+ toBase58,
45
+ fromBase58,
46
+ toBase62,
47
+ fromBase62,
48
+ toHex,
49
+ fromHex,
50
+ urlEncode,
51
+ urlDecode,
52
+ htmlEncode,
53
+ htmlDecode,
54
+ // Hashing
55
+ md5,
56
+ sha1,
57
+ sha224,
58
+ sha256,
59
+ sha384,
60
+ sha512,
61
+ sha3_224,
62
+ sha3_256,
63
+ sha3_384,
64
+ sha3_512,
65
+ bcrypt,
66
+ // Data format
67
+ jsonFormat,
68
+ jsonToYaml,
69
+ yamlToJson,
70
+ hexToRgb,
71
+ // Text
72
+ toLowerCase,
73
+ toUpperCase,
74
+ toTitleCase,
75
+ toCamelCase,
76
+ toPascalCase,
77
+ toSnakeCase,
78
+ toKebabCase,
79
+ toSlug,
80
+ sortLines,
81
+ shuffleLines,
82
+ uniqueLines,
83
+ reverseLines,
84
+ addLineNumbers,
85
+ reverseText,
86
+ removeNewlines,
87
+ removeWhitespace,
88
+ escapeQuotes,
89
+ countChars,
90
+ countLines,
91
+ countWords,
92
+ // Extractors
93
+ extractIps,
94
+ extractEmails,
95
+ extractUrls,
96
+ ] as const;
97
+
98
+ type AnyOperation = (typeof operations)[number];
99
+ export type OperationId = AnyOperation["id"];
100
+ export type ArgsOf<Id extends OperationId> = ArgValues<Extract<AnyOperation, { id: Id }>["args"]>;
101
+
102
+ // Compile-time check: a typo in an `inverse` shows up here with the unknown id.
103
+ type UnknownInverse = Exclude<NonNullable<AnyOperation["inverse"]>, OperationId>;
104
+ const inversesExist: [UnknownInverse] extends [never] ? true : UnknownInverse = true;
105
+ void inversesExist;
106
+
107
+ const byId = new Map<string, Operation>(operations.map((op) => [op.id, op as unknown as Operation]));
108
+
109
+ export function isOperationId(id: string): id is OperationId {
110
+ return byId.has(id);
111
+ }
112
+
113
+ export function getOperation(id: OperationId): Operation {
114
+ return byId.get(id)!;
115
+ }
116
+
117
+ function resolveArgs(op: Operation, args: Record<string, unknown>): ArgValues<ArgDefs> {
118
+ for (const key of Object.keys(args)) {
119
+ if (!Object.hasOwn(op.args, key)) {
120
+ const expected = Object.keys(op.args).join(", ") || "none";
121
+ throw new OperationError(`Unknown argument "${key}" (expected: ${expected})`);
122
+ }
123
+ }
124
+ const resolved: Record<string, unknown> = {};
125
+ for (const [key, def] of Object.entries(op.args)) {
126
+ const value = args[key] ?? def.default;
127
+ try {
128
+ checkArg(def, key, value);
129
+ } catch (error) {
130
+ throw new OperationError((error as Error).message);
131
+ }
132
+ resolved[key] = value;
133
+ }
134
+ return resolved as ArgValues<ArgDefs>;
135
+ }
136
+
137
+ /** Throws an `OperationError` tagged with the operation id when the input or arguments are invalid. */
138
+ export function run<Id extends OperationId>(id: Id, input: Uint8Array | string, args: Partial<ArgsOf<Id>> = {}): Uint8Array {
139
+ const op = getOperation(id);
140
+ const bytes = typeof input === "string" ? new TextEncoder().encode(input) : input;
141
+ try {
142
+ return op.run(bytes, resolveArgs(op, args));
143
+ } catch (error) {
144
+ const wrapped = error instanceof OperationError ? error : new OperationError((error as Error).message, { cause: error });
145
+ wrapped.operation = id;
146
+ throw wrapped;
147
+ }
148
+ }
149
+
150
+ export function runText<Id extends OperationId>(id: Id, input: Uint8Array | string, args: Partial<ArgsOf<Id>> = {}): string {
151
+ return new TextDecoder().decode(run(id, input, args));
152
+ }
@@ -0,0 +1,214 @@
1
+ import { coerceArg, findArg, kebab } from "./args.ts";
2
+ import { getOperation, isOperationId, run, type OperationId } from "./registry.ts";
3
+
4
+ // Syntax: `op{content}` or `op(name=value, flag){content}`, nestable.
5
+ // Only known operation ids are interpreted: `{x}`, `foo{x}` or `op(text)` stay plain text.
6
+ // In content, `\{`, `\}` and `\\` produce the literal character.
7
+ // Argument values are bare (`[A-Za-z0-9-_.+/=]`) or quoted with " or ', where
8
+ // `\"`, `\'` and `\\` escape; anything else inside quotes is literal.
9
+
10
+ export type TemplateNode =
11
+ | { type: "text"; value: string }
12
+ | { type: "op"; id: OperationId; args: Record<string, unknown>; children: TemplateNode[]; start: number };
13
+
14
+ export class TemplateError extends Error {
15
+ override name = "TemplateError";
16
+ /** 1-based line and column of `position`. */
17
+ readonly line: number;
18
+ readonly column: number;
19
+ /** Offset in the source. */
20
+ readonly position: number;
21
+
22
+ constructor(message: string, source: string, position: number) {
23
+ const before = source.slice(0, position);
24
+ const line = before.split("\n").length;
25
+ const column = position - before.lastIndexOf("\n");
26
+ super(`${message} (line ${line}, column ${column})`);
27
+ this.position = position;
28
+ this.line = line;
29
+ this.column = column;
30
+ }
31
+ }
32
+
33
+ const OP_START = /([a-z0-9]+(?:-[a-z0-9]+)*)([{(])/y;
34
+ const IDENT_CHAR = /[a-z0-9-]/;
35
+ const ARG_NAME = /[a-z0-9]+(?:-[a-z0-9]+)*/y;
36
+ const BARE_VALUE = /[A-Za-z0-9\-_.+/=]+/y;
37
+ const SPACE = /\s/;
38
+
39
+ /** Index of the `)` closing an argument list opened before `from`, ignoring quoted parts; -1 if none. */
40
+ function findArgsEnd(source: string, from: number): number {
41
+ let quote: string | null = null;
42
+ for (let j = from; j < source.length; j++) {
43
+ const c = source[j]!;
44
+ if (quote) {
45
+ if (c === "\\") j++;
46
+ else if (c === quote) quote = null;
47
+ } else if (c === '"' || c === "'") {
48
+ quote = c;
49
+ } else if (c === ")") {
50
+ return j;
51
+ }
52
+ }
53
+ return -1;
54
+ }
55
+
56
+ function parseArgList(id: OperationId, source: string, from: number, end: number): Record<string, unknown> {
57
+ const op = getOperation(id);
58
+ const args: Record<string, unknown> = {};
59
+ let j = from;
60
+
61
+ const skipSpaces = () => {
62
+ while (j < end && SPACE.test(source[j]!)) j++;
63
+ };
64
+ const fail = (message: string, at = j): never => {
65
+ throw new TemplateError(message, source, at);
66
+ };
67
+
68
+ const parseValue = (): string => {
69
+ const quote = source[j];
70
+ if (quote === '"' || quote === "'") {
71
+ let value = "";
72
+ for (j++; source[j] !== quote; j++) {
73
+ if (j >= end) fail("Unterminated quote");
74
+ if (source[j] === "\\" && (source[j + 1] === quote || source[j + 1] === "\\")) j++;
75
+ value += source[j];
76
+ }
77
+ j++;
78
+ return value;
79
+ }
80
+ BARE_VALUE.lastIndex = j;
81
+ const match = BARE_VALUE.exec(source);
82
+ if (!match || BARE_VALUE.lastIndex > end) return fail("Expected a value (use quotes for special characters)");
83
+ j = BARE_VALUE.lastIndex;
84
+ return match[0];
85
+ };
86
+
87
+ skipSpaces();
88
+ while (j < end) {
89
+ const nameStart = j;
90
+ ARG_NAME.lastIndex = j;
91
+ const name = ARG_NAME.exec(source)?.[0];
92
+ if (!name || ARG_NAME.lastIndex > end) fail("Expected an argument name");
93
+ j = ARG_NAME.lastIndex;
94
+
95
+ const arg = findArg(op, name!);
96
+ if (!arg) {
97
+ const expected = Object.keys(op.args).map(kebab).join(", ") || "none";
98
+ fail(`Unknown argument "${name}" for "${id}" (expected: ${expected})`, nameStart);
99
+ }
100
+ const [key, def] = arg!;
101
+ if (Object.hasOwn(args, key)) fail(`Duplicate argument "${name}"`, nameStart);
102
+
103
+ skipSpaces();
104
+ let raw: string | boolean = true;
105
+ if (source[j] === "=") {
106
+ j++;
107
+ skipSpaces();
108
+ raw = parseValue();
109
+ }
110
+ skipSpaces();
111
+ if (j < end && source[j] !== ",") fail(`Unexpected "${source[j]}" (use quotes for special characters)`);
112
+
113
+ try {
114
+ args[key] = coerceArg(def, name!, raw);
115
+ } catch (error) {
116
+ fail((error as Error).message, nameStart);
117
+ }
118
+
119
+ if (j < end) {
120
+ j++;
121
+ skipSpaces();
122
+ if (j >= end) fail("Expected an argument name");
123
+ }
124
+ }
125
+ return args;
126
+ }
127
+
128
+ export function parse(source: string): TemplateNode[] {
129
+ let i = 0;
130
+
131
+ function parseNodes(open: { start: number } | null): TemplateNode[] {
132
+ const nodes: TemplateNode[] = [];
133
+ let text = "";
134
+ const flush = () => {
135
+ if (text) nodes.push({ type: "text", value: text });
136
+ text = "";
137
+ };
138
+
139
+ while (i < source.length) {
140
+ const ch = source[i]!;
141
+
142
+ if (ch === "\\" && i + 1 < source.length && "{}\\".includes(source[i + 1]!)) {
143
+ text += source[i + 1];
144
+ i += 2;
145
+ continue;
146
+ }
147
+
148
+ if (ch === "}" && open) {
149
+ i++;
150
+ flush();
151
+ return nodes;
152
+ }
153
+
154
+ if (i === 0 || !IDENT_CHAR.test(source[i - 1]!)) {
155
+ OP_START.lastIndex = i;
156
+ const match = OP_START.exec(source);
157
+ const id = match?.[1];
158
+ if (id && isOperationId(id)) {
159
+ let args: Record<string, unknown> = {};
160
+ let contentStart = OP_START.lastIndex;
161
+ const argsEnd = match![2] === "(" ? findArgsEnd(source, contentStart) : -1;
162
+ const isBlock = match![2] === "{" || (argsEnd >= 0 && source[argsEnd + 1] === "{");
163
+ if (isBlock) {
164
+ if (argsEnd >= 0) {
165
+ args = parseArgList(id, source, contentStart, argsEnd);
166
+ contentStart = argsEnd + 2;
167
+ }
168
+ flush();
169
+ const start = i;
170
+ i = contentStart;
171
+ nodes.push({ type: "op", id, args, children: parseNodes({ start }), start });
172
+ continue;
173
+ }
174
+ }
175
+ }
176
+
177
+ text += ch;
178
+ i++;
179
+ }
180
+
181
+ if (open) throw new TemplateError("Missing closing brace", source, open.start);
182
+ flush();
183
+ return nodes;
184
+ }
185
+
186
+ return parseNodes(null);
187
+ }
188
+
189
+ function evaluate(nodes: TemplateNode[], source: string): Uint8Array {
190
+ const parts = nodes.map((node) => {
191
+ if (node.type === "text") return new TextEncoder().encode(node.value);
192
+ const input = evaluate(node.children, source);
193
+ try {
194
+ return run(node.id, input, node.args as never);
195
+ } catch (error) {
196
+ throw new TemplateError(`${node.id}: ${(error as Error).message}`, source, node.start);
197
+ }
198
+ });
199
+ const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
200
+ let offset = 0;
201
+ for (const part of parts) {
202
+ out.set(part, offset);
203
+ offset += part.length;
204
+ }
205
+ return out;
206
+ }
207
+
208
+ export function renderBytes(source: string): Uint8Array {
209
+ return evaluate(parse(source), source);
210
+ }
211
+
212
+ export function render(source: string): string {
213
+ return new TextDecoder().decode(renderBytes(source));
214
+ }