rakshit

I was reading the Rust Book and there was a mini-project to implement the grep command. I thought in similar lines and implemented a ls command.

The ls command should take the path as an argument and return the filename, its permissions, and the modified time.

To get the path as an argument, I used the clap crate as it simplifies parsing the arguments.

use clap::Parser;

#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct Args {
    /// Name of the person to greet
    #[clap(short, long, value_parser)]
    directory_name: String,
}

Afterwards, I implemented the function to get the filenames/directory names within the given path. I get the path and store it in the search variable. Using the read_dir function, I get all directories and files inside the given path. I pass the entry.path() to a function called get_name.

fn main() {
    let matches = Args::parse();
    let search = matches.directory_name;
    let entries = fs::read_dir(search).unwrap();
    for entry in entries {
        let entry = entry.unwrap();
        let name = get_name(entry.path());
    }
}

The get_name function takes the PathBuffer and returns a string which is the file name/directory name. The code for this part is pretty self-explanatory.

fn get_name(file: PathBuf) -> String {
    let path_name = file
        .into_os_string()
        .into_string()
        .unwrap_or("".to_string());
    let path_last = path_name.trim().split('/').last().unwrap_or("").to_string();
    path_last
}

For the file permissions, I extend the main function as following,

fn main() {
    let matches = Args::parse();
    let search = matches.directory_name;
    let entries = fs::read_dir(search).unwrap();
    for entry in entries {
        let entry = entry.unwrap();
        let name = get_name(entry.path());

        let metadata = entry.metadata().unwrap();
        let mode = metadata.permissions().mode();
        let permission_txt = get_permission_txt(&mode);
    }
}

The file/directory permissions are stored in the metadata of the entry item. I get the permissions and pass it to a get_permission_txt function, which converts the file permission from numeric representation to text.

fn get_permission_txt(mode: &u32) -> String {
    let mut permission_txt = String::new();

    let mode_oct = format!("{:o}", mode);
    let mode_int = mode_oct.parse::<i32>().unwrap() % 1000;
    let mod_file = mode_oct.parse::<i32>().unwrap() / 1000;

    let file_type = match mod_file {
        100 => "-",
        40 => "d",
        other => "",
    };
    permission_txt.push_str(file_type);

    let file_code = mode_int.to_string();
    for x in file_code.chars() {
        let code = match x {
            '0' => "-",
            '1' => "-x",
            '2' => "-w-",
            '3' => "-wx",
            '4' => "r-",
            '5' => "r-x",
            '6' => "rw-",
            '7' => "rwx",
            other => " ",
        };
        permission_txt.push_str(code);
    }
    permission_txt
}

The logic is to first convert the number from base 10 to base 8. Using the fact that the last three digits of the octal represent the permissions. Using the match feature along with a mutable string, I return the text representation.

The final part is to implement the modified time. For this, I use the chrono crate and this converts the given SystemTime struct to DateTime<Local>

fn main() {
    let matches = Args::parse();
    let search = matches.directory_name;
    let entries = fs::read_dir(search).unwrap();
    for entry in entries {
        let entry = entry.unwrap();
        let name = get_name(entry.path());

        let metadata = entry.metadata().unwrap();
        let mode = metadata.permissions().mode();
        let permission_txt = get_permission_txt(&mode);

        let modified = metadata.modified().unwrap();
        let modified_time: DateTime<Local> = DateTime::from(modified);
        println!(
            "{}, {}, {}",
            permission_txt,
            modified_time.format("%_d %b %H:%M").to_string(),
            name,
        );
    }
}

I simply get the modified time from the metadata and convert it to DateTime. Afterwards, I write a println! statement for each entry.

Overall the final code is as follows:

use chrono::{DateTime, Local};
use clap::Parser;
use std::fs;
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;

#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct Args {
    /// Name of the person to greet
    #[clap(short, long, value_parser)]
    directory_name: String,
}

fn get_permission_txt(mode: &u32) -> String {
    let mut permission_txt = String::new();

    let mode_oct = format!("{:o}", mode);
    let mode_int = mode_oct.parse::<i32>().unwrap() % 1000;
    let mod_file = mode_oct.parse::<i32>().unwrap() / 1000;

    let file_type = match mod_file {
        100 => "-",
        40 => "d",
        other => "",
    };
    permission_txt.push_str(file_type);

    let file_code = mode_int.to_string();
    for x in file_code.chars() {
        let code = match x {
            '0' => "-",
            '1' => "-x",
            '2' => "-w-",
            '3' => "-wx",
            '4' => "r-",
            '5' => "r-x",
            '6' => "rw-",
            '7' => "rwx",
            other => " ",
        };
        permission_txt.push_str(code);
    }

    permission_txt
}

fn get_name(file: PathBuf) -> String {
    let path_name = file
        .into_os_string()
        .into_string()
        .unwrap_or("".to_string());
    let path_last = path_name.trim().split('/').last().unwrap_or("").to_string();
    path_last
}

fn main() {
    let matches = Args::parse();
    let search = matches.directory_name;
    let entries = fs::read_dir(search).unwrap();
    for entry in entries {
        let entry = entry.unwrap();
        let metadata = entry.metadata().unwrap();
        let mode = metadata.permissions().mode();
        let permission_txt = get_permission_txt(&mode);
        let modified = metadata.modified().unwrap();
        let modified_time: DateTime<Local> = DateTime::from(modified);
        let name = get_name(entry.path());
        println!(
            "{}, {}, {}",
            permission_txt,
            modified_time.format("%_d %b %H:%M").to_string(),
            name,
        );
    }
}

That's all for today.