@@ -81,47 +81,147 @@ fn copy_lines<F: FnMut(&mut [u8]) -> Result<usize>>(
8181 Ok ( ExecuteResult :: from_exit_code ( 0 ) )
8282}
8383
84+ fn copy_all_but_last_lines < F : FnMut ( & mut [ u8 ] ) -> Result < usize > > (
85+ writer : & mut ShellPipeWriter ,
86+ skip_last : u64 ,
87+ kill_signal : & KillSignal ,
88+ mut read : F ,
89+ ) -> Result < ExecuteResult > {
90+ // read all content first
91+ let mut content = Vec :: new ( ) ;
92+ let mut buffer = vec ! [ 0 ; 512 ] ;
93+ loop {
94+ if let Some ( exit_code) = kill_signal. aborted_code ( ) {
95+ return Ok ( ExecuteResult :: from_exit_code ( exit_code) ) ;
96+ }
97+ let read_bytes = read ( & mut buffer) ?;
98+ if read_bytes == 0 {
99+ break ;
100+ }
101+ content. extend_from_slice ( & buffer[ ..read_bytes] ) ;
102+ }
103+
104+ // count total lines
105+ let total_lines = content. iter ( ) . filter ( |& & b| b == b'\n' ) . count ( ) as u64 ;
106+
107+ // output all but the last N lines
108+ if total_lines <= skip_last {
109+ return Ok ( ExecuteResult :: from_exit_code ( 0 ) ) ;
110+ }
111+ let lines_to_print = total_lines - skip_last;
112+
113+ let mut line_count = 0u64 ;
114+ let mut start = 0 ;
115+ for ( i, & b) in content. iter ( ) . enumerate ( ) {
116+ if let Some ( exit_code) = kill_signal. aborted_code ( ) {
117+ return Ok ( ExecuteResult :: from_exit_code ( exit_code) ) ;
118+ }
119+ if b == b'\n' {
120+ line_count += 1 ;
121+ if line_count <= lines_to_print {
122+ writer. write_all ( & content[ start..=i] ) ?;
123+ }
124+ start = i + 1 ;
125+ if line_count >= lines_to_print {
126+ break ;
127+ }
128+ }
129+ }
130+
131+ Ok ( ExecuteResult :: from_exit_code ( 0 ) )
132+ }
133+
84134fn execute_head ( mut context : ShellCommandContext ) -> Result < ExecuteResult > {
85135 let flags = parse_args ( & context. args ) ?;
86- if flags. path == "-" {
87- copy_lines (
88- & mut context. stdout ,
89- flags. lines ,
90- context. state . kill_signal ( ) ,
91- |buf| context. stdin . read ( buf) ,
92- 512 ,
93- )
94- } else {
95- let path = flags. path ;
96- match File :: open ( context. state . cwd ( ) . join ( path) ) {
97- Ok ( mut file) => copy_lines (
98- & mut context. stdout ,
99- flags. lines ,
100- context. state . kill_signal ( ) ,
101- |buf| file. read ( buf) . map_err ( Into :: into) ,
102- 512 ,
103- ) ,
104- Err ( err) => {
105- context. stderr . write_line ( & format ! (
106- "head: {}: {}" ,
107- path. to_string_lossy( ) ,
108- err
109- ) ) ?;
110- Ok ( ExecuteResult :: from_exit_code ( 1 ) )
136+ match flags. lines {
137+ LineCount :: First ( max_lines) => {
138+ if flags. path == "-" {
139+ copy_lines (
140+ & mut context. stdout ,
141+ max_lines,
142+ context. state . kill_signal ( ) ,
143+ |buf| context. stdin . read ( buf) ,
144+ 512 ,
145+ )
146+ } else {
147+ let path = flags. path ;
148+ match File :: open ( context. state . cwd ( ) . join ( path) ) {
149+ Ok ( mut file) => copy_lines (
150+ & mut context. stdout ,
151+ max_lines,
152+ context. state . kill_signal ( ) ,
153+ |buf| file. read ( buf) . map_err ( Into :: into) ,
154+ 512 ,
155+ ) ,
156+ Err ( err) => {
157+ context. stderr . write_line ( & format ! (
158+ "head: {}: {}" ,
159+ path. to_string_lossy( ) ,
160+ err
161+ ) ) ?;
162+ Ok ( ExecuteResult :: from_exit_code ( 1 ) )
163+ }
164+ }
165+ }
166+ }
167+ LineCount :: AllButLast ( skip_last) => {
168+ if flags. path == "-" {
169+ copy_all_but_last_lines (
170+ & mut context. stdout ,
171+ skip_last,
172+ context. state . kill_signal ( ) ,
173+ |buf| context. stdin . read ( buf) ,
174+ )
175+ } else {
176+ let path = flags. path ;
177+ match File :: open ( context. state . cwd ( ) . join ( path) ) {
178+ Ok ( mut file) => copy_all_but_last_lines (
179+ & mut context. stdout ,
180+ skip_last,
181+ context. state . kill_signal ( ) ,
182+ |buf| file. read ( buf) . map_err ( Into :: into) ,
183+ ) ,
184+ Err ( err) => {
185+ context. stderr . write_line ( & format ! (
186+ "head: {}: {}" ,
187+ path. to_string_lossy( ) ,
188+ err
189+ ) ) ?;
190+ Ok ( ExecuteResult :: from_exit_code ( 1 ) )
191+ }
192+ }
111193 }
112194 }
113195 }
114196}
115197
198+ #[ derive( Debug , PartialEq , Clone , Copy ) ]
199+ enum LineCount {
200+ /// print first N lines
201+ First ( u64 ) ,
202+ /// print all but last N lines
203+ AllButLast ( u64 ) ,
204+ }
205+
116206#[ derive( Debug , PartialEq ) ]
117207struct HeadFlags < ' a > {
118208 path : & ' a OsStr ,
119- lines : u64 ,
209+ lines : LineCount ,
210+ }
211+
212+ fn parse_line_count ( s : & str ) -> Result < LineCount > {
213+ if let Some ( rest) = s. strip_prefix ( '-' ) {
214+ let num = rest. parse :: < u64 > ( ) ?;
215+ Ok ( LineCount :: AllButLast ( num) )
216+ } else {
217+ let num = s. parse :: < u64 > ( ) ?;
218+ Ok ( LineCount :: First ( num) )
219+ }
120220}
121221
122222fn parse_args < ' a > ( args : & ' a [ OsString ] ) -> Result < HeadFlags < ' a > > {
123223 let mut path: Option < & ' a OsStr > = None ;
124- let mut lines: Option < u64 > = None ;
224+ let mut lines: Option < LineCount > = None ;
125225 let mut iterator = parse_arg_kinds ( args) . into_iter ( ) ;
126226 while let Some ( arg) = iterator. next ( ) {
127227 match arg {
@@ -137,9 +237,11 @@ fn parse_args<'a>(args: &'a [OsString]) -> Result<HeadFlags<'a>> {
137237 }
138238 ArgKind :: ShortFlag ( 'n' ) => match iterator. next ( ) {
139239 Some ( ArgKind :: Arg ( arg) ) => {
140- let num = arg. to_str ( ) . and_then ( |a| a. parse :: < u64 > ( ) . ok ( ) ) ;
141- if let Some ( num) = num {
142- lines = Some ( num) ;
240+ if let Some ( s) = arg. to_str ( ) {
241+ match parse_line_count ( s) {
242+ Ok ( count) => lines = Some ( count) ,
243+ Err ( _) => bail ! ( "expected a numeric value following -n" ) ,
244+ }
143245 } else {
144246 bail ! ( "expected a numeric value following -n" )
145247 }
@@ -150,7 +252,7 @@ fn parse_args<'a>(args: &'a [OsString]) -> Result<HeadFlags<'a>> {
150252 if flag == "lines" || flag == "lines=" {
151253 bail ! ( "expected a value for --lines" ) ;
152254 } else if let Some ( arg) = flag. strip_prefix ( "lines=" ) {
153- lines = Some ( arg . parse :: < u64 > ( ) ?) ;
255+ lines = Some ( parse_line_count ( arg ) ?) ;
154256 } else {
155257 arg. bail_unsupported ( ) ?
156258 }
@@ -161,7 +263,7 @@ fn parse_args<'a>(args: &'a [OsString]) -> Result<HeadFlags<'a>> {
161263
162264 Ok ( HeadFlags {
163265 path : path. unwrap_or ( OsStr :: new ( "-" ) ) ,
164- lines : lines. unwrap_or ( 10 ) ,
266+ lines : lines. unwrap_or ( LineCount :: First ( 10 ) ) ,
165267 } )
166268}
167269
@@ -231,56 +333,78 @@ mod test {
231333 parse_args( & [ ] ) . unwrap( ) ,
232334 HeadFlags {
233335 path: OsStr :: new( "-" ) ,
234- lines: 10
336+ lines: LineCount :: First ( 10 )
235337 }
236338 ) ;
237339 assert_eq ! (
238340 parse_args( & [ "-n" . into( ) , "5" . into( ) ] ) . unwrap( ) ,
239341 HeadFlags {
240342 path: OsStr :: new( "-" ) ,
241- lines: 5
343+ lines: LineCount :: First ( 5 )
242344 }
243345 ) ;
244346 assert_eq ! (
245347 parse_args( & [ "--lines=5" . into( ) ] ) . unwrap( ) ,
246348 HeadFlags {
247349 path: OsStr :: new( "-" ) ,
248- lines: 5
350+ lines: LineCount :: First ( 5 )
249351 }
250352 ) ;
251353 assert_eq ! (
252354 parse_args( & [ "path" . into( ) ] ) . unwrap( ) ,
253355 HeadFlags {
254356 path: OsStr :: new( "path" ) ,
255- lines: 10
357+ lines: LineCount :: First ( 10 )
256358 }
257359 ) ;
258360 assert_eq ! (
259361 parse_args( & [ "-n" . into( ) , "5" . into( ) , "path" . into( ) ] ) . unwrap( ) ,
260362 HeadFlags {
261363 path: OsStr :: new( "path" ) ,
262- lines: 5
364+ lines: LineCount :: First ( 5 )
263365 }
264366 ) ;
265367 assert_eq ! (
266368 parse_args( & [ "--lines=5" . into( ) , "path" . into( ) ] ) . unwrap( ) ,
267369 HeadFlags {
268370 path: OsStr :: new( "path" ) ,
269- lines: 5
371+ lines: LineCount :: First ( 5 )
270372 }
271373 ) ;
272374 assert_eq ! (
273375 parse_args( & [ "path" . into( ) , "-n" . into( ) , "5" . into( ) ] ) . unwrap( ) ,
274376 HeadFlags {
275377 path: OsStr :: new( "path" ) ,
276- lines: 5
378+ lines: LineCount :: First ( 5 )
277379 }
278380 ) ;
279381 assert_eq ! (
280382 parse_args( & [ "path" . into( ) , "--lines=5" . into( ) ] ) . unwrap( ) ,
281383 HeadFlags {
282384 path: OsStr :: new( "path" ) ,
283- lines: 5
385+ lines: LineCount :: First ( 5 )
386+ }
387+ ) ;
388+ // negative line counts (all but last N)
389+ assert_eq ! (
390+ parse_args( & [ "-n" . into( ) , "-1" . into( ) ] ) . unwrap( ) ,
391+ HeadFlags {
392+ path: OsStr :: new( "-" ) ,
393+ lines: LineCount :: AllButLast ( 1 )
394+ }
395+ ) ;
396+ assert_eq ! (
397+ parse_args( & [ "-n" . into( ) , "-5" . into( ) , "path" . into( ) ] ) . unwrap( ) ,
398+ HeadFlags {
399+ path: OsStr :: new( "path" ) ,
400+ lines: LineCount :: AllButLast ( 5 )
401+ }
402+ ) ;
403+ assert_eq ! (
404+ parse_args( & [ "--lines=-3" . into( ) ] ) . unwrap( ) ,
405+ HeadFlags {
406+ path: OsStr :: new( "-" ) ,
407+ lines: LineCount :: AllButLast ( 3 )
284408 }
285409 ) ;
286410 assert_eq ! (
@@ -304,4 +428,80 @@ mod test {
304428 "unsupported flag: -t"
305429 ) ;
306430 }
431+
432+ #[ tokio:: test]
433+ async fn copies_all_but_last_lines ( ) {
434+ let ( reader, mut writer) = pipe ( ) ;
435+ let reader_handle = reader. pipe_to_string_handle ( ) ;
436+ let data = b"line1\n line2\n line3\n line4\n line5\n " ;
437+ let mut offset = 0 ;
438+ let result = copy_all_but_last_lines (
439+ & mut writer,
440+ 1 ,
441+ & KillSignal :: default ( ) ,
442+ |buffer| {
443+ if offset >= data. len ( ) {
444+ return Ok ( 0 ) ;
445+ }
446+ let read_length = min ( buffer. len ( ) , data. len ( ) - offset) ;
447+ buffer[ ..read_length] . copy_from_slice ( & data[ offset..( offset + read_length) ] ) ;
448+ offset += read_length;
449+ Ok ( read_length)
450+ } ,
451+ ) ;
452+ drop ( writer) ;
453+ assert_eq ! ( reader_handle. await . unwrap( ) , "line1\n line2\n line3\n line4\n " ) ;
454+ assert_eq ! ( result. unwrap( ) . into_exit_code_and_handles( ) . 0 , 0 ) ;
455+ }
456+
457+ #[ tokio:: test]
458+ async fn copies_all_but_last_two_lines ( ) {
459+ let ( reader, mut writer) = pipe ( ) ;
460+ let reader_handle = reader. pipe_to_string_handle ( ) ;
461+ let data = b"line1\n line2\n line3\n line4\n line5\n " ;
462+ let mut offset = 0 ;
463+ let result = copy_all_but_last_lines (
464+ & mut writer,
465+ 2 ,
466+ & KillSignal :: default ( ) ,
467+ |buffer| {
468+ if offset >= data. len ( ) {
469+ return Ok ( 0 ) ;
470+ }
471+ let read_length = min ( buffer. len ( ) , data. len ( ) - offset) ;
472+ buffer[ ..read_length] . copy_from_slice ( & data[ offset..( offset + read_length) ] ) ;
473+ offset += read_length;
474+ Ok ( read_length)
475+ } ,
476+ ) ;
477+ drop ( writer) ;
478+ assert_eq ! ( reader_handle. await . unwrap( ) , "line1\n line2\n line3\n " ) ;
479+ assert_eq ! ( result. unwrap( ) . into_exit_code_and_handles( ) . 0 , 0 ) ;
480+ }
481+
482+ #[ tokio:: test]
483+ async fn copies_all_but_last_lines_when_skip_exceeds_total ( ) {
484+ let ( reader, mut writer) = pipe ( ) ;
485+ let reader_handle = reader. pipe_to_string_handle ( ) ;
486+ let data = b"line1\n line2\n " ;
487+ let mut offset = 0 ;
488+ let result = copy_all_but_last_lines (
489+ & mut writer,
490+ 5 ,
491+ & KillSignal :: default ( ) ,
492+ |buffer| {
493+ if offset >= data. len ( ) {
494+ return Ok ( 0 ) ;
495+ }
496+ let read_length = min ( buffer. len ( ) , data. len ( ) - offset) ;
497+ buffer[ ..read_length] . copy_from_slice ( & data[ offset..( offset + read_length) ] ) ;
498+ offset += read_length;
499+ Ok ( read_length)
500+ } ,
501+ ) ;
502+ drop ( writer) ;
503+ // when skip_last >= total_lines, output should be empty
504+ assert_eq ! ( reader_handle. await . unwrap( ) , "" ) ;
505+ assert_eq ! ( result. unwrap( ) . into_exit_code_and_handles( ) . 0 , 0 ) ;
506+ }
307507}
0 commit comments