mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
@@ -19,6 +19,7 @@ PROGS := \
|
||||
du \
|
||||
factor \
|
||||
false \
|
||||
fmt \
|
||||
fold \
|
||||
md5sum \
|
||||
mkdir \
|
||||
|
||||
@@ -134,7 +134,6 @@ To do
|
||||
- dircolors
|
||||
- expand (in progress)
|
||||
- expr
|
||||
- fmt
|
||||
- getlimits
|
||||
- install
|
||||
- join
|
||||
|
||||
+267
@@ -0,0 +1,267 @@
|
||||
#![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 {
|
||||
Ok(_) => (),
|
||||
Err(_) => unsafe { ::libc::exit(1) }
|
||||
}
|
||||
)
|
||||
)
|
||||
#[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 {
|
||||
crown : bool,
|
||||
tagged : bool,
|
||||
mail : bool,
|
||||
split_only : bool,
|
||||
use_prefix : bool,
|
||||
prefix : String,
|
||||
xprefix : bool,
|
||||
prefix_len : uint,
|
||||
use_anti_prefix : bool,
|
||||
anti_prefix : String,
|
||||
xanti_prefix : bool,
|
||||
uniform : bool,
|
||||
width : uint,
|
||||
goal : uint,
|
||||
tabwidth : uint,
|
||||
}
|
||||
|
||||
#[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, "{}\nTry `{} --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
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
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; }
|
||||
if matches.opt_present("s") { fmt_opts.split_only = true; fmt_opts.crown = false; fmt_opts.tagged = false; }
|
||||
if matches.opt_present("x") { fmt_opts.xprefix = true; }
|
||||
if matches.opt_present("X") { fmt_opts.xanti_prefix = true; }
|
||||
|
||||
match matches.opt_str("p") {
|
||||
Some(s) => {
|
||||
fmt_opts.prefix = s;
|
||||
fmt_opts.use_prefix = true;
|
||||
fmt_opts.prefix_len = fmt_opts.prefix.as_slice().char_len()
|
||||
}
|
||||
None => ()
|
||||
};
|
||||
|
||||
match matches.opt_str("P") {
|
||||
Some(s) => {
|
||||
fmt_opts.anti_prefix = s;
|
||||
fmt_opts.use_anti_prefix = true;
|
||||
}
|
||||
None => ()
|
||||
};
|
||||
|
||||
match matches.opt_str("w") {
|
||||
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 => ()
|
||||
};
|
||||
|
||||
match matches.opt_str("g") {
|
||||
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 => ()
|
||||
};
|
||||
|
||||
match matches.opt_str("T") {
|
||||
Some(s) => {
|
||||
fmt_opts.tabwidth =
|
||||
match from_str(s.as_slice()) {
|
||||
Some(t) => t,
|
||||
None => { crash!(1, "Invalid TABWIDTH specification: `{}'", s); }
|
||||
};
|
||||
}
|
||||
None => ()
|
||||
};
|
||||
|
||||
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()) {
|
||||
let mut fp =
|
||||
match open_file(i) {
|
||||
Err(e) => {
|
||||
show_warning!("{}: {}",i,e);
|
||||
continue;
|
||||
}
|
||||
Ok(f) => f
|
||||
};
|
||||
let mut pStream = ParagraphStream::new(&fmt_opts, &mut fp);
|
||||
for paraResult in pStream {
|
||||
match paraResult {
|
||||
Err(s) => silent_unwrap!(ostream.write(s.as_bytes())),
|
||||
Ok(para) => {
|
||||
// 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, ¶);
|
||||
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
|
||||
} else if !para.mail_header {
|
||||
// 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)) {
|
||||
Ok(f) => Ok(BufferedReader::new(box f as Box<Reader>)),
|
||||
Err(e) => return Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// break_simple implements the "tight" breaking algorithm: print words until
|
||||
// maxlength would be exceeded, then print a linebreak and indent and continue.
|
||||
// Note that any first line indent should already have been printed before
|
||||
// calling this function, and the length of said indent should be passed as
|
||||
// init_len
|
||||
pub fn break_simple<'a, T: Iterator<&'a str>>(s: &'a mut T, maxlen: uint, indent_str: &'a str, indent_len: uint, init_len: uint, uniform: bool, ostream: &mut Box<Writer>) -> uint {
|
||||
s.fold(init_len, |l, w| accum_words_simple(maxlen, indent_len, indent_str, ostream, uniform, l, w))
|
||||
}
|
||||
|
||||
fn accum_words_simple(maxlen: uint, indent_len: uint, indent_str: &str, ostream: &mut Box<Writer>, uniform: bool, l: uint, w: &str) -> uint {
|
||||
let wlen = w.len();
|
||||
let lnew =
|
||||
if l + wlen > maxlen {
|
||||
silent_unwrap!(ostream.write("\n".as_bytes()));
|
||||
silent_unwrap!(ostream.write(indent_str.as_bytes()));
|
||||
indent_len
|
||||
} else {
|
||||
l
|
||||
};
|
||||
|
||||
silent_unwrap!(ostream.write(w.as_bytes()));
|
||||
if uniform { silent_unwrap!(ostream.write(" ".as_bytes())); }
|
||||
lnew + wlen + 1
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user