@jacobknightley/fabric-format 0.0.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.
- package/README.md +196 -0
- package/dist/cell-formatter.d.ts +75 -0
- package/dist/cell-formatter.js +144 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +435 -0
- package/dist/formatters/index.d.ts +19 -0
- package/dist/formatters/index.js +76 -0
- package/dist/formatters/python/config.d.ts +33 -0
- package/dist/formatters/python/config.js +29 -0
- package/dist/formatters/python/index.d.ts +7 -0
- package/dist/formatters/python/index.js +13 -0
- package/dist/formatters/python/python-formatter.d.ts +51 -0
- package/dist/formatters/python/python-formatter.js +180 -0
- package/dist/formatters/sparksql/constants.d.ts +16 -0
- package/dist/formatters/sparksql/constants.js +16 -0
- package/dist/formatters/sparksql/fmt-detector.d.ts +65 -0
- package/dist/formatters/sparksql/fmt-detector.js +84 -0
- package/dist/formatters/sparksql/formatter.d.ts +24 -0
- package/dist/formatters/sparksql/formatter.js +1276 -0
- package/dist/formatters/sparksql/formatting-context.d.ts +154 -0
- package/dist/formatters/sparksql/formatting-context.js +363 -0
- package/dist/formatters/sparksql/generated/SqlBaseLexer.d.ts +529 -0
- package/dist/formatters/sparksql/generated/SqlBaseLexer.js +2609 -0
- package/dist/formatters/sparksql/generated/SqlBaseParser.d.ts +8195 -0
- package/dist/formatters/sparksql/generated/SqlBaseParser.js +48793 -0
- package/dist/formatters/sparksql/generated/SqlBaseParserListener.d.ts +910 -0
- package/dist/formatters/sparksql/generated/SqlBaseParserListener.js +2730 -0
- package/dist/formatters/sparksql/generated/SqlBaseParserVisitor.d.ts +456 -0
- package/dist/formatters/sparksql/generated/SqlBaseParserVisitor.js +1822 -0
- package/dist/formatters/sparksql/generated/builtinFunctions.d.ts +8 -0
- package/dist/formatters/sparksql/generated/builtinFunctions.js +510 -0
- package/dist/formatters/sparksql/index.d.ts +11 -0
- package/dist/formatters/sparksql/index.js +22 -0
- package/dist/formatters/sparksql/output-builder.d.ts +89 -0
- package/dist/formatters/sparksql/output-builder.js +191 -0
- package/dist/formatters/sparksql/parse-tree-analyzer.d.ts +264 -0
- package/dist/formatters/sparksql/parse-tree-analyzer.js +1956 -0
- package/dist/formatters/sparksql/sql-formatter.d.ts +25 -0
- package/dist/formatters/sparksql/sql-formatter.js +56 -0
- package/dist/formatters/sparksql/token-utils.d.ts +68 -0
- package/dist/formatters/sparksql/token-utils.js +155 -0
- package/dist/formatters/sparksql/types.d.ts +264 -0
- package/dist/formatters/sparksql/types.js +7 -0
- package/dist/formatters/types.d.ts +57 -0
- package/dist/formatters/types.js +7 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +41 -0
- package/dist/notebook-formatter.d.ts +107 -0
- package/dist/notebook-formatter.js +424 -0
- package/package.json +63 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI for Fabric Notebook Formatter (Spark SQL & Python)
|
|
4
|
+
*
|
|
5
|
+
* Commands:
|
|
6
|
+
* fabfmt format <files...> - Format files in-place
|
|
7
|
+
* fabfmt format --type sparksql - Format stdin (pipe mode)
|
|
8
|
+
* fabfmt check <files...> - Check if files need formatting
|
|
9
|
+
*/
|
|
10
|
+
import { formatNotebook } from './notebook-formatter.js';
|
|
11
|
+
import { formatCell, initializePythonFormatter } from './cell-formatter.js';
|
|
12
|
+
import * as fs from 'fs';
|
|
13
|
+
import * as path from 'path';
|
|
14
|
+
const args = process.argv.slice(2);
|
|
15
|
+
/** Supported file extensions for formatting */
|
|
16
|
+
const SUPPORTED_EXTENSIONS = ['.sql', '.py', '.scala', '.r'];
|
|
17
|
+
/**
|
|
18
|
+
* Read all content from stdin.
|
|
19
|
+
*/
|
|
20
|
+
async function readStdin() {
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
let data = '';
|
|
23
|
+
process.stdin.setEncoding('utf-8');
|
|
24
|
+
process.stdin.on('data', chunk => data += chunk);
|
|
25
|
+
process.stdin.on('end', () => resolve(data));
|
|
26
|
+
process.stdin.on('error', reject);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Recursively find all files with supported extensions in a directory.
|
|
31
|
+
*/
|
|
32
|
+
function findSupportedFiles(dir) {
|
|
33
|
+
const files = [];
|
|
34
|
+
function walk(currentDir) {
|
|
35
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
36
|
+
for (const entry of entries) {
|
|
37
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
38
|
+
if (entry.isDirectory()) {
|
|
39
|
+
// Skip common non-source directories
|
|
40
|
+
if (!['node_modules', '.git', '__pycache__', '.venv', 'venv', 'dist', 'build'].includes(entry.name)) {
|
|
41
|
+
walk(fullPath);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
else if (entry.isFile()) {
|
|
45
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
46
|
+
if (SUPPORTED_EXTENSIONS.includes(ext)) {
|
|
47
|
+
files.push(fullPath);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
walk(dir);
|
|
53
|
+
return files;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Expand a list of paths to include files from directories.
|
|
57
|
+
*/
|
|
58
|
+
function expandPaths(paths) {
|
|
59
|
+
const files = [];
|
|
60
|
+
for (const p of paths) {
|
|
61
|
+
if (fs.existsSync(p)) {
|
|
62
|
+
const stat = fs.statSync(p);
|
|
63
|
+
if (stat.isDirectory()) {
|
|
64
|
+
files.push(...findSupportedFiles(p));
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
files.push(p);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
console.error(`Error: File or directory not found: ${p}`);
|
|
72
|
+
process.exit(2);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return files;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Format a Fabric notebook file.
|
|
79
|
+
*/
|
|
80
|
+
async function formatFile(content, filePath) {
|
|
81
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
82
|
+
const { content: formatted } = await formatNotebook(content, ext, {
|
|
83
|
+
formatPython: true,
|
|
84
|
+
formatSql: true,
|
|
85
|
+
});
|
|
86
|
+
return formatted;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Detect the line ending style used in content.
|
|
90
|
+
*/
|
|
91
|
+
function detectLineEnding(content) {
|
|
92
|
+
if (content.includes('\r\n')) {
|
|
93
|
+
return '\r\n';
|
|
94
|
+
}
|
|
95
|
+
return '\n';
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Normalize line endings to Unix style.
|
|
99
|
+
*/
|
|
100
|
+
function normalizeLineEndings(content) {
|
|
101
|
+
return content.replace(/\r\n/g, '\n');
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Convert line endings to the specified style.
|
|
105
|
+
*/
|
|
106
|
+
function convertLineEndings(content, lineEnding) {
|
|
107
|
+
const normalized = normalizeLineEndings(content);
|
|
108
|
+
if (lineEnding === '\r\n') {
|
|
109
|
+
return normalized.replace(/\n/g, '\r\n');
|
|
110
|
+
}
|
|
111
|
+
return normalized;
|
|
112
|
+
}
|
|
113
|
+
/** Print main help */
|
|
114
|
+
function printHelp() {
|
|
115
|
+
console.log(`fabfmt - Fabric Notebook Formatter (Spark SQL & Python)
|
|
116
|
+
|
|
117
|
+
Usage:
|
|
118
|
+
fabfmt <command> [options] [arguments]
|
|
119
|
+
|
|
120
|
+
Commands:
|
|
121
|
+
format <files...> Format files or directories in-place
|
|
122
|
+
check <files...> Check if files need formatting (exit 1 if so)
|
|
123
|
+
|
|
124
|
+
Run 'fabfmt <command> --help' for command-specific help.
|
|
125
|
+
`);
|
|
126
|
+
}
|
|
127
|
+
/** Print format command help */
|
|
128
|
+
function printFormatHelp() {
|
|
129
|
+
console.log(`fabfmt format - Format files in-place
|
|
130
|
+
|
|
131
|
+
Usage:
|
|
132
|
+
fabfmt format [options] <file|directory...>
|
|
133
|
+
fabfmt format --type <type> [-i <string>]
|
|
134
|
+
|
|
135
|
+
Arguments:
|
|
136
|
+
file A .sql, .py, .scala, or .r Fabric notebook file
|
|
137
|
+
directory Recursively format all supported files
|
|
138
|
+
|
|
139
|
+
Options:
|
|
140
|
+
--type <type> Cell type: sparksql, python, or pyspark
|
|
141
|
+
Required for stdin or inline input
|
|
142
|
+
-i <string> Inline string to format (requires --type)
|
|
143
|
+
--print Print formatted output instead of modifying file
|
|
144
|
+
-h, --help Show this help message
|
|
145
|
+
|
|
146
|
+
Examples:
|
|
147
|
+
fabfmt format notebook.py Format a single file
|
|
148
|
+
fabfmt format ./src Format all files in directory
|
|
149
|
+
fabfmt format query.sql --print Print formatted output
|
|
150
|
+
fabfmt format --type sparksql -i "select * from t" Format inline string
|
|
151
|
+
echo "select * from t" | fabfmt format --type sparksql Format from stdin
|
|
152
|
+
`);
|
|
153
|
+
}
|
|
154
|
+
/** Print check command help */
|
|
155
|
+
function printCheckHelp() {
|
|
156
|
+
console.log(`fabfmt check - Check if files need formatting
|
|
157
|
+
|
|
158
|
+
Usage:
|
|
159
|
+
fabfmt check [options] <file|directory...>
|
|
160
|
+
fabfmt check --type <type> [-i <string>]
|
|
161
|
+
|
|
162
|
+
Arguments:
|
|
163
|
+
file A .sql, .py, .scala, or .r Fabric notebook file
|
|
164
|
+
directory Recursively check all supported files
|
|
165
|
+
|
|
166
|
+
Options:
|
|
167
|
+
--type <type> Cell type: sparksql, python, or pyspark
|
|
168
|
+
Required for stdin or inline input
|
|
169
|
+
-i <string> Inline string to check (requires --type)
|
|
170
|
+
-h, --help Show this help message
|
|
171
|
+
|
|
172
|
+
Exit codes:
|
|
173
|
+
0 All files are properly formatted / input needs no changes
|
|
174
|
+
1 One or more files need formatting / input needs formatting
|
|
175
|
+
|
|
176
|
+
Examples:
|
|
177
|
+
fabfmt check notebook.py Check a single file
|
|
178
|
+
fabfmt check ./src Check all files in directory
|
|
179
|
+
fabfmt check --type sparksql -i "select * from t" Check inline string
|
|
180
|
+
echo "select * from t" | fabfmt check --type sparksql Check from stdin
|
|
181
|
+
`);
|
|
182
|
+
}
|
|
183
|
+
/** Parse --type argument */
|
|
184
|
+
function parseType(args) {
|
|
185
|
+
const remaining = [];
|
|
186
|
+
let type = null;
|
|
187
|
+
for (let i = 0; i < args.length; i++) {
|
|
188
|
+
if (args[i] === '--type') {
|
|
189
|
+
const nextArg = args[i + 1];
|
|
190
|
+
if (nextArg && !nextArg.startsWith('-')) {
|
|
191
|
+
const cellType = nextArg.toLowerCase();
|
|
192
|
+
if (cellType === 'sparksql' || cellType === 'python' || cellType === 'pyspark') {
|
|
193
|
+
type = cellType;
|
|
194
|
+
i++; // Skip next arg
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
console.error(`Error: Invalid type '${cellType}'. Use sparksql, python, or pyspark.`);
|
|
198
|
+
process.exit(2);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
console.error('Error: --type requires a value (sparksql, python, or pyspark)');
|
|
203
|
+
process.exit(2);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
remaining.push(args[i]);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return { type, remaining };
|
|
211
|
+
}
|
|
212
|
+
/** Parse -i argument */
|
|
213
|
+
function parseInline(args) {
|
|
214
|
+
const remaining = [];
|
|
215
|
+
let inline = null;
|
|
216
|
+
for (let i = 0; i < args.length; i++) {
|
|
217
|
+
if (args[i] === '-i') {
|
|
218
|
+
const nextArg = args[i + 1];
|
|
219
|
+
if (nextArg !== undefined) {
|
|
220
|
+
inline = nextArg;
|
|
221
|
+
i++; // Skip next arg
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
console.error('Error: -i requires a string value');
|
|
225
|
+
process.exit(2);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
remaining.push(args[i]);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return { inline, remaining };
|
|
233
|
+
}
|
|
234
|
+
/** Format command: format files in-place or stdin */
|
|
235
|
+
async function cmdFormat(args) {
|
|
236
|
+
if (args.includes('-h') || args.includes('--help')) {
|
|
237
|
+
printFormatHelp();
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const toPrint = args.includes('--print');
|
|
241
|
+
const argsWithoutPrint = args.filter(a => a !== '--print');
|
|
242
|
+
const { type, remaining: argsAfterType } = parseType(argsWithoutPrint);
|
|
243
|
+
const { inline, remaining: paths } = parseInline(argsAfterType);
|
|
244
|
+
// Inline mode: -i with --type
|
|
245
|
+
if (inline !== null) {
|
|
246
|
+
if (!type) {
|
|
247
|
+
console.error('Error: -i requires --type (sparksql, python, or pyspark)');
|
|
248
|
+
process.exit(2);
|
|
249
|
+
}
|
|
250
|
+
// Initialize Python formatter if needed
|
|
251
|
+
if (type === 'python' || type === 'pyspark') {
|
|
252
|
+
await initializePythonFormatter();
|
|
253
|
+
}
|
|
254
|
+
const result = formatCell(inline, type);
|
|
255
|
+
if (result.error) {
|
|
256
|
+
console.error(`Error: ${result.error}`);
|
|
257
|
+
process.exit(1);
|
|
258
|
+
}
|
|
259
|
+
process.stdout.write(result.formatted);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
// Stdin mode: --type specified with no files
|
|
263
|
+
if (type && paths.length === 0) {
|
|
264
|
+
// Initialize Python formatter if needed
|
|
265
|
+
if (type === 'python' || type === 'pyspark') {
|
|
266
|
+
await initializePythonFormatter();
|
|
267
|
+
}
|
|
268
|
+
const content = await readStdin();
|
|
269
|
+
const result = formatCell(content, type);
|
|
270
|
+
if (result.error) {
|
|
271
|
+
console.error(`Error: ${result.error}`);
|
|
272
|
+
process.exit(1);
|
|
273
|
+
}
|
|
274
|
+
process.stdout.write(result.formatted);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (paths.length === 0) {
|
|
278
|
+
console.error('Error: No files or directories specified');
|
|
279
|
+
console.error('Run "fabfmt format --help" for usage');
|
|
280
|
+
process.exit(2);
|
|
281
|
+
}
|
|
282
|
+
// --print mode: single file to stdout
|
|
283
|
+
if (toPrint) {
|
|
284
|
+
if (paths.length !== 1) {
|
|
285
|
+
console.error('Error: --print requires exactly one file');
|
|
286
|
+
process.exit(2);
|
|
287
|
+
}
|
|
288
|
+
const file = paths[0];
|
|
289
|
+
const stat = fs.statSync(file);
|
|
290
|
+
if (stat.isDirectory()) {
|
|
291
|
+
console.error('Error: --print cannot be used with directories');
|
|
292
|
+
process.exit(2);
|
|
293
|
+
}
|
|
294
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
295
|
+
const formatted = await formatFile(content, file);
|
|
296
|
+
process.stdout.write(formatted);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
// Expand directories to files
|
|
300
|
+
const files = expandPaths(paths);
|
|
301
|
+
if (files.length === 0) {
|
|
302
|
+
console.log('No supported files found');
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
// Format files in-place
|
|
306
|
+
let formattedCount = 0;
|
|
307
|
+
for (const file of files) {
|
|
308
|
+
try {
|
|
309
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
310
|
+
const originalLineEnding = detectLineEnding(content);
|
|
311
|
+
const normalizedContent = normalizeLineEndings(content);
|
|
312
|
+
const formatted = await formatFile(normalizedContent, file);
|
|
313
|
+
const formattedWithOriginalEndings = convertLineEndings(formatted, originalLineEnding);
|
|
314
|
+
if (formattedWithOriginalEndings !== content) {
|
|
315
|
+
fs.writeFileSync(file, formattedWithOriginalEndings, 'utf-8');
|
|
316
|
+
console.log(`Formatted ${file}`);
|
|
317
|
+
formattedCount++;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
catch (e) {
|
|
321
|
+
console.error(`Error formatting ${file}: ${e.message}`);
|
|
322
|
+
process.exit(1);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (formattedCount === 0) {
|
|
326
|
+
console.log(`All ${files.length} file(s) already formatted`);
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
console.log(`Formatted ${formattedCount} of ${files.length} file(s)`);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
/** Check command: check if files need formatting */
|
|
333
|
+
async function cmdCheck(args) {
|
|
334
|
+
if (args.includes('-h') || args.includes('--help')) {
|
|
335
|
+
printCheckHelp();
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const { type, remaining: argsAfterType } = parseType(args);
|
|
339
|
+
const { inline, remaining: paths } = parseInline(argsAfterType);
|
|
340
|
+
// Inline mode: -i with --type
|
|
341
|
+
if (inline !== null) {
|
|
342
|
+
if (!type) {
|
|
343
|
+
console.error('Error: -i requires --type (sparksql, python, or pyspark)');
|
|
344
|
+
process.exit(2);
|
|
345
|
+
}
|
|
346
|
+
// Initialize Python formatter if needed
|
|
347
|
+
if (type === 'python' || type === 'pyspark') {
|
|
348
|
+
await initializePythonFormatter();
|
|
349
|
+
}
|
|
350
|
+
const result = formatCell(inline, type);
|
|
351
|
+
if (result.error) {
|
|
352
|
+
console.error(`Error: ${result.error}`);
|
|
353
|
+
process.exit(1);
|
|
354
|
+
}
|
|
355
|
+
// Exit 1 if formatting would change the input
|
|
356
|
+
if (result.formatted !== inline) {
|
|
357
|
+
process.exit(1);
|
|
358
|
+
}
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
// Stdin mode: --type specified with no files
|
|
362
|
+
if (type && paths.length === 0) {
|
|
363
|
+
// Initialize Python formatter if needed
|
|
364
|
+
if (type === 'python' || type === 'pyspark') {
|
|
365
|
+
await initializePythonFormatter();
|
|
366
|
+
}
|
|
367
|
+
const content = await readStdin();
|
|
368
|
+
const result = formatCell(content, type);
|
|
369
|
+
if (result.error) {
|
|
370
|
+
console.error(`Error: ${result.error}`);
|
|
371
|
+
process.exit(1);
|
|
372
|
+
}
|
|
373
|
+
// Exit 1 if formatting would change the input
|
|
374
|
+
if (result.formatted !== content) {
|
|
375
|
+
process.exit(1);
|
|
376
|
+
}
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
if (paths.length === 0) {
|
|
380
|
+
console.error('Error: No files or directories specified');
|
|
381
|
+
console.error('Run "fabfmt check --help" for usage');
|
|
382
|
+
process.exit(2);
|
|
383
|
+
}
|
|
384
|
+
// Expand directories to files
|
|
385
|
+
const files = expandPaths(paths);
|
|
386
|
+
if (files.length === 0) {
|
|
387
|
+
console.log('No supported files found');
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
// Check mode: check without modifying
|
|
391
|
+
let needsFormatting = false;
|
|
392
|
+
for (const file of files) {
|
|
393
|
+
try {
|
|
394
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
395
|
+
const formatted = await formatFile(content, file);
|
|
396
|
+
if (formatted !== content) {
|
|
397
|
+
console.log(file);
|
|
398
|
+
needsFormatting = true;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
catch (e) {
|
|
402
|
+
console.error(`Error checking ${file}: ${e.message}`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
if (needsFormatting) {
|
|
406
|
+
process.exit(1);
|
|
407
|
+
}
|
|
408
|
+
console.log(`All ${files.length} file(s) are properly formatted`);
|
|
409
|
+
}
|
|
410
|
+
/** Main entry point */
|
|
411
|
+
async function main() {
|
|
412
|
+
const command = args[0];
|
|
413
|
+
const commandArgs = args.slice(1);
|
|
414
|
+
if (!command || command === '-h' || command === '--help') {
|
|
415
|
+
printHelp();
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
switch (command) {
|
|
419
|
+
case 'format':
|
|
420
|
+
await cmdFormat(commandArgs);
|
|
421
|
+
break;
|
|
422
|
+
case 'check':
|
|
423
|
+
await cmdCheck(commandArgs);
|
|
424
|
+
break;
|
|
425
|
+
default:
|
|
426
|
+
console.error(`Error: Unknown command '${command}'`);
|
|
427
|
+
console.error('Run "fabfmt --help" for usage');
|
|
428
|
+
process.exit(2);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
// Run main
|
|
432
|
+
main().catch((e) => {
|
|
433
|
+
console.error(`Error: ${e.message}`);
|
|
434
|
+
process.exit(1);
|
|
435
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formatter Registry
|
|
3
|
+
*
|
|
4
|
+
* Central registry for all language formatters.
|
|
5
|
+
* Provides a unified interface for formatting any supported language.
|
|
6
|
+
*/
|
|
7
|
+
import type { FormatterRegistry } from './types.js';
|
|
8
|
+
export type { LanguageFormatter, FormatterOptions, FormatResult, FormatterConfig, FormatterRegistry } from './types.js';
|
|
9
|
+
export { SqlFormatter, getSqlFormatter, isSqlCode } from './sparksql/index.js';
|
|
10
|
+
export { PythonFormatter, getPythonFormatter, isPythonCode } from './python/index.js';
|
|
11
|
+
/**
|
|
12
|
+
* Get the global formatter registry.
|
|
13
|
+
* Registers default formatters on first call.
|
|
14
|
+
*/
|
|
15
|
+
export declare function getFormatterRegistry(): FormatterRegistry;
|
|
16
|
+
/**
|
|
17
|
+
* Detect the language of code based on cell type or magic command.
|
|
18
|
+
*/
|
|
19
|
+
export declare function detectLanguage(cellType: string, code?: string): string;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formatter Registry
|
|
3
|
+
*
|
|
4
|
+
* Central registry for all language formatters.
|
|
5
|
+
* Provides a unified interface for formatting any supported language.
|
|
6
|
+
*/
|
|
7
|
+
import { getSqlFormatter, isSqlCode } from './sparksql/index.js';
|
|
8
|
+
import { getPythonFormatter, isPythonCode } from './python/index.js';
|
|
9
|
+
// Re-export formatters
|
|
10
|
+
export { SqlFormatter, getSqlFormatter, isSqlCode } from './sparksql/index.js';
|
|
11
|
+
export { PythonFormatter, getPythonFormatter, isPythonCode } from './python/index.js';
|
|
12
|
+
/**
|
|
13
|
+
* Default formatter registry implementation.
|
|
14
|
+
*/
|
|
15
|
+
class DefaultFormatterRegistry {
|
|
16
|
+
formatters = new Map();
|
|
17
|
+
get(language) {
|
|
18
|
+
return this.formatters.get(language.toLowerCase());
|
|
19
|
+
}
|
|
20
|
+
register(formatter) {
|
|
21
|
+
this.formatters.set(formatter.language.toLowerCase(), formatter);
|
|
22
|
+
}
|
|
23
|
+
languages() {
|
|
24
|
+
return Array.from(this.formatters.keys());
|
|
25
|
+
}
|
|
26
|
+
async initializeAll() {
|
|
27
|
+
const promises = Array.from(this.formatters.values()).map(f => f.initialize());
|
|
28
|
+
await Promise.all(promises);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** Global formatter registry */
|
|
32
|
+
let registryInstance = null;
|
|
33
|
+
/**
|
|
34
|
+
* Get the global formatter registry.
|
|
35
|
+
* Registers default formatters on first call.
|
|
36
|
+
*/
|
|
37
|
+
export function getFormatterRegistry() {
|
|
38
|
+
if (!registryInstance) {
|
|
39
|
+
registryInstance = new DefaultFormatterRegistry();
|
|
40
|
+
// Register built-in formatters
|
|
41
|
+
registryInstance.register(getSqlFormatter());
|
|
42
|
+
registryInstance.register(getPythonFormatter());
|
|
43
|
+
}
|
|
44
|
+
return registryInstance;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Detect the language of code based on cell type or magic command.
|
|
48
|
+
*/
|
|
49
|
+
export function detectLanguage(cellType, code) {
|
|
50
|
+
// Check for cell magic commands at the start
|
|
51
|
+
if (code) {
|
|
52
|
+
const firstLine = code.trim().split('\n')[0].trim();
|
|
53
|
+
if (firstLine === '%%sql' || firstLine.startsWith('%%sql ')) {
|
|
54
|
+
return 'sql';
|
|
55
|
+
}
|
|
56
|
+
if (firstLine === '%%python' || firstLine.startsWith('%%python ')) {
|
|
57
|
+
return 'python';
|
|
58
|
+
}
|
|
59
|
+
if (firstLine === '%%pyspark' || firstLine.startsWith('%%pyspark ')) {
|
|
60
|
+
return 'python'; // PySpark is Python
|
|
61
|
+
}
|
|
62
|
+
if (firstLine === '%%scala' || firstLine.startsWith('%%scala ')) {
|
|
63
|
+
return 'scala';
|
|
64
|
+
}
|
|
65
|
+
if (firstLine === '%%r' || firstLine.startsWith('%%r ') || firstLine === '%%R') {
|
|
66
|
+
return 'r';
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Fall back to cell type
|
|
70
|
+
const type = cellType.toLowerCase();
|
|
71
|
+
if (isSqlCode(type))
|
|
72
|
+
return 'sql';
|
|
73
|
+
if (isPythonCode(type))
|
|
74
|
+
return 'python';
|
|
75
|
+
return type;
|
|
76
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python Formatter Configuration
|
|
3
|
+
*
|
|
4
|
+
* Hardcoded ruff configuration - no file loading needed.
|
|
5
|
+
*/
|
|
6
|
+
/** Ruff format-specific configuration */
|
|
7
|
+
export interface RuffFormatConfig {
|
|
8
|
+
'quote-style'?: 'single' | 'double' | 'preserve';
|
|
9
|
+
'indent-style'?: 'space' | 'tab';
|
|
10
|
+
'skip-magic-trailing-comma'?: boolean;
|
|
11
|
+
'line-ending'?: 'auto' | 'lf' | 'cr-lf' | 'native';
|
|
12
|
+
'docstring-code-format'?: boolean;
|
|
13
|
+
'docstring-code-line-length'?: number | 'dynamic';
|
|
14
|
+
}
|
|
15
|
+
/** Full ruff configuration (subset relevant to formatting) */
|
|
16
|
+
export interface RuffConfig {
|
|
17
|
+
'line-length'?: number;
|
|
18
|
+
'indent-width'?: number;
|
|
19
|
+
format?: RuffFormatConfig;
|
|
20
|
+
}
|
|
21
|
+
/** Default ruff configuration matching our style (140 char lines, 4 space indent) */
|
|
22
|
+
export declare const DEFAULT_RUFF_CONFIG: RuffConfig;
|
|
23
|
+
/** Ruff WASM config format (kebab-case keys as per the Ruff WASM API) */
|
|
24
|
+
export declare const RUFF_WASM_CONFIG: {
|
|
25
|
+
'line-length': number;
|
|
26
|
+
'indent-width': number;
|
|
27
|
+
format: {
|
|
28
|
+
'quote-style': "double" | "single" | "preserve";
|
|
29
|
+
'indent-style': "space" | "tab";
|
|
30
|
+
'skip-magic-trailing-comma': boolean;
|
|
31
|
+
'line-ending': "auto" | "lf" | "cr-lf" | "native";
|
|
32
|
+
};
|
|
33
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python Formatter Configuration
|
|
3
|
+
*
|
|
4
|
+
* Hardcoded ruff configuration - no file loading needed.
|
|
5
|
+
*/
|
|
6
|
+
/** Default ruff configuration matching our style (140 char lines, 4 space indent) */
|
|
7
|
+
export const DEFAULT_RUFF_CONFIG = {
|
|
8
|
+
'line-length': 140,
|
|
9
|
+
'indent-width': 4,
|
|
10
|
+
format: {
|
|
11
|
+
'quote-style': 'double',
|
|
12
|
+
'indent-style': 'space',
|
|
13
|
+
'skip-magic-trailing-comma': false,
|
|
14
|
+
'line-ending': 'lf',
|
|
15
|
+
'docstring-code-format': true,
|
|
16
|
+
'docstring-code-line-length': 'dynamic',
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
/** Ruff WASM config format (kebab-case keys as per the Ruff WASM API) */
|
|
20
|
+
export const RUFF_WASM_CONFIG = {
|
|
21
|
+
'line-length': DEFAULT_RUFF_CONFIG['line-length'],
|
|
22
|
+
'indent-width': DEFAULT_RUFF_CONFIG['indent-width'],
|
|
23
|
+
format: {
|
|
24
|
+
'quote-style': DEFAULT_RUFF_CONFIG.format['quote-style'],
|
|
25
|
+
'indent-style': DEFAULT_RUFF_CONFIG.format['indent-style'],
|
|
26
|
+
'skip-magic-trailing-comma': DEFAULT_RUFF_CONFIG.format['skip-magic-trailing-comma'],
|
|
27
|
+
'line-ending': DEFAULT_RUFF_CONFIG.format['line-ending'],
|
|
28
|
+
}
|
|
29
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python Formatter - Module Exports
|
|
3
|
+
*
|
|
4
|
+
* Python/PySpark formatting using Ruff WASM.
|
|
5
|
+
*/
|
|
6
|
+
export { PythonFormatter, getPythonFormatter, resetPythonFormatter, isPythonCode, type PythonFormatterOptions, type WasmInitOptions, } from './python-formatter.js';
|
|
7
|
+
export { DEFAULT_RUFF_CONFIG, RUFF_WASM_CONFIG, type RuffConfig, type RuffFormatConfig, } from './config.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python Formatter - Module Exports
|
|
3
|
+
*
|
|
4
|
+
* Python/PySpark formatting using Ruff WASM.
|
|
5
|
+
*/
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// FORMATTER CLASS (LanguageFormatter interface)
|
|
8
|
+
// ============================================================================
|
|
9
|
+
export { PythonFormatter, getPythonFormatter, resetPythonFormatter, isPythonCode, } from './python-formatter.js';
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// CONFIGURATION (types only, no file loading)
|
|
12
|
+
// ============================================================================
|
|
13
|
+
export { DEFAULT_RUFF_CONFIG, RUFF_WASM_CONFIG, } from './config.js';
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python Formatter
|
|
3
|
+
*
|
|
4
|
+
* Uses Ruff WASM to format Python/PySpark code.
|
|
5
|
+
* Handles Jupyter/IPython magic commands by preserving them.
|
|
6
|
+
*/
|
|
7
|
+
import type { LanguageFormatter, FormatterOptions, FormatResult } from '../types.js';
|
|
8
|
+
/** Options for initializing the WASM module */
|
|
9
|
+
export interface WasmInitOptions {
|
|
10
|
+
/** URL to the .wasm file (for browser environments) */
|
|
11
|
+
wasmUrl?: string | URL;
|
|
12
|
+
/** WASM binary as ArrayBuffer or Uint8Array (for sync initialization) */
|
|
13
|
+
wasmBinary?: ArrayBuffer | Uint8Array;
|
|
14
|
+
}
|
|
15
|
+
/** Options specific to Python formatting */
|
|
16
|
+
export interface PythonFormatterOptions extends FormatterOptions {
|
|
17
|
+
/** Strip trailing newlines from formatted output */
|
|
18
|
+
stripTrailingNewline?: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Python formatter using Ruff WASM.
|
|
22
|
+
*/
|
|
23
|
+
export declare class PythonFormatter implements LanguageFormatter {
|
|
24
|
+
readonly language = "python";
|
|
25
|
+
readonly displayName = "Python (Ruff)";
|
|
26
|
+
private initialized;
|
|
27
|
+
private initError;
|
|
28
|
+
private wasmOptions;
|
|
29
|
+
/**
|
|
30
|
+
* Create a new Python formatter.
|
|
31
|
+
* @param options - Optional WASM initialization options for browser environments
|
|
32
|
+
*/
|
|
33
|
+
constructor(options?: WasmInitOptions);
|
|
34
|
+
isReady(): boolean;
|
|
35
|
+
initialize(): Promise<void>;
|
|
36
|
+
format(code: string, options?: PythonFormatterOptions): FormatResult;
|
|
37
|
+
needsFormatting(code: string, options?: PythonFormatterOptions): boolean;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Detect if a cell/file is Python/PySpark.
|
|
41
|
+
*/
|
|
42
|
+
export declare function isPythonCode(cellType: string): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Get the Python formatter instance (creates on first call).
|
|
45
|
+
* @param options - Optional WASM initialization options. Only used on first call.
|
|
46
|
+
*/
|
|
47
|
+
export declare function getPythonFormatter(options?: WasmInitOptions): PythonFormatter;
|
|
48
|
+
/**
|
|
49
|
+
* Reset the Python formatter instance (for testing or reinitialization with different options).
|
|
50
|
+
*/
|
|
51
|
+
export declare function resetPythonFormatter(): void;
|