Compare commits
No commits in common. "7b7feb5513990468b09a61c4848656ae83adfb98" and "d6d6df7298b5c49054e486ec744fb034f19d38a8" have entirely different histories.
7b7feb5513
...
d6d6df7298
2 changed files with 11 additions and 282 deletions
43
build.rs
43
build.rs
|
@ -16,48 +16,9 @@ fn main() {
|
||||||
.text([bold("sesh"), roman(" [options]")])
|
.text([bold("sesh"), roman(" [options]")])
|
||||||
.control("SH", ["DESCRIPTION"])
|
.control("SH", ["DESCRIPTION"])
|
||||||
.text([
|
.text([
|
||||||
bold("Sesh"),
|
bold("sesh"),
|
||||||
roman(
|
roman("is a shell designed to be as semantic to use as possible"),
|
||||||
" 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")
|
|
||||||
])
|
|
||||||
.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, \
|
|
||||||
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")
|
|
||||||
]
|
|
||||||
)
|
|
||||||
.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();
|
.render();
|
||||||
std::fs::write(
|
std::fs::write(
|
||||||
PathBuf::from(env::var_os("OUT_DIR").unwrap())
|
PathBuf::from(env::var_os("OUT_DIR").unwrap())
|
||||||
|
|
250
src/main.rs
250
src/main.rs
|
@ -4,13 +4,11 @@
|
||||||
#![feature(cfg_match)]
|
#![feature(cfg_match)]
|
||||||
#![feature(slice_concat_trait)]
|
#![feature(slice_concat_trait)]
|
||||||
#![feature(test)]
|
#![feature(test)]
|
||||||
#![feature(let_chains)]
|
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
ffi::OsStr,
|
ffi::OsStr,
|
||||||
fmt::Display,
|
fmt::Display,
|
||||||
io::{Read, Write},
|
io::{Read, Write},
|
||||||
os::fd::FromRawFd,
|
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::{Arc, RwLock},
|
sync::{Arc, RwLock},
|
||||||
};
|
};
|
||||||
|
@ -110,7 +108,7 @@ unsafe impl Sync for State {}
|
||||||
unsafe impl Send for State {}
|
unsafe impl Send for State {}
|
||||||
|
|
||||||
/// Split a statement.
|
/// Split a statement.
|
||||||
fn split_statement(statement: &str) -> Vec<Result<IndirectRes, &str>> {
|
fn split_statement(statement: &str) -> Vec<String> {
|
||||||
let mut out = vec![String::new()];
|
let mut out = vec![String::new()];
|
||||||
let mut i = 0usize;
|
let mut i = 0usize;
|
||||||
let mut in_str = (false, ' ');
|
let mut in_str = (false, ' ');
|
||||||
|
@ -166,98 +164,7 @@ fn split_statement(statement: &str) -> Vec<Result<IndirectRes, &str>> {
|
||||||
}
|
}
|
||||||
out.iter()
|
out.iter()
|
||||||
.map(|v| v.trim().to_string())
|
.map(|v| v.trim().to_string())
|
||||||
.map(|v| is_indirect(v))
|
.collect::<Vec<String>>()
|
||||||
.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
|
/// Removes comments from a statement
|
||||||
|
@ -360,46 +267,7 @@ fn eval(statement: &str, state: &mut State) {
|
||||||
let statements = split_statements(&substitute_vars(&statement, state.clone()));
|
let statements = split_statements(&substitute_vars(&statement, state.clone()));
|
||||||
|
|
||||||
for statement in statements {
|
for statement in statements {
|
||||||
let statement_split = split_statement(&statement);
|
let mut 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() {
|
if statement.is_empty() || statement_split[0].is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -407,13 +275,7 @@ fn eval(statement: &str, state: &mut State) {
|
||||||
|
|
||||||
for alias in &state.aliases {
|
for alias in &state.aliases {
|
||||||
if program_name == alias.name {
|
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() {
|
for (i, item) in to_split[1..].iter().enumerate() {
|
||||||
statement_split.insert(i + 1, (*item).clone());
|
statement_split.insert(i + 1, (*item).clone());
|
||||||
}
|
}
|
||||||
|
@ -427,9 +289,6 @@ fn eval(statement: &str, state: &mut State) {
|
||||||
let writer = raw_term.write().unwrap();
|
let writer = raw_term.write().unwrap();
|
||||||
let _ = writer.suspend_raw_mode();
|
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);
|
let status = builtin.1(statement_split, statement.to_string(), state);
|
||||||
garbage_collect_vars(state);
|
garbage_collect_vars(state);
|
||||||
if let Some(raw_term) = state.raw_term.clone() {
|
if let Some(raw_term) = state.raw_term.clone() {
|
||||||
|
@ -457,77 +316,11 @@ fn eval(statement: &str, state: &mut State) {
|
||||||
std::env::set_var(env.name.clone(), env.value.clone());
|
std::env::set_var(env.name.clone(), env.value.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut command = std::process::Command::new(program_name.clone());
|
match std::process::Command::new(program_name.clone())
|
||||||
command
|
|
||||||
.args(&statement_split[1..])
|
.args(&statement_split[1..])
|
||||||
.current_dir(state.working_dir.clone());
|
.current_dir(state.working_dir.clone())
|
||||||
for indirect in indirects {
|
.spawn()
|
||||||
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) => {
|
Ok(mut child) => {
|
||||||
for (i, var) in state.shell_env.clone().into_iter().enumerate() {
|
for (i, var) in state.shell_env.clone().into_iter().enumerate() {
|
||||||
if var.name == "STATUS" {
|
if var.name == "STATUS" {
|
||||||
|
@ -633,32 +426,7 @@ fn log_file(value: &str) {
|
||||||
|
|
||||||
#[allow(clippy::arc_with_non_send_sync)]
|
#[allow(clippy::arc_with_non_send_sync)]
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let mut options = Args::parse();
|
let options = Args::parse();
|
||||||
|
|
||||||
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");
|
|
||||||
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");
|
|
||||||
return Ok(());
|
|
||||||
} else {
|
|
||||||
let rc = rc.unwrap();
|
|
||||||
options.run_expr = rc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut state = State {
|
let mut state = State {
|
||||||
shell_env: Vec::new(),
|
shell_env: Vec::new(),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue