<file_sep>[package]
name = "plato"
version = "0.1.0"
authors = ["Joshuad2uiuc <<EMAIL>>"]
edition = "2018"

[dependencies]
structopt = "0.2.10"
pathdiff = "0.1.0"
chrono = "0.4"
libc = "0.2"
users = "0.9"
groups = "0.1.1"
pretty-bytes = "0.2"
<file_sep>use std::env;
use std::path::PathBuf;
use std::fs;
use super::opts::Opts;
use std::vec::Vec;
use std::fs::ReadDir;
use std::collections::HashMap;
use std::io::{Error, ErrorKind};

use crate::lib::list_item;

// Gets the current directory of the user
pub fn get_current_directory() -> PathBuf {
    let path = env::current_dir();
    match path {
        Ok(p) => return p,
        Err(e) => panic!(e)
    }
}

// Reads the contents of the current directory
pub fn get_directory_contents(directory: &PathBuf) -> Result<ReadDir, Error> {
   let paths = fs::read_dir(directory);
   match paths {
       Ok(p) => Ok(p),
       Err(e) => Err(e)
   }
}

// Prints similar output to what ls does in bash
pub fn print_ls(items: Vec<list_item::ListItem>) {
  for item in items {
    print!("{}\t", item.name);
  }
  println!();
}

// Prints the extra file details similar to ls -l and ls -lh
pub fn print_ls_extra(items: Vec<list_item::ListItem>) {
  for item in items {
    println!("{}\t{}\t{}\t{}",
      item.owning_user.to_str().unwrap(),
      item.file_size,
      item.time_last_modified.format("%b %d %H:%M"),
      item.name);
  }
}

// Determines what information we are going to want to print out
pub fn print_items(items: Vec<list_item::ListItem>, is_human: bool, is_long: bool) {
  if is_long {
    print_ls_extra(items);
  }
  else {
    print_ls(items);
  }
}

// Uses the command line arguments to figure out what to print in response.
pub fn exec_ls(args: Opts) {
  let mut cmdflags = HashMap::new();
  let mut is_long = false;
  let mut is_hidden = false;
  let mut is_human = false;

  cmdflags.insert("long", args.long);
  is_long = args.long;
  cmdflags.insert("human", args.human);
  is_human = args.human;
  cmdflags.insert("hidden", args.hidden);

  match args.object {
    Some(x) => print_items(list_item::create_list_items(x, &cmdflags), is_human, is_long),
    None => print_items(list_item::create_list_items(get_current_directory(), &cmdflags), is_human, is_long)
  }
}
<file_sep>pub mod opts;
pub mod list;
pub mod list_item;
pub mod file;
<file_sep>use std::fs;
use fs::Metadata;
use std::os::unix::fs::MetadataExt;
use chrono::{DateTime, Local};
use libc::{S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR, S_IXGRP, S_IXOTH, S_IXUSR};
use users;
use std::ffi::OsString;
use pretty_bytes::converter::convert;
use pathdiff::diff_paths;
use std::path::PathBuf;

// Takes in the raw permission decimal value and returns the 
// the permissions string using the letter format. Grabs the 
// permissions for user, group, and other. The S_IRUSR constants
// are used for doing bitwise and operations to check if those bits
// are set in the file's permissions. 
fn parse_permissions(is_dir: bool, mode: u32) -> String {
    let user = triplet(mode, S_IRUSR, S_IWUSR, S_IXUSR);
    let group = triplet(mode, S_IRGRP, S_IWGRP, S_IXGRP);
    let other = triplet(mode, S_IROTH, S_IWOTH, S_IXOTH);
    if is_dir {
        ["d".to_string(), user, group, other].join("")
    }
    else {
        ["-".to_string(), user, group, other].join("")
    }
}

// Uses bitwise and operator to fetch what permissions are set for this object.
fn triplet(mode: u32, read: u32, write: u32, execute: u32) -> String {
    match (mode & u32::from(read), mode & u32::from(write), mode & u32::from(execute)) {
        (0, 0, 0) => "---",
        (_, 0, 0) => "r--",
        (0, _, 0) => "-w-",
        (0, 0, _) => "--x",
        (_, 0, _) => "r-x",
        (_, _, 0) => "rw-",
        (0, _, _) => "-wx",
        (_, _, _) => "rwx",
    }.to_string()
}

// Determines if a file is a hidden file or not.
pub fn is_hidden_file(file: &PathBuf, current_dir: &PathBuf) -> bool{
    let relative_path = diff_paths(&file, &current_dir).unwrap();
    let file = relative_path.to_str().unwrap();
    return file.starts_with(".")
}

// Get the permissions using the associate file metadata
pub fn get_permissions(meta: &Metadata) ->  String {
    return parse_permissions(meta.is_dir(), meta.mode());
}

// Get the name of the owning user of this file
pub fn get_owning_user(meta: &Metadata) -> OsString {
    let user = users::get_user_by_uid(meta.uid()).unwrap();
    return user.name().to_os_string();
}

// Get the time this file was last modified
pub fn get_time_last_modified(meta: &Metadata) -> DateTime<Local> {
    let modified: DateTime<Local> = DateTime::from(meta.modified().unwrap());
    modified
}

// Get the file size
pub fn get_file_size(meta: &Metadata) -> u64 {
    if meta.is_dir() {
        0
    }
    else {
        meta.size()
    }
}

// Get the file size but formatted to easily be read
// Uses the rust pretty bytes crate 
// https://github.com/banyan/rust-pretty-bytes
pub fn get_human_file_size(meta: &Metadata) -> String {
    let num = get_file_size(meta);
    return convert(num as f64);
}

<file_sep># Plato - A reduced version of the ls command

## CS 196 Project
For CS196 (<NAME>), we were tasked with creating Plato, a rust-built program that would behave in the same way as LS. This was a way for us to get comfortable writing CLI's with Rust, and to get involved with more of the low level things that Rust has to offer.

## Getting Started

Once you've cloned or forked the repo and downloaded it to your local machine, `cd` into the root directory and run:

```
cargo build
```
This will build the `target/` folder and allow you to use the plato script.

Example usage below.

## Usage
```Bash
davis-MacBook-Pro-6:plato davis$ ./target/debug/plato
Cargo.toml	target	Cargo.lock	README.md	src	
davis-MacBook-Pro-6:plato davis$ ./target/debug/plato -l
davis	247	 Aug 12 23:22	Cargo.toml
davis	0	 Sep 16 15:38	target
davis	12349	 Aug 12 23:29	Cargo.lock
davis	625	 Aug 19 00:27	README.md
davis	0	 Sep 17 15:47	src
davis-MacBook-Pro-6:plato davis$ ./target/debug/plato -lh
davis	247 B	 Aug 12 23:22	Cargo.toml
davis	0 B	 Sep 16 15:38	target
davis	12.35 kB	 Aug 12 23:29	Cargo.lock
davis	625 B	 Aug 19 00:27	README.md
davis	0 B	 Sep 17 15:47	src
davis-MacBook-Pro-6:plato davis$ ./target/debug/plato -lha
davis	6 B	 Aug 19 01:52	.hello
davis	247 B	 Aug 12 23:22	Cargo.toml
davis	0 B	 Sep 16 15:38	target
davis	12.35 kB	 Aug 12 23:29	Cargo.lock
davis	625 B	 Aug 19 00:27	README.md
davis	0 B	 Sep 16 17:34	.git
davis	0 B	 Sep 17 15:47	src
davis-MacBook-Pro-6:plato davis$ ./target/debug/plato -a
.hello	Cargo.toml	target	Cargo.lock	README.md	.git	src	
davis-MacBook-Pro-6:plato davis$ ./target/debug/plato src/
lib	main.rs	
davis-MacBook-Pro-6:plato davis$ 
```

<file_sep>use structopt::StructOpt;
use std::path::PathBuf;

#[derive(StructOpt, Debug)]
#[structopt(name = "basic")]
pub struct Opts {

    #[structopt(parse(from_os_str))]
    pub object: Option<PathBuf>,

    #[structopt(short = "a")]
    pub hidden: bool,

    #[structopt(short = "l")]
    pub long: bool,

    #[structopt(short = "h")]
    pub human: bool,
}
<file_sep>// Ref : https://doc.rust-lang.org/edition-guide/rust-2018/module-system/path-clarity.html
use structopt::StructOpt;

// Import the lib module
mod lib;

// Bring the Opts struct into scope
use lib::opts::Opts;
use lib::list;

// Grabs the command line arguments from the user
// executes the ls command.
fn main() {
    let args = Opts::from_args();
    list::exec_ls(args);
}
<file_sep>use std::fs;
use std::ffi::OsString;
use chrono::{DateTime, Local};
use super::file;
use std::collections::HashMap;
use std::path::PathBuf;
use std::vec::Vec;
use pathdiff::diff_paths;

use crate::lib::list;

#[derive(Debug)]
pub struct ListItem {
    pub owning_user: OsString,
    pub file_size: String,
    pub time_last_modified: DateTime<Local>,
    pub name: String
}

pub fn get_file_data(path: PathBuf, directory: &PathBuf, flags: &HashMap<&str, bool>) -> ListItem {
    let meta = fs::metadata(&path).unwrap();
    let owning_user = file::get_owning_user(&meta);
    let time_last_modified = file::get_time_last_modified(&meta);
    let relative_path = diff_paths(&path, &directory);
    let name = match relative_path {
        Some(f) => f.to_str().unwrap().to_string(),
        None => path.to_str().unwrap().to_string()
    };

    let file_size = if *flags.get("human").unwrap() && *flags.get("long").unwrap() {
        file::get_human_file_size(&meta)
    }
    else if *flags.get("long").unwrap() {
        file::get_file_size(&meta).to_string()
    }
    else {
        "".to_string()
    };

    let list_item = ListItem {
        owning_user: owning_user,
        file_size: file_size,
        time_last_modified: time_last_modified,
        name: name
    };

    return list_item;
}

// Uses the current directory and the associated metadata with a file to
// create list item structs for each file. Depending on the flags, the list item 
// content might be different for file size. 
pub fn create_list_items(base: PathBuf, flags: &HashMap<&str, bool>) -> Vec<ListItem> {
   
    let mut list_items = Vec::new();
    let result = list::get_directory_contents(&base);
    match result {
        Ok(dir_items) => {
            let hidden = flags.get("hidden").unwrap();
            for item in dir_items {
                let path = item.unwrap().path();
                if !hidden {
                    if file::is_hidden_file(&path, &base) {
                        continue;
                    }
                }
                let list_item = get_file_data(path, &base, flags);
                list_items.push(list_item);
            }
            return list_items;
        },
        Err(_e) => {
            let current_directory = list::get_current_directory();
            let list_item = get_file_data(base, &current_directory, flags);
            list_items.push(list_item);
            return list_items;
        }
    }
}
