@jacobknightley/fabric-format 0.0.1 → 0.0.3
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/dist/cli.js +4 -24
- package/dist/formatters/python/python-formatter.js +56 -2
- package/dist/notebook-formatter.js +12 -15
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -86,30 +86,12 @@ async function formatFile(content, filePath) {
|
|
|
86
86
|
return formatted;
|
|
87
87
|
}
|
|
88
88
|
/**
|
|
89
|
-
*
|
|
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.
|
|
89
|
+
* Normalize line endings to LF (Unix style).
|
|
90
|
+
* This library standardizes on LF for all output.
|
|
99
91
|
*/
|
|
100
92
|
function normalizeLineEndings(content) {
|
|
101
93
|
return content.replace(/\r\n/g, '\n');
|
|
102
94
|
}
|
|
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
95
|
/** Print main help */
|
|
114
96
|
function printHelp() {
|
|
115
97
|
console.log(`fabfmt - Fabric Notebook Formatter (Spark SQL & Python)
|
|
@@ -307,12 +289,10 @@ async function cmdFormat(args) {
|
|
|
307
289
|
for (const file of files) {
|
|
308
290
|
try {
|
|
309
291
|
const content = fs.readFileSync(file, 'utf-8');
|
|
310
|
-
const originalLineEnding = detectLineEnding(content);
|
|
311
292
|
const normalizedContent = normalizeLineEndings(content);
|
|
312
293
|
const formatted = await formatFile(normalizedContent, file);
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
fs.writeFileSync(file, formattedWithOriginalEndings, 'utf-8');
|
|
294
|
+
if (formatted !== normalizedContent) {
|
|
295
|
+
fs.writeFileSync(file, formatted, 'utf-8');
|
|
316
296
|
console.log(`Formatted ${file}`);
|
|
317
297
|
formattedCount++;
|
|
318
298
|
}
|
|
@@ -8,6 +8,56 @@ import { RUFF_WASM_CONFIG } from './config.js';
|
|
|
8
8
|
// Dynamic import for ruff WASM (loaded on demand)
|
|
9
9
|
let ruffModule = null;
|
|
10
10
|
let workspace = null;
|
|
11
|
+
/**
|
|
12
|
+
* Detect if we're running in Node.js
|
|
13
|
+
*/
|
|
14
|
+
function isNodeEnvironment() {
|
|
15
|
+
return typeof process !== 'undefined' &&
|
|
16
|
+
process.versions != null &&
|
|
17
|
+
process.versions.node != null;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Find the WASM file path relative to the ruff-wasm-web package in Node.js.
|
|
21
|
+
* Uses indirect dynamic imports to avoid bundler static analysis.
|
|
22
|
+
*/
|
|
23
|
+
async function findWasmFileForNode() {
|
|
24
|
+
// Use Function constructor to create dynamic import that bundlers can't statically analyze
|
|
25
|
+
// This is intentional - these modules only exist in Node.js, not in browsers
|
|
26
|
+
const dynamicImport = new Function('specifier', 'return import(specifier)');
|
|
27
|
+
const { createRequire } = await dynamicImport('module');
|
|
28
|
+
const { dirname, join } = await dynamicImport('path');
|
|
29
|
+
const { readFile } = await dynamicImport('fs/promises');
|
|
30
|
+
// Get the path to ruff-wasm-web package
|
|
31
|
+
// We need import.meta.url to create a require function
|
|
32
|
+
// Use a fallback for bundled environments (though this path shouldn't be hit in browsers)
|
|
33
|
+
let ruffWasmDir;
|
|
34
|
+
try {
|
|
35
|
+
const require = createRequire(import.meta.url);
|
|
36
|
+
const ruffWasmPath = require.resolve('@astral-sh/ruff-wasm-web');
|
|
37
|
+
ruffWasmDir = dirname(ruffWasmPath);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// Fallback: try to find it via node_modules traversal
|
|
41
|
+
const { fileURLToPath } = await dynamicImport('url');
|
|
42
|
+
const currentDir = dirname(fileURLToPath(import.meta.url));
|
|
43
|
+
// Walk up to find node_modules
|
|
44
|
+
let searchDir = currentDir;
|
|
45
|
+
const { existsSync } = await dynamicImport('fs');
|
|
46
|
+
while (searchDir !== dirname(searchDir)) {
|
|
47
|
+
const candidate = join(searchDir, 'node_modules', '@astral-sh', 'ruff-wasm-web');
|
|
48
|
+
if (existsSync(candidate)) {
|
|
49
|
+
ruffWasmDir = candidate;
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
searchDir = dirname(searchDir);
|
|
53
|
+
}
|
|
54
|
+
if (!ruffWasmDir) {
|
|
55
|
+
throw new Error('Could not locate @astral-sh/ruff-wasm-web package');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const wasmPath = join(ruffWasmDir, 'ruff_wasm_bg.wasm');
|
|
59
|
+
return readFile(wasmPath);
|
|
60
|
+
}
|
|
11
61
|
/**
|
|
12
62
|
* Python formatter using Ruff WASM.
|
|
13
63
|
*/
|
|
@@ -43,9 +93,13 @@ export class PythonFormatter {
|
|
|
43
93
|
// Use async initialization with provided URL
|
|
44
94
|
await ruffModule.default({ module_or_path: this.wasmOptions.wasmUrl });
|
|
45
95
|
}
|
|
96
|
+
else if (isNodeEnvironment()) {
|
|
97
|
+
// Node.js: Load WASM file from disk
|
|
98
|
+
const wasmBinary = await findWasmFileForNode();
|
|
99
|
+
ruffModule.initSync({ module: wasmBinary });
|
|
100
|
+
}
|
|
46
101
|
else {
|
|
47
|
-
//
|
|
48
|
-
// This works in Node.js and ESM environments but may fail in bundled IIFE
|
|
102
|
+
// Browser: let ruff-wasm-web use import.meta.url to find the WASM file
|
|
49
103
|
await ruffModule.default();
|
|
50
104
|
}
|
|
51
105
|
// Create workspace with config
|
|
@@ -37,7 +37,7 @@ const PYTHON_CONFIG = {
|
|
|
37
37
|
metadataMarker: '# METADATA ********************',
|
|
38
38
|
magicPrefix: '# MAGIC ',
|
|
39
39
|
magicSqlCommand: '# MAGIC %%sql',
|
|
40
|
-
emptyMagic: '# MAGIC',
|
|
40
|
+
emptyMagic: '# MAGIC ',
|
|
41
41
|
supportsRawSql: false,
|
|
42
42
|
defaultLanguage: 'python',
|
|
43
43
|
};
|
|
@@ -47,7 +47,7 @@ const SCALA_CONFIG = {
|
|
|
47
47
|
metadataMarker: '// METADATA ********************',
|
|
48
48
|
magicPrefix: '// MAGIC ',
|
|
49
49
|
magicSqlCommand: '// MAGIC %%sql',
|
|
50
|
-
emptyMagic: '// MAGIC',
|
|
50
|
+
emptyMagic: '// MAGIC ',
|
|
51
51
|
supportsRawSql: false,
|
|
52
52
|
defaultLanguage: 'scala',
|
|
53
53
|
};
|
|
@@ -57,7 +57,7 @@ const SPARKSQL_CONFIG = {
|
|
|
57
57
|
metadataMarker: '-- METADATA ********************',
|
|
58
58
|
magicPrefix: '-- MAGIC ',
|
|
59
59
|
magicSqlCommand: '-- MAGIC %%sql',
|
|
60
|
-
emptyMagic: '-- MAGIC',
|
|
60
|
+
emptyMagic: '-- MAGIC ',
|
|
61
61
|
supportsRawSql: true,
|
|
62
62
|
defaultLanguage: 'sparksql',
|
|
63
63
|
};
|
|
@@ -73,13 +73,9 @@ const R_CONFIG = {
|
|
|
73
73
|
// INTERNAL UTILITIES
|
|
74
74
|
// ============================================================================
|
|
75
75
|
/**
|
|
76
|
-
*
|
|
76
|
+
* Line ending constant - this library standardizes on LF.
|
|
77
77
|
*/
|
|
78
|
-
|
|
79
|
-
if (content.includes('\r\n'))
|
|
80
|
-
return '\r\n';
|
|
81
|
-
return '\n';
|
|
82
|
-
}
|
|
78
|
+
const LINE_ENDING = '\n';
|
|
83
79
|
/**
|
|
84
80
|
* Get language config based on file extension.
|
|
85
81
|
* The file extension determines the default language and comment syntax.
|
|
@@ -203,7 +199,9 @@ function stripMagicPrefix(line, config) {
|
|
|
203
199
|
if (line.startsWith(config.magicPrefix)) {
|
|
204
200
|
return line.slice(config.magicPrefix.length);
|
|
205
201
|
}
|
|
206
|
-
|
|
202
|
+
// Check for empty magic line (with or without trailing space)
|
|
203
|
+
const trimmed = line.trim();
|
|
204
|
+
if (trimmed === config.emptyMagic.trim()) {
|
|
207
205
|
return '';
|
|
208
206
|
}
|
|
209
207
|
return line;
|
|
@@ -332,7 +330,7 @@ export function parseNotebook(content, fileExtension) {
|
|
|
332
330
|
/**
|
|
333
331
|
* Replace a cell's content in the file.
|
|
334
332
|
*/
|
|
335
|
-
function replaceCell(fileContent, cell, formattedContent, config
|
|
333
|
+
function replaceCell(fileContent, cell, formattedContent, config) {
|
|
336
334
|
const lines = fileContent.split(/\r?\n/);
|
|
337
335
|
let newLines;
|
|
338
336
|
if (cell.isMagicCell) {
|
|
@@ -343,7 +341,7 @@ function replaceCell(fileContent, cell, formattedContent, config, lineEnding) {
|
|
|
343
341
|
}
|
|
344
342
|
const before = lines.slice(0, cell.contentStartLine);
|
|
345
343
|
const after = lines.slice(cell.contentEndLine + 1);
|
|
346
|
-
return [...before, ...newLines, ...after].join(
|
|
344
|
+
return [...before, ...newLines, ...after].join(LINE_ENDING);
|
|
347
345
|
}
|
|
348
346
|
/**
|
|
349
347
|
* Format all cells in a Fabric notebook.
|
|
@@ -370,7 +368,6 @@ export async function formatNotebook(content, fileExtension, options) {
|
|
|
370
368
|
if (!notebook.isFabricNotebook || notebook.cells.length === 0 || !notebook.config) {
|
|
371
369
|
return { content, stats };
|
|
372
370
|
}
|
|
373
|
-
const lineEnding = detectLineEnding(content);
|
|
374
371
|
// Initialize Python formatter if needed
|
|
375
372
|
if (formatPythonCells) {
|
|
376
373
|
try {
|
|
@@ -397,7 +394,7 @@ export async function formatNotebook(content, fileExtension, options) {
|
|
|
397
394
|
const formatResult = formatCell(cell.content, 'sparksql');
|
|
398
395
|
if (formatResult.changed) {
|
|
399
396
|
// replaceCell will add back MAGIC prefixes if needed
|
|
400
|
-
result = replaceCell(result, cell, formatResult.formatted, notebook.config
|
|
397
|
+
result = replaceCell(result, cell, formatResult.formatted, notebook.config);
|
|
401
398
|
stats.sparkSqlCellsFormatted++;
|
|
402
399
|
}
|
|
403
400
|
if (formatResult.error) {
|
|
@@ -409,7 +406,7 @@ export async function formatNotebook(content, fileExtension, options) {
|
|
|
409
406
|
const formatResult = formatCell(cell.content, 'python');
|
|
410
407
|
if (formatResult.changed) {
|
|
411
408
|
// replaceCell will add back MAGIC prefixes if needed
|
|
412
|
-
result = replaceCell(result, cell, formatResult.formatted, notebook.config
|
|
409
|
+
result = replaceCell(result, cell, formatResult.formatted, notebook.config);
|
|
413
410
|
stats.pythonCellsFormatted++;
|
|
414
411
|
}
|
|
415
412
|
if (formatResult.error) {
|
package/package.json
CHANGED