@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.
Files changed (50) hide show
  1. package/README.md +196 -0
  2. package/dist/cell-formatter.d.ts +75 -0
  3. package/dist/cell-formatter.js +144 -0
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.js +435 -0
  6. package/dist/formatters/index.d.ts +19 -0
  7. package/dist/formatters/index.js +76 -0
  8. package/dist/formatters/python/config.d.ts +33 -0
  9. package/dist/formatters/python/config.js +29 -0
  10. package/dist/formatters/python/index.d.ts +7 -0
  11. package/dist/formatters/python/index.js +13 -0
  12. package/dist/formatters/python/python-formatter.d.ts +51 -0
  13. package/dist/formatters/python/python-formatter.js +180 -0
  14. package/dist/formatters/sparksql/constants.d.ts +16 -0
  15. package/dist/formatters/sparksql/constants.js +16 -0
  16. package/dist/formatters/sparksql/fmt-detector.d.ts +65 -0
  17. package/dist/formatters/sparksql/fmt-detector.js +84 -0
  18. package/dist/formatters/sparksql/formatter.d.ts +24 -0
  19. package/dist/formatters/sparksql/formatter.js +1276 -0
  20. package/dist/formatters/sparksql/formatting-context.d.ts +154 -0
  21. package/dist/formatters/sparksql/formatting-context.js +363 -0
  22. package/dist/formatters/sparksql/generated/SqlBaseLexer.d.ts +529 -0
  23. package/dist/formatters/sparksql/generated/SqlBaseLexer.js +2609 -0
  24. package/dist/formatters/sparksql/generated/SqlBaseParser.d.ts +8195 -0
  25. package/dist/formatters/sparksql/generated/SqlBaseParser.js +48793 -0
  26. package/dist/formatters/sparksql/generated/SqlBaseParserListener.d.ts +910 -0
  27. package/dist/formatters/sparksql/generated/SqlBaseParserListener.js +2730 -0
  28. package/dist/formatters/sparksql/generated/SqlBaseParserVisitor.d.ts +456 -0
  29. package/dist/formatters/sparksql/generated/SqlBaseParserVisitor.js +1822 -0
  30. package/dist/formatters/sparksql/generated/builtinFunctions.d.ts +8 -0
  31. package/dist/formatters/sparksql/generated/builtinFunctions.js +510 -0
  32. package/dist/formatters/sparksql/index.d.ts +11 -0
  33. package/dist/formatters/sparksql/index.js +22 -0
  34. package/dist/formatters/sparksql/output-builder.d.ts +89 -0
  35. package/dist/formatters/sparksql/output-builder.js +191 -0
  36. package/dist/formatters/sparksql/parse-tree-analyzer.d.ts +264 -0
  37. package/dist/formatters/sparksql/parse-tree-analyzer.js +1956 -0
  38. package/dist/formatters/sparksql/sql-formatter.d.ts +25 -0
  39. package/dist/formatters/sparksql/sql-formatter.js +56 -0
  40. package/dist/formatters/sparksql/token-utils.d.ts +68 -0
  41. package/dist/formatters/sparksql/token-utils.js +155 -0
  42. package/dist/formatters/sparksql/types.d.ts +264 -0
  43. package/dist/formatters/sparksql/types.js +7 -0
  44. package/dist/formatters/types.d.ts +57 -0
  45. package/dist/formatters/types.js +7 -0
  46. package/dist/index.d.ts +18 -0
  47. package/dist/index.js +41 -0
  48. package/dist/notebook-formatter.d.ts +107 -0
  49. package/dist/notebook-formatter.js +424 -0
  50. 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;