Merge pull request #845 from peschkaj/master

Basic implementation of ls
This commit is contained in:
Heather 2016-05-01 00:56:11 +04:00
commit 1fafde8c27
7 changed files with 290 additions and 0 deletions

20
Cargo.lock generated
View file

@ -35,6 +35,7 @@ dependencies = [
"link 0.0.1",
"ln 0.0.1",
"logname 0.0.1",
"ls 0.0.1",
"memchr 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"mkdir 0.0.1",
"mkfifo 0.0.1",
@ -444,6 +445,16 @@ dependencies = [
"uucore 0.0.1",
]
[[package]]
name = "ls"
version = "0.0.1"
dependencies = [
"getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
"pretty-bytes 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"uucore 0.0.1",
]
[[package]]
name = "memchr"
version = "0.1.7"
@ -567,6 +578,15 @@ dependencies = [
"uucore 0.0.1",
]
[[package]]
name = "pretty-bytes"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "primal"
version = "0.2.3"

View file

@ -50,6 +50,7 @@ generic = [
"head",
"link",
"ln",
"ls",
"mkdir",
"nl",
"nproc",
@ -119,6 +120,7 @@ id = { optional=true, path="src/id" }
kill = { optional=true, path="src/kill" }
link = { optional=true, path="src/link" }
ln = { optional=true, path="src/ln" }
ls = { optional=true, path="src/ls" }
logname = { optional=true, path="src/logname" }
mkdir = { optional=true, path="src/mkdir" }
mktemp = { optional=true, path="src/mktemp" }

View file

@ -58,6 +58,7 @@ PROGS := \
head \
link \
ln \
ls \
mkdir \
mktemp \
nl \
@ -145,6 +146,7 @@ TEST_PROGS := \
head \
link \
ln \
ls \
mkdir \
mktemp \
mv \

18
src/ls/Cargo.toml Normal file
View file

@ -0,0 +1,18 @@
[package]
name = "ls"
version = "0.0.1"
authors = ["Jeremiah Peschka <jeremiah.peschka@gmail.com>"]
[lib]
name = "uu_ls"
path = "ls.rs"
[dependencies]
getopts = "*"
libc = "*"
uucore = { path="../uucore" }
pretty-bytes = "0.1.0"
[[bin]]
name = "ls"
path = "main.rs"

227
src/ls/ls.rs Normal file
View file

@ -0,0 +1,227 @@
#![crate_name = "uu_ls"]
/*
* This file is part of the uutils coreutils package.
*
* (c) Jeremiah Peschka <jeremiah.peschka@gmail.com>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/
extern crate getopts;
extern crate pretty_bytes;
use pretty_bytes::converter::convert;
#[macro_use]
extern crate uucore;
use getopts::Options;
use std::fs;
use std::fs::{ReadDir, DirEntry, FileType, Metadata};
use std::ffi::{OsString};
use std::path::Path;
use std::io::Write;
#[derive(Copy, Clone, PartialEq)]
enum Mode {
Help,
Version,
List
}
static NAME: &'static str = "ls";
static VERSION: &'static str = env!("CARGO_PKG_VERSION");
pub fn uumain(args: Vec<String>) -> i32 {
let mut opts = Options::new();
opts.optflag("", "help", "display this help and exit");
opts.optflag("", "version", "output version information and exit");
opts.optflag("a", "all", "Do not ignore hidden files (files with names that start with '.').");
opts.optflag("A", "almost-all", "In a directory, do not ignore all file names that start with '.', only ignore '.' and '..'.");
opts.optflag("B", "ignore-backups", "Ignore files that end with ~. Equivalent to using `--ignore='*~'` or `--ignore='.*~'.");
opts.optflag("d", "directory", "Only list the names of directories, rather than listing directory contents. This will not follow symbolic links unless one of `--dereference-command-line (-H)`, `--dereference (-L)`, or `--dereference-command-line-symlink-to-dir` is specified.");
opts.optflag("H", "dereference-command-line", "If a command line argument specifies a symbolic link, show information about the linked file rather than the link itself.");
opts.optflag("h", "human-readable", "Print human readable file sizes (e.g. 1K 234M 56G).");
let matches = match opts.parse(&args[1..]) {
Ok(m) => m,
Err(e) => {
show_error!("{}", e);
panic!()
},
};
let mode = if matches.opt_present("version") {
Mode::Version
} else if matches.opt_present("help") {
Mode::Help
} else {
Mode::List
};
match mode {
Mode::Version => version(),
Mode::Help => help(),
Mode::List => list(matches)
}
0
}
fn version() {
println!("{} {}", NAME, VERSION);
}
fn help() {
let msg = format!("{0} {1}\n\n\
Usage: {0} [OPTION]... DIRECTORY \n \
or: {0} [OPTION]... [FILE]... \n \
\n \
By default, ls will list the files and contents of any directories on \
the command line, expect that it will ignore files and directories \
whose names start with '.'. \n\
\n", NAME, VERSION);
println!("{}", msg);
}
fn list(options: getopts::Matches) {
let locs: Vec<String> = if options.free.is_empty() {
vec![String::from(".")]
} else {
options.free.iter().cloned().collect()
};
for loc in locs {
let p = Path::new(&loc);
if !p.exists() {
show_error!("Cannot find path '{}' because it does not exist.", loc);
panic!();
}
if p.is_dir() {
match fs::read_dir(p) {
Err(e) => {
show_error!("Cannot read directory '{}'. \n Reason: {}", loc, e);
panic!();
},
Ok(entries) => enter_directory(entries, &options),
};
}
if p.is_file() {
display_item(Path::new(p), &options)
}
}
}
fn enter_directory(contents: ReadDir, options: &getopts::Matches) {
for entry in contents {
let entry = match entry {
Err(err) => {
show_error!("{}", err);
panic!();
},
Ok(en) => en
};
// Currently have a DirEntry that we can believe in.
display_dir_entry(entry, options);
}
}
fn display_dir_entry(entry: DirEntry, options: &getopts::Matches) {
let md = match entry.metadata() {
Err(e) => {
show_error!("Unable to retrieve metadata for {}. \n Error: {}",
display_file_name(entry.file_name()), e);
panic!();
},
Ok(md) => md
};
println!(" {}{} {} somebody somegroup {: >9} {}",
display_file_type(entry.file_type()),
display_permissions(&md),
display_symlink_count(&md),
display_file_size(&md, options),
display_file_name(entry.file_name())
);
}
fn display_file_size(metadata: &Metadata, options: &getopts::Matches) -> String {
if options.opt_present("human-readable") {
convert(metadata.len() as f64)
} else {
metadata.len().to_string()
}
}
fn display_file_type(file_type: Result<FileType, std::io::Error>) -> String {
let file_type = match file_type {
Err(e) => {
show_error!("{}", e);
panic!()
},
Ok(ft) => ft
};
if file_type.is_dir() {
"d".to_string()
} else if file_type.is_symlink() {
"l".to_string()
} else {
"-".to_string()
}
}
fn display_file_name(name: OsString) -> String {
name.to_string_lossy().into_owned()
}
#[cfg(target_family = "windows")]
#[allow(unused_variables)]
fn display_symlink_count(metadata: &Metadata) -> String {
// Currently not sure of how to get this on Windows, so I'm punting.
// Git Bash looks like it may do the same thing.
String::from("1")
}
#[cfg(target_family = "unix")]
fn display_symlink_count(metadata: &Metadata) -> String {
use std::os::unix::fs::MetadataExt;
metadata.nlink().to_string()
}
#[cfg(target_family = "windows")]
#[allow(unused_variables)]
fn display_permissions(metadata: &Metadata) -> String {
String::from("---------")
}
#[cfg(target_family = "unix")]
fn display_permissions(metadata: &Metadata) -> String {
use std::os::unix::fs::PermissionsExt;
"xxxxxxxxx".to_string()
}
fn display_item(item: &Path, options: &getopts::Matches) {
// let fileType = item.file
// let mut fileMeta = String::new();
// fileMeta = fileMeta + if item.is_dir() {
// "d"
// } else if item.sy
// } else {
// "-"
// };
// println!("{}{}", displayString, item.display());
}

5
src/ls/main.rs Normal file
View file

@ -0,0 +1,5 @@
extern crate uu_ls;
fn main() {
std::process::exit(uu_ls::uumain(std::env::args().collect()));
}

16
tests/ls.rs Normal file
View file

@ -0,0 +1,16 @@
#[macro_use]
mod common;
use common::util::*;
static UTIL_NAME: &'static str = "ls";
#[test]
fn test_ls_ls() {
let (at, mut ucmd) = testing(UTIL_NAME);
let result = ucmd.run();
let exit_success = result.success;
assert_eq!(exit_success, true);
}