33/* eslint-disable no-cond-assign */
44/* eslint-disable no-bitwise */
55
6- import * as fs from "fs" ;
7- import * as path from "path" ;
6+ // ---------------------------------------------------------------------------
7+ // Node.js dependency injection (avoids top-level fs/path imports for bundlers)
8+ // ---------------------------------------------------------------------------
9+
10+ /** Subset of Node.js `fs` used by the splitter. */
11+ export interface IfcSplitterFs {
12+ openSync ( path : string , flags : string ) : number ;
13+ readSync (
14+ fd : number ,
15+ buffer : any ,
16+ offset : number ,
17+ length : number ,
18+ position : null ,
19+ ) : number ;
20+ writeSync ( fd : number , data : any , offset ?: number , length ?: number ) : number ;
21+ closeSync ( fd : number ) : void ;
22+ existsSync ( path : string ) : boolean ;
23+ mkdirSync ( path : string , options ?: { recursive ?: boolean } ) : void ;
24+ statSync ( path : string ) : { size : number } ;
25+ }
26+
27+ /** Subset of Node.js `path` used by the splitter. */
28+ export interface IfcSplitterPath {
29+ join ( ...paths : string [ ] ) : string ;
30+ dirname ( p : string ) : string ;
31+ basename ( p : string ) : string ;
32+ }
33+
34+ /** Dependencies that must be provided by the caller (Node.js modules). */
35+ export interface IfcSplitterDeps {
36+ fs : IfcSplitterFs ;
37+ path : IfcSplitterPath ;
38+ }
839
940// ---------------------------------------------------------------------------
1041// Exported interfaces
@@ -227,13 +258,19 @@ function extractArgsString(raw: string | undefined): string | null {
227258// ---------------------------------------------------------------------------
228259// Synchronous chunked file reader — replaces readline (3-5x faster)
229260// ---------------------------------------------------------------------------
230- function forEachLine ( filePath : string , callback : ( line : string ) => void ) : void {
261+ function forEachLine (
262+ fsLike : IfcSplitterFs ,
263+ filePath : string ,
264+ callback : ( line : string ) => void ,
265+ ) : void {
231266 const CHUNK = 8 * 1024 * 1024 ;
232- const fd = fs . openSync ( filePath , "r" ) ;
267+ const fd = fsLike . openSync ( filePath , "r" ) ;
233268 const readBuf = Buffer . allocUnsafe ( CHUNK ) ;
234269 let tail = "" ;
235270 let bytesRead : number ;
236- while ( ( bytesRead = fs . readSync ( fd , readBuf as any , 0 , CHUNK , null ) ) > 0 ) {
271+ while (
272+ ( bytesRead = fsLike . readSync ( fd , readBuf as any , 0 , CHUNK , null ) ) > 0
273+ ) {
237274 const chunk = readBuf . toString ( "utf-8" , 0 , bytesRead ) ;
238275 let start = 0 ;
239276 let idx = chunk . indexOf ( "\n" ) ;
@@ -260,20 +297,22 @@ function forEachLine(filePath: string, callback: (line: string) => void): void {
260297 if ( start < chunk . length ) tail = chunk . substring ( start ) ;
261298 }
262299 if ( tail ) callback ( tail ) ;
263- fs . closeSync ( fd ) ;
300+ fsLike . closeSync ( fd ) ;
264301}
265302
266303// ---------------------------------------------------------------------------
267304// Buffered synchronous file writer — avoids per-line write() syscalls
268305// ---------------------------------------------------------------------------
269306class BufferedWriter {
307+ private fsLike : IfcSplitterFs ;
270308 private fd : number ;
271309 private buf : Buffer ;
272310 private pos : number ;
273311 private bufSize : number ;
274312
275- constructor ( filePath : string , bufSize : number ) {
276- this . fd = fs . openSync ( filePath , "w" ) ;
313+ constructor ( fsLike : IfcSplitterFs , filePath : string , bufSize : number ) {
314+ this . fsLike = fsLike ;
315+ this . fd = fsLike . openSync ( filePath , "w" ) ;
277316 this . buf = Buffer . allocUnsafe ( bufSize ) ;
278317 this . pos = 0 ;
279318 this . bufSize = bufSize ;
@@ -284,7 +323,7 @@ class BufferedWriter {
284323 if ( this . pos + bytes > this . bufSize ) {
285324 this . flush ( ) ;
286325 if ( bytes > this . bufSize ) {
287- fs . writeSync ( this . fd , str ) ;
326+ this . fsLike . writeSync ( this . fd , str ) ;
288327 return ;
289328 }
290329 }
@@ -293,14 +332,14 @@ class BufferedWriter {
293332
294333 flush ( ) : void {
295334 if ( this . pos > 0 ) {
296- fs . writeSync ( this . fd , this . buf as any , 0 , this . pos ) ;
335+ this . fsLike . writeSync ( this . fd , this . buf as any , 0 , this . pos ) ;
297336 this . pos = 0 ;
298337 }
299338 }
300339
301340 close ( ) : void {
302341 this . flush ( ) ;
303- fs . closeSync ( this . fd ) ;
342+ this . fsLike . closeSync ( this . fd ) ;
304343 }
305344}
306345
@@ -389,7 +428,7 @@ class LineIndex {
389428// ---------------------------------------------------------------------------
390429// Streaming IFC parser
391430// ---------------------------------------------------------------------------
392- function parseIfc ( filePath : string ) : ParseResult {
431+ function parseIfc ( fsLike : IfcSplitterFs , filePath : string ) : ParseResult {
393432 const header : string [ ] = [ ] ;
394433 const footer : string [ ] = [ ] ;
395434 const index = new LineIndex ( ) ;
@@ -400,7 +439,7 @@ function parseIfc(filePath: string): ParseResult {
400439
401440 console . time ( "parse" ) ;
402441
403- forEachLine ( filePath , ( line : string ) => {
442+ forEachLine ( fsLike , filePath , ( line : string ) => {
404443 if ( section === "header" ) {
405444 header . push ( line ) ;
406445 if ( line . trim ( ) === "DATA;" ) section = "data" ;
@@ -758,10 +797,12 @@ function resolveStyles(
758797 * @param outputDir - Directory for output files. Defaults to `output/` next to the input file.
759798 */
760799export function split (
800+ deps : IfcSplitterDeps ,
761801 inputPath : string ,
762802 numGroups : number ,
763803 outputDir ?: string ,
764804) : void {
805+ const { fs, path } = deps ;
765806 if ( ! fs . existsSync ( inputPath ) ) {
766807 console . error ( `File not found: ${ inputPath } ` ) ;
767808 process . exit ( 1 ) ;
@@ -772,7 +813,7 @@ export function split(
772813 fs . mkdirSync ( resolvedOutputDir , { recursive : true } ) ;
773814
774815 // 1. Parse
775- const { header, footer, index } = parseIfc ( inputPath ) ;
816+ const { header, footer, index } = parseIfc ( fs , inputPath ) ;
776817
777818 // 2. Identify spatial structure (shared in all files)
778819 console . time ( "spatial" ) ;
@@ -1004,6 +1045,7 @@ export function split(
10041045 // 10. Second pass: write output files
10051046 console . time ( "write" ) ;
10061047 writeOutputFiles (
1048+ deps ,
10071049 inputPath ,
10081050 resolvedOutputDir ,
10091051 header ,
@@ -1031,10 +1073,12 @@ export function split(
10311073 * @param outputPath - Path for the output IFC file.
10321074 */
10331075export function extract (
1076+ deps : IfcSplitterDeps ,
10341077 inputPath : string ,
10351078 elementIds : number [ ] ,
10361079 outputPath : string ,
10371080) : void {
1081+ const { fs, path } = deps ;
10381082 if ( ! fs . existsSync ( inputPath ) ) {
10391083 console . error ( `File not found: ${ inputPath } ` ) ;
10401084 process . exit ( 1 ) ;
@@ -1044,7 +1088,7 @@ export function extract(
10441088 fs . mkdirSync ( outputDir , { recursive : true } ) ;
10451089
10461090 // 1. Parse
1047- const { header, footer, index } = parseIfc ( inputPath ) ;
1091+ const { header, footer, index } = parseIfc ( fs , inputPath ) ;
10481092
10491093 // 2. Identify spatial structure (shared)
10501094 console . time ( "spatial" ) ;
@@ -1181,20 +1225,20 @@ export function extract(
11811225 console . log ( ` Total lines in output: ${ fileIds . size } ` ) ;
11821226
11831227 // 7. Free index, write output
1184- const maxParsedId = index . maxId ;
1228+ // const maxParsedId = index.maxId;
11851229 index . free ( ) ;
11861230
11871231 // Build simple inclusion set
11881232 const includeSet = new Set < number > ( fileIds ) ;
11891233
11901234 console . time ( "write" ) ;
1191- const bw = new BufferedWriter ( outputPath , 4 * 1024 * 1024 ) ;
1235+ const bw = new BufferedWriter ( fs , outputPath , 4 * 1024 * 1024 ) ;
11921236 bw . write ( `${ header . join ( "\n" ) } \n` ) ;
11931237
11941238 let section : "header" | "data" | "footer" = "header" ;
11951239 let accumulator = "" ;
11961240
1197- forEachLine ( inputPath , ( line : string ) => {
1241+ forEachLine ( fs , inputPath , ( line : string ) => {
11981242 if ( section === "header" ) {
11991243 if ( line . trim ( ) === "DATA;" ) section = "data" ;
12001244 return ;
@@ -1260,13 +1304,15 @@ function emitSingleLine(
12601304// Second-pass output writer
12611305// ---------------------------------------------------------------------------
12621306function writeOutputFiles (
1307+ deps : IfcSplitterDeps ,
12631308 inputPath : string ,
12641309 outputDir : string ,
12651310 header : string [ ] ,
12661311 footer : string [ ] ,
12671312 groupsData : ( GroupData | null ) [ ] ,
12681313 idGroupMask : Uint32Array ,
12691314) : void {
1315+ const { fs, path } = deps ;
12701316 const numGroups = groupsData . length ;
12711317
12721318 const writers : ( BufferedWriter | null ) [ ] = [ ] ;
@@ -1280,15 +1326,15 @@ function writeOutputFiles(
12801326 outputDir ,
12811327 `split_${ String ( g + 1 ) . padStart ( 3 , "0" ) } .ifc` ,
12821328 ) ;
1283- const bw = new BufferedWriter ( outName , 4 * 1024 * 1024 ) ;
1329+ const bw = new BufferedWriter ( fs , outName , 4 * 1024 * 1024 ) ;
12841330 bw . write ( headerStr ) ;
12851331 writers . push ( bw ) ;
12861332 }
12871333
12881334 let section : "header" | "data" | "footer" = "header" ;
12891335 let accumulator = "" ;
12901336
1291- forEachLine ( inputPath , ( line : string ) => {
1337+ forEachLine ( fs , inputPath , ( line : string ) => {
12921338 if ( section === "header" ) {
12931339 if ( line . trim ( ) === "DATA;" ) section = "data" ;
12941340 return ;
0 commit comments