Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@ rustix = { version = "1.0.7", features = [
"termios",
"fs",
"stdio",
"time",
] }
sap = "0.0.5"
90 changes: 24 additions & 66 deletions src/bin/ls/main.rs
Original file line number Diff line number Diff line change
@@ -1,88 +1,46 @@
mod options;
mod settings;
pub(crate) mod options;
pub(crate) mod settings;
pub(crate) mod sorting;
pub(crate) mod traverse;

use puppyutils::Result;
use rustix::{
fs::{Dir, Mode, OFlags, open},
termios::tcgetwinsize,
};
use std::io::{self, BufWriter, stdout};
use rustix::termios::tcgetwinsize;
use traverse::Printer;

const CURRENT_DIR_PATH: &str = ".";
use std::io::stdout;

pub fn main() -> Result {
let mut stdout = stdout();
let winsize = get_win_size();
let _cfg = settings::parse_arguments(winsize.ws_col, &mut stdout)?;
let cfg = settings::parse_arguments(winsize.ws_col, &mut stdout)?;

let fd = open(
CURRENT_DIR_PATH,
OFlags::DIRECTORY | OFlags::RDONLY,
Mode::RUSR,
)?;
// let fd = open(
// cfg.directory(),
// OFlags::DIRECTORY | OFlags::RDONLY,
// Mode::RUSR,
// )?;

let dir = Dir::new(fd)?;
// let dir = Dir::new(fd)?;

// bad bad bad
// FIXME: do not allocate
let names = dir
.filter_map(Result::ok)
.map(|entry| entry.file_name().to_string_lossy().into_owned())
.filter(|entry| !entry.starts_with('.'))
.collect::<Vec<_>>();
// let names = dir
// .filter_map(Result::ok)
// .map(|entry| entry.file_name().to_string_lossy().into_owned())
// .filter(|entry| !entry.starts_with('.'))
// .collect::<Vec<_>>();

let mut stdout = BufWriter::new(stdout);
// let mut stdout = BufWriter::new(stdout);

print_all(names, &mut stdout)?;
// print_all(names, &mut stdout)?;

let printer = Printer::new(cfg, &mut stdout);

printer?.traverse()?;
Ok(())
}

fn get_win_size() -> rustix::termios::Winsize {
let stderr_fd = rustix::stdio::stderr();
tcgetwinsize(stderr_fd).expect("couldn't get terminal size")
}

// FIXME: This algorithm to print out lines is incredibly simplistic
// and slightly worse than the one used in GNU's ls.
fn print_all<O: io::Write>(cols: Vec<String>, stdout: &mut O) -> Result {
const MIN_COLUMN_WIDTH: u16 = 3;

let len = cols.len();
let stderr_fd = rustix::stdio::stderr();
let winsize = tcgetwinsize(stderr_fd).expect("couldn't get terminal size");

let max_idx = ((winsize.ws_col / 3) / MIN_COLUMN_WIDTH - 1) as usize;

let max_cols = if max_idx < len { max_idx } else { len };

print_into_columns(cols.iter().map(String::as_str), max_cols, stdout)
}

fn print_into_columns<I, O: io::Write>(iter: I, columns: usize, stdout: &mut O) -> Result
where
I: IntoIterator<Item: AsRef<str> + core::fmt::Display>,
{
let mut counter = 0;
for line in iter {
if counter == columns {
stdout.write_all(b"\n")?;
counter = 0;
}

if counter == columns - 1 {
stdout.write_all(line.as_ref().as_bytes())?;
} else {
stdout.write_all(line.as_ref().as_bytes())?;
stdout.write_all(b" ")?;
}

counter += 1;
}

// fixes the shell returning a "return symbol" at the end.
stdout.write_all(b"\n")?;
stdout.flush()?;

Ok(())
}
100 changes: 100 additions & 0 deletions src/bin/ls/options.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#![allow(dead_code, unused_variables)]

use std::{fmt::Display, num::NonZero};
#[repr(u8)]
#[derive(Copy, Clone)]
pub(crate) enum SortOrder {
None,
Name,
Expand Down Expand Up @@ -123,3 +126,100 @@ from_bytes! {
{Extension} => {b"extension"}
{Width} => {b"width"}
}

#[derive(Debug)]
pub(crate) enum SizeParseError<'a> {
TooLarge(&'a [u8]),
InvalidSuffix(&'a [u8]),
InvalidArgument(&'a [u8]),
}

impl Display for SizeParseError<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
todo!()
}
}

impl std::error::Error for SizeParseError<'_> {}

pub(crate) fn size_arg_to_multiplier(arg: &[u8]) -> Result<NonZero<u64>, SizeParseError<'_>> {
use core::str::from_utf8_unchecked;
let mut num_len = 0;

while arg[num_len].is_ascii_digit() {
num_len += 1;
}

if num_len == 0 {
let err = SizeParseError::InvalidArgument(arg);
return Err(err);
}

let digits = &arg[..num_len + 1];

// SAFETY:
//
// The above loop guarantees that
// the bytes in this subslice
// represent only ASCII digits.
let multiplier = unsafe {
from_utf8_unchecked(digits)
.parse::<u64>()
.expect("infallible")
};

if multiplier == 0 {
let err = SizeParseError::InvalidArgument(arg);
return Err(err);
}

let (base, shift) = match &arg[num_len..] {
b"K" | b"KiB" => (1024_u64, 1_u32),
b"M" | b"MiB" => (1024, 10),
b"G" | b"GiB" => (1024, 20),
b"T" | b"TiB" => (1024, 30),
b"P" | b"PiB" => (1024, 40),
b"E" | b"EiB" => (1024, 50),

b"KB" => (1024, 0),
b"MB" => (1000, 10),
b"GB" => (1000, 20),
b"TB" => (1000, 30),
b"PB" => (1000, 40),
b"EB" => (1000, 50),

b"" => return unsafe { Ok(NonZero::new_unchecked(multiplier)) },

b"Z" | b"ZiB" | b"Y" | b"YiB" | b"R" | b"RiB" | b"ZB" | b"RB" | b"YB" => {
let err = SizeParseError::TooLarge(arg);
return Err(err);
}

_ => return Err(SizeParseError::InvalidSuffix(arg)),
};

let unit = match base.checked_shl(shift) {
Some(val) => val,
None => {
let err = SizeParseError::TooLarge(arg);
return Err(err);
}
};

debug_assert!(
multiplier != 0 && unit != 0,
"this equation would end up as zero"
);

match unit.checked_mul(multiplier) {
None => Err(SizeParseError::TooLarge(arg)),

// SAFETY:
//
// The check above that `multiplier` is non-zero
// guarantees this value is non-zero
// the other part of the multiplication (`unit`) is
// always guaranteed to be above 0
Some(val) => unsafe { Ok(NonZero::new_unchecked(val)) },
}
}
63 changes: 46 additions & 17 deletions src/bin/ls/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ use super::options::*;
use puppyutils::{Result, cli_with_args};
use sap::Parser;
use std::io;
use std::num::NonZero;

const CURRENT_DIR_PATH: &str = ".";
const DEFAULT_BLOCK_SIZE: usize = 512;

fn needs_an_argument() -> ! {
Expand Down Expand Up @@ -72,6 +74,7 @@ pub(crate) fn parse_arguments<O: io::Write>(width: u16, out: &mut O) -> Result<L
blk_size: DEFAULT_BLOCK_SIZE,
format: Formatting::Horizontal, // default seems to be horizontal
width,
size_unit: None,
};

cli_with_args! {
Expand All @@ -93,10 +96,27 @@ pub(crate) fn parse_arguments<O: io::Write>(width: u16, out: &mut O) -> Result<L
}

Long("block-size") => {
if let Some(_arg) = args.value() {
// do the block-size
} else {
needs_an_argument();
// use crate::options::SizeParseError;

if let Some(arg) = args.value() {
match size_arg_to_multiplier(arg.as_bytes()) {
Err(err) => match err {
SizeParseError::TooLarge(_arg) => {
todo!()
}

SizeParseError::InvalidSuffix(_arg) => {
todo!()
}

SizeParseError::InvalidArgument(_arg) => {
todo!()
}

}

Ok(val) => settings.size_unit = Some(val)
}
}
}

Expand Down Expand Up @@ -401,42 +421,51 @@ pub(crate) fn parse_arguments<O: io::Write>(width: u16, out: &mut O) -> Result<L

pub(crate) struct LsConfig {
// order by which the entries will be sorted.
order: SortOrder,
pub order: SortOrder,

// time of timestamp used by ls
time_ty: TimeStampType,
pub time_ty: TimeStampType,

// settings that could be contained in bitflags.
flags: LsFlags,
pub flags: LsFlags,

// quoting style for names
quoting: QuotingStyle,
pub quoting: QuotingStyle,

// indicator style to append to entry names.
indicator: IndicatorStyle,
pub indicator: IndicatorStyle,

// specifies how and which symlinks
// should be dereferenced
deref: Dereference,
pub deref: Dereference,

// related to --color.
color: When,
pub color: When,

// related to --hyperlink
hyperlink_file_names: When,
pub hyperlink_file_names: When,

// related to --classify and -F
classify_files: When,
pub classify_files: When,

// directory to search through.
dir: Option<String>,
pub dir: Option<String>,

// block size
blk_size: usize,
pub blk_size: usize,

// formatting used
format: Formatting,
pub format: Formatting,

// line width.
width: u16,
pub width: u16,

// size from `--block-size`
pub size_unit: Option<NonZero<u64>>,
}

impl LsConfig {
pub(crate) fn directory(&self) -> &str {
self.dir.as_deref().unwrap_or(CURRENT_DIR_PATH)
}
}
Loading