good progress; made man page

This commit is contained in:
Arthur Beck 2025-05-06 17:20:51 -05:00
parent 154281cfca
commit 7b7feb5513
2 changed files with 247 additions and 25 deletions

View file

@ -18,38 +18,46 @@ fn main() {
.text([
bold("Sesh"),
roman(
"is a shell designed to be as semantic to use as possible. It isn't completely compatible\
with sh(yet) however the point is for it to be easily usable and understandable by humans. It can\
" is a shell designed to be as semantic to use as possible. It isn't completely compatible \
with sh(yet) however the point is for it to be easily usable and understandable by humans. It can \
interpret commands from standard input or from a file."
),
])
.control("SH", ["OPTIONS"])
.text([
bold("-c, --run"), roman("\tIf this option is present, then commands are read from the\
argument provided to it and executed in a non-interactive environment(a shell will not be opened after\
they are done executing).\n"),
bold("-b, --before"), roman("\tIf this option is present, then commands are read from the\
argument provided to it and executed in an interactive environment(a shell WILL be opened after they\
bold("-c, --run "), roman("\tIf this option is present, then commands are read from the \
argument provided to it and executed in a non-interactive environment(a shell will not be opened after \
they are done executing).\n")
])
.text([
bold("-b, --before"), roman("\tIf this option is present, then commands are read from the \
argument provided to it and executed in an interactive environment(a shell WILL be opened after they \
are done executing).\n")
])
.control("SH", ["ARGUMENTS"])
.text(
[
roman("If arguments remain after option processing and neither -c nor -b have been supplied,\
roman("If arguments remain after option processing and neither -c nor -b have been supplied, \
the first argument is assumed to be the name of a shell file.")
]
)
.control("SH", ["FILES"])
.text(
[
bold("Sesh"), roman(" reads from and writes to a couple of files depending on the circumstances:\n\
.seshrc - Executed upon startup \n\
.sesh_history - Contains commands previously ran, one per line. Read upon startup in an interactive shell and\
written to after each command.\n\
Other files - Scripts may write to files via other methods, including outside tools. Scripts may be read from\
the path in the first argument of the shell after options.")
bold("Sesh"), roman(" reads from and writes to a couple of files depending on the circumstances:\n")
]
)
.text(
[bold(".seshrc"), roman(" - Executed upon startup\n")]
)
.text(
[bold(".sesh_history"), roman(" - Contains commands previously ran, one per line. \
Read upon startup in an interactive shell and written to after each command.\n")]
)
.text(
[bold("Other files"), roman(" - Scripts may write to files via other methods, \
including outside tools. Scripts may be read from the path in the first argument of the shell after options.")]
)
.render();
std::fs::write(
PathBuf::from(env::var_os("OUT_DIR").unwrap())

View file

@ -10,6 +10,7 @@ use std::{
ffi::OsStr,
fmt::Display,
io::{Read, Write},
os::fd::FromRawFd,
path::PathBuf,
sync::{Arc, RwLock},
};
@ -109,7 +110,7 @@ unsafe impl Sync for State {}
unsafe impl Send for State {}
/// Split a statement.
fn split_statement(statement: &str) -> Vec<String> {
fn split_statement(statement: &str) -> Vec<Result<IndirectRes, &str>> {
let mut out = vec![String::new()];
let mut i = 0usize;
let mut in_str = (false, ' ');
@ -165,7 +166,98 @@ fn split_statement(statement: &str) -> Vec<String> {
}
out.iter()
.map(|v| v.trim().to_string())
.collect::<Vec<String>>()
.map(|v| is_indirect(v))
.collect::<Vec<Result<IndirectRes, &str>>>()
}
/// An indirect to the value.
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
enum Indirect {
/// default to
#[default]
Default,
/// Redirect to stdout(not for stdin!)
Stdout,
/// Redirect to stderr(not for stdin!)
Stderr,
/// Redirect to/from a file descriptor
Fd(i32),
/// Redirect to/from a path
Path(PathBuf),
/// Redirect to the next statement
NextStatement,
/// Redirect from the previous statement
PrevStatement,
}
/// A result from [is_indirect]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
enum IndirectRes {
/// Isn't a indirect; the part
Statement(String),
/// TO stdin
Stdin(Indirect),
/// FROM stdout
Stdout(Indirect),
/// FROM stderr
Stderr(Indirect),
}
impl IndirectRes {
/// get the statement value or panic if not
fn unwrap_statement(self) -> String {
if let Self::Statement(v) = self {
v
} else {
panic!("IndirectRes is not statement");
}
}
/// return whether self is a statement
fn is_statement(&self) -> bool {
matches!(self, Self::Statement(_))
}
}
/// Return whether a statement is a indirect pointer and if it is what to.
fn is_indirect(statement: String) -> Result<IndirectRes, &'static str> {
fn is_indirect_inner(i: (&str, &str)) -> Indirect {
if i.1.is_empty() {
if i.0 == "0" {
Indirect::PrevStatement
} else {
Indirect::NextStatement
}
} else if i.0 == "0" {
if let Ok(n) = i.1.parse::<std::os::fd::RawFd>() {
Indirect::Fd(n)
} else {
Indirect::Path(PathBuf::from(i.1))
}
} else {
match i.1 {
"1" => Indirect::Stdout,
"2" => Indirect::Stderr,
v => {
if let Ok(n) = v.parse::<std::os::fd::RawFd>() {
Indirect::Fd(n)
} else {
Indirect::Path(PathBuf::from(v))
}
}
}
}
}
if let Some(i) = statement.split_once("@") {
match i.0 {
"0" => Ok(IndirectRes::Stdin(is_indirect_inner(i))),
"1" => Ok(IndirectRes::Stdout(is_indirect_inner(i))),
"2" => Ok(IndirectRes::Stderr(is_indirect_inner(i))),
_ => Err("unknown indirect from"),
}
} else {
Ok(IndirectRes::Statement(statement))
}
}
/// Removes comments from a statement
@ -268,7 +360,46 @@ fn eval(statement: &str, state: &mut State) {
let statements = split_statements(&substitute_vars(&statement, state.clone()));
for statement in statements {
let mut statement_split = split_statement(&statement);
let statement_split = split_statement(&statement);
if let Some(e) = statement_split.iter().find(|v| v.is_err()) {
println!("sesh: {}\r", e.clone().unwrap_err());
return;
}
let statement_split = statement_split
.iter()
.map(|v| v.clone().unwrap())
.collect::<Vec<IndirectRes>>();
if !statement_split[0].is_statement() {
println!("sesh: program name is indirect\r");
return;
}
let mut indirects = statement_split
.clone()
.into_iter()
.filter(|v| !v.is_statement())
.collect::<Vec<IndirectRes>>();
indirects.sort_by(|v1, v2| {
if matches!(v1, IndirectRes::Stderr(_)) && matches!(v2, IndirectRes::Stderr(_)) {
return std::cmp::Ordering::Equal;
}
if matches!(v1, IndirectRes::Stdout(_)) && matches!(v2, IndirectRes::Stdout(_)) {
return std::cmp::Ordering::Equal;
}
if matches!(v1, IndirectRes::Stdin(_)) && matches!(v2, IndirectRes::Stdin(_)) {
return std::cmp::Ordering::Equal;
}
v1.cmp(v2)
});
indirects.dedup();
let mut statement_split = statement_split
.into_iter()
.filter(|v| v.is_statement())
.map(|v| v.unwrap_statement())
.collect::<Vec<String>>();
if statement.is_empty() || statement_split[0].is_empty() {
continue;
}
@ -276,7 +407,13 @@ fn eval(statement: &str, state: &mut State) {
for alias in &state.aliases {
if program_name == alias.name {
let to_split = split_statement(&alias.to);
let to_split = split_statement(&alias.to)
.iter()
.filter_map(|v| v.clone().ok())
.filter(|v| v.is_statement())
.map(|v| v.unwrap_statement())
.collect::<Vec<String>>();
for (i, item) in to_split[1..].iter().enumerate() {
statement_split.insert(i + 1, (*item).clone());
}
@ -290,6 +427,9 @@ fn eval(statement: &str, state: &mut State) {
let writer = raw_term.write().unwrap();
let _ = writer.suspend_raw_mode();
}
if indirects.len() > 1 {
println!("sesh: warning: indirects ignored for builtin")
}
let status = builtin.1(statement_split, statement.to_string(), state);
garbage_collect_vars(state);
if let Some(raw_term) = state.raw_term.clone() {
@ -317,11 +457,77 @@ fn eval(statement: &str, state: &mut State) {
std::env::set_var(env.name.clone(), env.value.clone());
}
}
match std::process::Command::new(program_name.clone())
let mut command = std::process::Command::new(program_name.clone());
command
.args(&statement_split[1..])
.current_dir(state.working_dir.clone())
.spawn()
{
.current_dir(state.working_dir.clone());
for indirect in indirects {
match indirect {
IndirectRes::Statement(_) => (),
IndirectRes::Stderr(i) => match i {
Indirect::Default => (),
Indirect::Fd(fd) => {
command.stderr(unsafe { std::os::fd::OwnedFd::from_raw_fd(fd) });
}
Indirect::NextStatement => todo!(),
Indirect::Path(p) => {
command.stderr(
std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(p)
.unwrap(),
);
},
Indirect::PrevStatement => todo!(),
Indirect::Stderr => (),
Indirect::Stdout => {
command.stderr(std::io::stdout());
}
},
IndirectRes::Stdout(i) => match i {
Indirect::Default => (),
Indirect::Fd(fd) => {
command.stdout(unsafe { std::os::fd::OwnedFd::from_raw_fd(fd) });
}
Indirect::NextStatement => todo!(),
Indirect::Path(p) => {
command.stdout(
std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(p)
.unwrap(),
);
},
Indirect::PrevStatement => todo!(),
Indirect::Stderr => {
command.stdout(std::io::stderr());
},
Indirect::Stdout => ()
},
IndirectRes::Stdin(i) => match i {
Indirect::Default => (),
Indirect::Fd(fd) => {
command.stdin(unsafe { std::os::fd::OwnedFd::from_raw_fd(fd) });
}
Indirect::NextStatement => todo!(),
Indirect::Path(p) => {
command.stdin(
std::fs::OpenOptions::new()
.read(true)
.open(p)
.unwrap(),
);
},
Indirect::PrevStatement => todo!(),
Indirect::Stderr => (),
Indirect::Stdout => ()
}
}
}
match command.spawn() {
Ok(mut child) => {
for (i, var) in state.shell_env.clone().into_iter().enumerate() {
if var.name == "STATUS" {
@ -429,16 +635,24 @@ fn log_file(value: &str) {
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut options = Args::parse();
if let Some(filename) = std::env::args().next() && options.run_before.is_empty() && options.run_expr.is_empty() {
let mut args = std::env::args();
let _ = args.next();
if let Some(filename) = args.next()
&& options.run_before.is_empty()
&& options.run_expr.is_empty()
{
let rc = std::fs::read(filename.clone());
if rc.is_err() {
println!("sesh: reading {} failed: {}", filename, rc.unwrap_err());
println!("sesh: exiting")
println!("sesh: exiting");
return Ok(());
} else {
let rc = String::from_utf8(rc.unwrap());
if rc.is_err() {
println!("sesh: reading {} failed: not valid UTF-8", filename);
println!("sesh: exiting")
println!("sesh: exiting");
return Ok(());
} else {
let rc = rc.unwrap();
options.run_expr = rc;