1+ import { program } from "commander" ;
2+ import process from "node:process" ;
3+ import { readFileSync , existsSync } from "node:fs" ;
4+
5+ program
6+ . name ( "wc" )
7+ . description ( "Count lines, words, and characters in files" )
8+ . argument ( "[files...]" , "Files to process" )
9+ . option ( "-l, --lines" , "Count lines" )
10+ . option ( "-w, --words" , "Count words" )
11+ . option ( "-c, --chars" , "Count characters (bytes)" )
12+ program . parse ( process . argv ) ;
13+
14+ const options = program . opts ( ) ;
15+ const files = program . args . length ? program . args : [ "/dev/stdin" ] ;
16+
17+ function countFile ( filePath , options ) {
18+ let content = "" ;
19+ try {
20+ if ( filePath === "/dev/stdin" ) {
21+ content = readFileSync ( process . stdin . fd , "utf8" ) ;
22+ } else {
23+ if ( ! existsSync ( filePath ) ) {
24+ console . error ( `wc: ${ filePath } : No such file or directory` ) ;
25+ return null ;
26+ }
27+ content = readFileSync ( filePath , "utf8" ) ;
28+ }
29+ } catch ( error ) {
30+ console . error ( `wc: ${ filePath } : ${ error . message } ` ) ;
31+ return null ;
32+ }
33+
34+ if ( typeof content !== 'string' ) {
35+ content = "" ;
36+ }
37+
38+ const lineCount = ( content . match ( / \n / g) || [ ] ) . length ;
39+
40+ const wordCount = content . trim ( ) . split ( / \s + / ) . filter ( Boolean ) . length ;
41+
42+ const charCount = Buffer . byteLength ( content , "utf8" ) ;
43+
44+ return {
45+ file : filePath ,
46+ lines : options . lines || ( ! options . words && ! options . chars ) ? lineCount : null ,
47+ words : options . words || ( ! options . lines && ! options . chars ) ? wordCount : null ,
48+ chars : options . chars || ( ! options . lines && ! options . words ) ? charCount : null ,
49+ } ;
50+ }
51+
52+ const results = [ ] ;
53+ let totalLines = 0 ;
54+ let totalWords = 0 ;
55+ let totalChars = 0 ;
56+ const hasMultipleFiles = files . length > 1 ;
57+
58+ files . forEach ( file => {
59+ const result = countFile ( file , options ) ;
60+ if ( result ) {
61+ results . push ( result ) ;
62+
63+ if ( result . lines !== null ) totalLines += result . lines ;
64+ if ( result . words !== null ) totalWords += result . words ;
65+ if ( result . chars !== null ) totalChars += result . chars ;
66+ }
67+ } ) ;
68+
69+ results . forEach ( result => {
70+ const output = [ ] ;
71+ if ( result . lines !== null ) output . push ( result . lines . toString ( ) . padStart ( 8 ) ) ;
72+ if ( result . words !== null ) output . push ( result . words . toString ( ) . padStart ( 8 ) ) ;
73+ if ( result . chars !== null ) output . push ( result . chars . toString ( ) . padStart ( 8 ) ) ;
74+ console . log ( output . join ( " " ) , result . file ) ;
75+ } ) ;
76+
77+ if ( hasMultipleFiles && results . length > 0 ) {
78+ const totalOutput = [ ] ;
79+ if ( options . lines || ( ! options . words && ! options . chars ) ) {
80+ totalOutput . push ( totalLines . toString ( ) . padStart ( 8 ) ) ;
81+ }
82+ if ( options . words || ( ! options . lines && ! options . chars ) ) {
83+ totalOutput . push ( totalWords . toString ( ) . padStart ( 8 ) ) ;
84+ }
85+ if ( options . chars || ( ! options . lines && ! options . words ) ) {
86+ totalOutput . push ( totalChars . toString ( ) . padStart ( 8 ) ) ;
87+ }
88+ console . log ( totalOutput . join ( " " ) , "total" ) ;
89+ }
0 commit comments