2014-06-19 00:43:26 +00:00
#![ crate_id(name= " fmt " , vers= " 0.0.1 " , author= " kwantam " ) ]
/*
* This file is part of ` fmt ` from the uutils coreutils package .
*
* ( c ) kwantam < kwantam @ gmail . com >
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
* /
#![ feature(macro_rules) ]
extern crate core ;
extern crate getopts ;
extern crate libc ;
use std ::io ::{ BufferedReader , BufferedWriter , File , IoResult } ;
use std ::io ::stdio ::{ stdin_raw , stdout_raw , stdout } ;
use std ::os ;
use linebreak ::break_simple ;
use parasplit ::{ ParagraphStream , ParaWords } ;
#[ macro_export ]
macro_rules ! silent_unwrap (
( $exp :expr ) = > (
match $exp {
2014-06-19 02:23:48 +00:00
Ok ( _ ) = > ( ) ,
Err ( _ ) = > unsafe { ::libc ::exit ( 1 ) }
2014-06-19 00:43:26 +00:00
}
)
)
#[ path = " ../common/util.rs " ]
mod util ;
mod linebreak ;
mod parasplit ;
// program's NAME and VERSION are used for -V and -h
static NAME : & 'static str = " fmt " ;
static VERSION : & 'static str = " 0.0.1 " ;
struct FmtOptions {
2014-06-19 02:23:48 +00:00
crown : bool ,
tagged : bool ,
mail : bool ,
split_only : bool ,
use_prefix : bool ,
prefix : String ,
xprefix : bool ,
prefix_len : uint ,
2014-06-19 00:43:26 +00:00
use_anti_prefix : bool ,
2014-06-19 02:23:48 +00:00
anti_prefix : String ,
xanti_prefix : bool ,
uniform : bool ,
width : uint ,
goal : uint ,
tabwidth : uint ,
2014-06-19 00:43:26 +00:00
}
#[ allow(dead_code) ]
fn main ( ) { os ::set_exit_status ( uumain ( os ::args ( ) ) ) }
fn uumain ( args : Vec < String > ) -> int {
let opts = [
getopts ::optflag ( " c " , " crown-margin " , " First and second line of paragraph may have different indentations, in which case the first line's indentation is preserved, and each subsequent line's indentation matches the second line. " ) ,
getopts ::optflag ( " t " , " tagged-paragraph " , " Like -c, except that the first and second line of a paragraph *must* have different indentation or they are treated as separate paragraphs. " ) ,
getopts ::optflag ( " m " , " preserve-headers " , " Attempt to detect and preserve mail headers in the input. Be careful when combining this flag with -p. " ) ,
getopts ::optflag ( " s " , " split-only " , " Split lines only, do not reflow. " ) ,
getopts ::optflag ( " u " , " uniform-spacing " , " Insert exactly one space between words, and two between sentences. Sentence breaks in the input are detected as [?!.] followed by two spaces or a newline; other punctuation is not interpreted as a sentence break. " ) ,
getopts ::optopt ( " p " , " prefix " , " Reformat only lines beginning with PREFIX, reattaching PREFIX to reformatted lines. Unless -x is specified, leading whitespace will be ignored when matching PREFIX. " , " PREFIX " ) ,
getopts ::optopt ( " P " , " skip-prefix " , " Do not reformat lines beginning with PSKIP. Unless -X is specified, leading whitespace will be ignored when matching PSKIP " , " PSKIP " ) ,
getopts ::optflag ( " x " , " exact-prefix " , " PREFIX must match at the beginning of the line with no preceding whitespace. " ) ,
getopts ::optflag ( " X " , " exact-skip-prefix " , " PSKIP must match at the beginning of the line with no preceding whitespace. " ) ,
getopts ::optopt ( " w " , " width " , " Fill output lines up to a maximum of WIDTH columns, default 78. " , " WIDTH " ) ,
getopts ::optopt ( " g " , " goal " , " Goal width, default ~0.92*WIDTH. Must be less than WIDTH. " , " GOAL " ) ,
getopts ::optopt ( " T " , " tab-width " , " Treat tabs as TABWIDTH spaces for determining line length, default 8. Note that this is used only for calculating line lengths; tabs are preserved in the output. " , " TABWIDTH " ) ,
getopts ::optflag ( " V " , " version " , " Output version information and exit. " ) ,
getopts ::optflag ( " h " , " help " , " Display this help message and exit. " )
] ;
let matches = match getopts ::getopts ( args . tail ( ) , opts . as_slice ( ) ) {
Ok ( m ) = > m ,
Err ( f ) = > crash! ( 1 , " {} \n Try `{} --help' for more information. " , f , args . get ( 0 ) )
} ;
if matches . opt_present ( " h " ) {
print_usage ( args . get ( 0 ) . as_slice ( ) , opts . as_slice ( ) , " " ) ;
}
if matches . opt_present ( " V " ) | | matches . opt_present ( " h " ) {
println! ( " uutils {} v {} " , NAME , VERSION ) ;
return 0
}
2014-06-19 02:23:48 +00:00
let mut fmt_opts = FmtOptions {
crown : false ,
tagged : false ,
mail : false ,
uniform : false ,
split_only : false ,
use_prefix : false ,
prefix : String ::new ( ) ,
xprefix : false ,
prefix_len : 0 ,
use_anti_prefix : false ,
anti_prefix : String ::new ( ) ,
xanti_prefix : false ,
width : 78 ,
goal : 72 ,
tabwidth : 8 ,
} ;
2014-06-19 00:43:26 +00:00
if matches . opt_present ( " t " ) { fmt_opts . tagged = true ; }
if matches . opt_present ( " c " ) { fmt_opts . crown = true ; fmt_opts . tagged = false ; }
if matches . opt_present ( " m " ) { fmt_opts . mail = true ; }
if matches . opt_present ( " u " ) { fmt_opts . uniform = true ; }
2014-06-19 02:23:48 +00:00
if matches . opt_present ( " s " ) { fmt_opts . split_only = true ; fmt_opts . crown = false ; fmt_opts . tagged = false ; }
2014-06-19 00:43:26 +00:00
if matches . opt_present ( " x " ) { fmt_opts . xprefix = true ; }
if matches . opt_present ( " X " ) { fmt_opts . xanti_prefix = true ; }
match matches . opt_str ( " p " ) {
2014-06-19 02:23:48 +00:00
Some ( s ) = > {
fmt_opts . prefix = s ;
fmt_opts . use_prefix = true ;
fmt_opts . prefix_len = fmt_opts . prefix . as_slice ( ) . char_len ( )
}
None = > ( )
2014-06-19 00:43:26 +00:00
} ;
match matches . opt_str ( " P " ) {
2014-06-19 02:23:48 +00:00
Some ( s ) = > {
fmt_opts . anti_prefix = s ;
fmt_opts . use_anti_prefix = true ;
}
None = > ( )
2014-06-19 00:43:26 +00:00
} ;
match matches . opt_str ( " w " ) {
2014-06-19 02:23:48 +00:00
Some ( s ) = > {
fmt_opts . width =
match from_str ( s . as_slice ( ) ) {
Some ( t ) = > t ,
None = > { crash! ( 1 , " Invalid WIDTH specification: `{}' " , s ) ; }
} ;
fmt_opts . goal = std ::cmp ::min ( fmt_opts . width * 92 / 100 , fmt_opts . width - 4 ) ;
}
None = > ( )
2014-06-19 00:43:26 +00:00
} ;
match matches . opt_str ( " g " ) {
2014-06-19 02:23:48 +00:00
Some ( s ) = > {
fmt_opts . goal =
match from_str ( s . as_slice ( ) ) {
Some ( t ) = > t ,
None = > { crash! ( 1 , " Invalid GOAL specification: `{}' " , s ) ; }
} ;
if ! matches . opt_present ( " w " ) {
fmt_opts . width = std ::cmp ::max ( fmt_opts . goal * 100 / 92 , fmt_opts . goal + 4 ) ;
} else if fmt_opts . goal > fmt_opts . width {
crash! ( 1 , " GOAL cannot be greater than WIDTH. " ) ;
}
}
None = > ( )
2014-06-19 00:43:26 +00:00
} ;
match matches . opt_str ( " T " ) {
2014-06-19 02:23:48 +00:00
Some ( s ) = > {
fmt_opts . tabwidth =
match from_str ( s . as_slice ( ) ) {
Some ( t ) = > t ,
None = > { crash! ( 1 , " Invalid TABWIDTH specification: `{}' " , s ) ; }
} ;
}
None = > ( )
2014-06-19 00:43:26 +00:00
} ;
if fmt_opts . tabwidth < 1 {
fmt_opts . tabwidth = 1 ;
}
// immutable now
let fmt_opts = fmt_opts ;
let mut files = matches . free ;
if files . is_empty ( ) {
files . push ( " - " . to_string ( ) ) ;
}
let mut ostream = box BufferedWriter ::new ( stdout_raw ( ) ) as Box < Writer > ;
for i in files . iter ( ) . map ( | x | x . as_slice ( ) ) {
2014-06-19 02:23:48 +00:00
let mut fp =
match open_file ( i ) {
Err ( e ) = > {
show_warning! ( " {}: {} " , i , e ) ;
continue ;
}
Ok ( f ) = > f
} ;
2014-06-19 00:43:26 +00:00
let mut pStream = ParagraphStream ::new ( & fmt_opts , & mut fp ) ;
for paraResult in pStream {
match paraResult {
2014-06-19 02:23:48 +00:00
Err ( s ) = > silent_unwrap! ( ostream . write ( s . as_bytes ( ) ) ) ,
Ok ( para ) = > {
2014-06-19 00:43:26 +00:00
// indent
let pIndent = para . pfxind_str . clone ( ) . append ( fmt_opts . prefix . as_slice ( ) ) . append ( para . indent_str . as_slice ( ) ) ;
let pIndentLen = para . pfxind_len + fmt_opts . prefix_len + para . indent_len ;
// words
let pWords = ParaWords ::new ( & fmt_opts , & para ) ;
let mut pWords_words = pWords . words ( ) . map ( | & x | x ) ;
// print the init, if it exists, and get its length
let pInitLen =
if fmt_opts . crown | | fmt_opts . tagged {
// handle "init" portion
silent_unwrap! ( ostream . write ( para . init_str . as_bytes ( ) ) ) ;
para . init_len
2014-06-19 02:23:48 +00:00
} else if ! para . mail_header {
2014-06-19 00:43:26 +00:00
// for non-(crown, tagged) that's the same as a normal indent
silent_unwrap! ( ostream . write ( pIndent . as_bytes ( ) ) ) ;
pIndentLen
} else {
// except that mail headers get no indent at all
0
} ;
// does ths paragraph require uniform spacing?
let uniform = para . mail_header | | fmt_opts . uniform ;
break_simple ( & mut pWords_words , fmt_opts . width , pIndent . as_slice ( ) , pIndentLen , pInitLen , uniform , & mut ostream ) ;
silent_unwrap! ( ostream . write ( " \n " . as_bytes ( ) ) ) ;
}
}
}
// flush the output after each file
silent_unwrap! ( ostream . flush ( ) ) ;
}
0
}
fn print_usage ( arg0 : & str , opts : & [ getopts ::OptGroup ] , errmsg : & str ) {
break_simple ( & mut getopts ::short_usage ( arg0 , opts ) . as_slice ( ) . words ( ) , 64 , " " , 7 , 0 , true , & mut ( box stdout ( ) as Box < Writer > ) ) ;
println! ( " \n \n {} {} " , getopts ::usage ( " Reformat paragraphs from input files (or stdin) to stdout. " , opts ) , errmsg ) ;
}
// uniform interface for opening files
// since we don't need seeking
type FileOrStdReader = BufferedReader < Box < Reader > > ;
fn open_file ( filename : & str ) -> IoResult < FileOrStdReader > {
if filename = = " - " {
Ok ( BufferedReader ::new ( box stdin_raw ( ) as Box < Reader > ) )
} else {
match File ::open ( & Path ::new ( filename ) ) {
2014-06-19 02:23:48 +00:00
Ok ( f ) = > Ok ( BufferedReader ::new ( box f as Box < Reader > ) ) ,
Err ( e ) = > return Err ( e )
2014-06-19 00:43:26 +00:00
}
}
}