Implemented assignments, working on job control
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -9,7 +9,7 @@ shell.nix
|
||||
*~
|
||||
TODO.md
|
||||
rust-toolchain.toml
|
||||
*snapshot*
|
||||
*src_old
|
||||
|
||||
# cachix tmp file
|
||||
store-path-pre-build
|
||||
|
||||
168
Cargo.lock
generated
168
Cargo.lock
generated
@@ -29,12 +29,42 @@ dependencies = [
|
||||
"error-code",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "console"
|
||||
version = "0.15.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8"
|
||||
dependencies = [
|
||||
"encode_unicode",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diff"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
||||
|
||||
[[package]]
|
||||
name = "encode_unicode"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
|
||||
|
||||
[[package]]
|
||||
name = "endian-type"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.10"
|
||||
@@ -67,10 +97,20 @@ name = "fern"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"insta",
|
||||
"nix",
|
||||
"pretty_assertions",
|
||||
"rustyline",
|
||||
"serde",
|
||||
"serde_yaml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.11"
|
||||
@@ -80,12 +120,47 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "insta"
|
||||
version = "1.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50259abbaa67d11d2bcafc7ba1d094ed7a0c70e3ce893f0d0997f73558cb3084"
|
||||
dependencies = [
|
||||
"console",
|
||||
"linked-hash-map",
|
||||
"once_cell",
|
||||
"pin-project",
|
||||
"similar",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.169"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.15"
|
||||
@@ -125,6 +200,42 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
|
||||
dependencies = [
|
||||
"pin-project-internal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-internal"
|
||||
version = "1.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pretty_assertions"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
|
||||
dependencies = [
|
||||
"diff",
|
||||
"yansi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.93"
|
||||
@@ -200,6 +311,51 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.9.34+deprecated"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
"unsafe-libyaml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "similar"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.14.0"
|
||||
@@ -235,6 +391,12 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
|
||||
|
||||
[[package]]
|
||||
name = "unsafe-libyaml"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
@@ -322,3 +484,9 @@ name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
||||
|
||||
@@ -11,5 +11,13 @@ debug = true
|
||||
|
||||
[dependencies]
|
||||
bitflags = "2.8.0"
|
||||
insta = "1.42.2"
|
||||
nix = { version = "0.29.0", features = ["uio", "term", "user", "hostname", "fs", "default", "signal", "process", "event", "ioctl"] }
|
||||
pretty_assertions = "1.4.1"
|
||||
rustyline = { version = "15.0.0", features = [ "derive" ] }
|
||||
serde = { version = "1.0.219", features = [ "derive" ] }
|
||||
serde_yaml = "0.9.34"
|
||||
|
||||
[[bin]]
|
||||
name = "fern"
|
||||
path = "src/fern.rs"
|
||||
|
||||
30
src/builtin/echo.rs
Normal file
30
src/builtin/echo.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use crate::{libsh::error::ShResult, parse::{execute::prepare_argv, NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}};
|
||||
|
||||
pub fn echo(node: Node, io_stack: &mut IoStack) -> ShResult<()> {
|
||||
let NdRule::Command { assignments: _, argv } = node.class else {
|
||||
unreachable!()
|
||||
};
|
||||
assert!(!argv.is_empty());
|
||||
|
||||
for redir in node.redirs {
|
||||
io_stack.push_to_frame(redir);
|
||||
}
|
||||
let mut io_frame = io_stack.pop_frame();
|
||||
|
||||
io_frame.redirect()?;
|
||||
|
||||
let stdout = borrow_fd(STDOUT_FILENO);
|
||||
|
||||
let mut echo_output = prepare_argv(argv)
|
||||
.into_iter()
|
||||
.skip(1) // Skip 'echo'
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
|
||||
echo_output.push('\n');
|
||||
|
||||
write(stdout, echo_output.as_bytes())?;
|
||||
|
||||
io_frame.restore()?;
|
||||
Ok(())
|
||||
}
|
||||
5
src/builtin/mod.rs
Normal file
5
src/builtin/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
pub mod echo;
|
||||
|
||||
pub const BUILTINS: [&str;1] = [
|
||||
"echo"
|
||||
];
|
||||
21
src/fern.rs
21
src/fern.rs
@@ -5,19 +5,19 @@ pub mod procio;
|
||||
pub mod parse;
|
||||
pub mod expand;
|
||||
pub mod state;
|
||||
pub mod builtin;
|
||||
pub mod jobs;
|
||||
#[cfg(test)]
|
||||
pub mod tests;
|
||||
|
||||
use std::process::exit;
|
||||
|
||||
use parse::{execute::{get_pipe_stack, Dispatcher}, lex::{LexFlags, LexStream}, ParseResult, ParseStream};
|
||||
use state::write_vars;
|
||||
use parse::{execute::Dispatcher, lex::{LexFlags, LexStream}, ParseStream};
|
||||
use crate::prelude::*;
|
||||
|
||||
fn main() {
|
||||
loop {
|
||||
'main: loop {
|
||||
let input = prompt::read_line().unwrap();
|
||||
if input == "quit" { break };
|
||||
write_vars(|v| v.new_var("foo", "bar"));
|
||||
let start = Instant::now();
|
||||
|
||||
let mut tokens = vec![];
|
||||
for token in LexStream::new(&input, LexFlags::empty()) {
|
||||
@@ -31,13 +31,16 @@ fn main() {
|
||||
let mut nodes = vec![];
|
||||
for result in ParseStream::new(tokens) {
|
||||
match result {
|
||||
ParseResult::Error(e) => panic!("{}",e),
|
||||
ParseResult::Match(node) => nodes.push(node),
|
||||
_ => unreachable!()
|
||||
Ok(node) => nodes.push(node),
|
||||
Err(e) => {
|
||||
eprintln!("{:?}",e);
|
||||
continue 'main // Isn't rust cool
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut dispatcher = Dispatcher::new(nodes);
|
||||
dispatcher.begin_dispatch().unwrap();
|
||||
flog!(INFO, "elapsed: {:?}", start.elapsed());
|
||||
}
|
||||
}
|
||||
|
||||
390
src/jobs.rs
Normal file
390
src/jobs.rs
Normal file
@@ -0,0 +1,390 @@
|
||||
use crate::{libsh::{error::ShResult, term::{Style, Styled}}, prelude::*, procio::borrow_fd};
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct JobCmdFlags: u8 {
|
||||
const LONG = 0b0000_0001; // 0x01
|
||||
const PIDS = 0b0000_0010; // 0x02
|
||||
const NEW_ONLY = 0b0000_0100; // 0x04
|
||||
const RUNNING = 0b0000_1000; // 0x08
|
||||
const STOPPED = 0b0001_0000; // 0x10
|
||||
const INIT = 0b0010_0000; // 0x20
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DisplayWaitStatus(pub WtStat);
|
||||
|
||||
impl fmt::Display for DisplayWaitStatus {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match &self.0 {
|
||||
WtStat::Exited(_, code) => {
|
||||
match code {
|
||||
0 => write!(f, "done"),
|
||||
_ => write!(f, "failed: {}", code),
|
||||
}
|
||||
}
|
||||
WtStat::Signaled(_, signal, _) => {
|
||||
write!(f, "signaled: {:?}", signal)
|
||||
}
|
||||
WtStat::Stopped(_, signal) => {
|
||||
write!(f, "stopped: {:?}", signal)
|
||||
}
|
||||
WtStat::PtraceEvent(_, signal, _) => {
|
||||
write!(f, "ptrace event: {:?}", signal)
|
||||
}
|
||||
WtStat::PtraceSyscall(_) => {
|
||||
write!(f, "ptrace syscall")
|
||||
}
|
||||
WtStat::Continued(_) => {
|
||||
write!(f, "continued")
|
||||
}
|
||||
WtStat::StillAlive => {
|
||||
write!(f, "running")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub enum JobID {
|
||||
Pgid(Pid),
|
||||
Pid(Pid),
|
||||
TableID(usize),
|
||||
Command(String)
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct ChildProc {
|
||||
pgid: Pid,
|
||||
pid: Pid,
|
||||
command: Option<String>,
|
||||
stat: WtStat
|
||||
}
|
||||
|
||||
impl<'a> ChildProc {
|
||||
pub fn new(pid: Pid, command: Option<&str>, pgid: Option<Pid>) -> ShResult<Self> {
|
||||
let command = command.map(|str| str.to_string());
|
||||
let stat = if kill(pid,None).is_ok() {
|
||||
WtStat::StillAlive
|
||||
} else {
|
||||
WtStat::Exited(pid, 0)
|
||||
};
|
||||
let mut child = Self { pgid: pid, pid, command, stat };
|
||||
if let Some(pgid) = pgid {
|
||||
child.set_pgid(pgid).ok();
|
||||
}
|
||||
Ok(child)
|
||||
}
|
||||
pub fn pid(&self) -> Pid {
|
||||
self.pid
|
||||
}
|
||||
pub fn pgid(&self) -> Pid {
|
||||
self.pgid
|
||||
}
|
||||
pub fn cmd(&self) -> Option<&str> {
|
||||
self.command.as_ref().map(|cmd| cmd.as_str())
|
||||
}
|
||||
pub fn stat(&self) -> WtStat {
|
||||
self.stat
|
||||
}
|
||||
pub fn wait(&mut self, flags: Option<WtFlag>) -> Result<WtStat,Errno> {
|
||||
let result = waitpid(self.pid, flags);
|
||||
if let Ok(stat) = result {
|
||||
self.stat = stat
|
||||
}
|
||||
result
|
||||
}
|
||||
pub fn kill<T: Into<Option<Signal>>>(&self, sig: T) -> ShResult<()> {
|
||||
Ok(kill(self.pid, sig)?)
|
||||
}
|
||||
pub fn set_pgid(&mut self, pgid: Pid) -> ShResult<()> {
|
||||
setpgid(self.pid, pgid)?;
|
||||
self.pgid = pgid;
|
||||
Ok(())
|
||||
}
|
||||
pub fn set_stat(&mut self, stat: WtStat) {
|
||||
self.stat = stat
|
||||
}
|
||||
pub fn is_alive(&self) -> bool {
|
||||
self.stat == WtStat::StillAlive
|
||||
}
|
||||
pub fn is_stopped(&self) -> bool {
|
||||
matches!(self.stat,WtStat::Stopped(..))
|
||||
}
|
||||
pub fn exited(&self) -> bool {
|
||||
matches!(self.stat,WtStat::Exited(..))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct JobBldr {
|
||||
table_id: Option<usize>,
|
||||
pgid: Option<Pid>,
|
||||
children: Vec<ChildProc>
|
||||
}
|
||||
|
||||
impl Default for JobBldr {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl JobBldr {
|
||||
pub fn new() -> Self {
|
||||
Self { table_id: None, pgid: None, children: vec![] }
|
||||
}
|
||||
pub fn with_id(self, id: usize) -> Self {
|
||||
Self {
|
||||
table_id: Some(id),
|
||||
pgid: self.pgid,
|
||||
children: self.children
|
||||
}
|
||||
}
|
||||
pub fn with_pgid(self, pgid: Pid) -> Self {
|
||||
Self {
|
||||
table_id: self.table_id,
|
||||
pgid: Some(pgid),
|
||||
children: self.children
|
||||
}
|
||||
}
|
||||
pub fn with_children(self, children: Vec<ChildProc>) -> Self {
|
||||
Self {
|
||||
table_id: self.table_id,
|
||||
pgid: self.pgid,
|
||||
children
|
||||
}
|
||||
}
|
||||
pub fn build(self) -> Job {
|
||||
Job {
|
||||
table_id: self.table_id,
|
||||
pgid: self.pgid.unwrap_or(Pid::from_raw(0)),
|
||||
children: self.children
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct Job {
|
||||
table_id: Option<usize>,
|
||||
pgid: Pid,
|
||||
children: Vec<ChildProc>
|
||||
}
|
||||
|
||||
impl Job {
|
||||
pub fn set_tabid(&mut self, id: usize) {
|
||||
self.table_id = Some(id)
|
||||
}
|
||||
pub fn running(&self) -> bool {
|
||||
!self.children.iter().all(|chld| chld.exited())
|
||||
}
|
||||
pub fn tabid(&self) -> Option<usize> {
|
||||
self.table_id
|
||||
}
|
||||
pub fn pgid(&self) -> Pid {
|
||||
self.pgid
|
||||
}
|
||||
pub fn get_cmds(&self) -> Vec<&str> {
|
||||
let mut cmds = vec![];
|
||||
for child in &self.children {
|
||||
cmds.push(child.cmd().unwrap_or_default())
|
||||
}
|
||||
cmds
|
||||
}
|
||||
pub fn set_stats(&mut self, stat: WtStat) {
|
||||
for child in self.children.iter_mut() {
|
||||
child.set_stat(stat);
|
||||
}
|
||||
}
|
||||
pub fn get_stats(&self) -> Vec<WtStat> {
|
||||
self.children
|
||||
.iter()
|
||||
.map(|chld| chld.stat())
|
||||
.collect::<Vec<WtStat>>()
|
||||
}
|
||||
pub fn get_pids(&self) -> Vec<Pid> {
|
||||
self.children
|
||||
.iter()
|
||||
.map(|chld| chld.pid())
|
||||
.collect::<Vec<Pid>>()
|
||||
}
|
||||
pub fn children(&self) -> &[ChildProc] {
|
||||
&self.children
|
||||
}
|
||||
pub fn children_mut(&mut self) -> &mut Vec<ChildProc> {
|
||||
&mut self.children
|
||||
}
|
||||
pub fn killpg(&mut self, sig: Signal) -> ShResult<()> {
|
||||
let stat = match sig {
|
||||
Signal::SIGTSTP => WtStat::Stopped(self.pgid, Signal::SIGTSTP),
|
||||
Signal::SIGCONT => WtStat::Continued(self.pgid),
|
||||
Signal::SIGTERM => WtStat::Signaled(self.pgid, Signal::SIGTERM, false),
|
||||
_ => unimplemented!("{}",sig)
|
||||
};
|
||||
self.set_stats(stat);
|
||||
Ok(killpg(self.pgid, sig)?)
|
||||
}
|
||||
pub fn wait_pgrp<'a>(&mut self) -> ShResult<Vec<WtStat>> {
|
||||
let mut stats = vec![];
|
||||
for child in self.children.iter_mut() {
|
||||
let result = child.wait(Some(WtFlag::WUNTRACED));
|
||||
match result {
|
||||
Ok(stat) => {
|
||||
stats.push(stat);
|
||||
}
|
||||
Err(Errno::ECHILD) => break,
|
||||
Err(e) => return Err(e.into())
|
||||
}
|
||||
}
|
||||
Ok(stats)
|
||||
}
|
||||
pub fn update_by_id(&mut self, id: JobID, stat: WtStat) -> ShResult<()> {
|
||||
match id {
|
||||
JobID::Pid(pid) => {
|
||||
let query_result = self.children.iter_mut().find(|chld| chld.pid == pid);
|
||||
if let Some(child) = query_result {
|
||||
child.set_stat(stat);
|
||||
}
|
||||
}
|
||||
JobID::Command(cmd) => {
|
||||
let query_result = self.children
|
||||
.iter_mut()
|
||||
.find(|chld| chld
|
||||
.cmd()
|
||||
.is_some_and(|chld_cmd| chld_cmd.contains(&cmd))
|
||||
);
|
||||
if let Some(child) = query_result {
|
||||
child.set_stat(stat);
|
||||
}
|
||||
}
|
||||
JobID::TableID(tid) => {
|
||||
if self.table_id.is_some_and(|tblid| tblid == tid) {
|
||||
for child in self.children.iter_mut() {
|
||||
child.set_stat(stat);
|
||||
}
|
||||
}
|
||||
}
|
||||
JobID::Pgid(pgid) => {
|
||||
if pgid == self.pgid {
|
||||
for child in self.children.iter_mut() {
|
||||
child.set_stat(stat);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn display(&self, job_order: &[usize], flags: JobCmdFlags) -> String {
|
||||
let long = flags.contains(JobCmdFlags::LONG);
|
||||
let init = flags.contains(JobCmdFlags::INIT);
|
||||
let pids = flags.contains(JobCmdFlags::PIDS);
|
||||
|
||||
let current = job_order.last();
|
||||
let prev = if job_order.len() > 2 {
|
||||
job_order.get(job_order.len() - 2)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let id = self.table_id.unwrap();
|
||||
let symbol = if current == self.table_id.as_ref() {
|
||||
"+"
|
||||
} else if prev == self.table_id.as_ref() {
|
||||
"-"
|
||||
} else {
|
||||
" "
|
||||
};
|
||||
let padding_count = symbol.len() + id.to_string().len() + 3;
|
||||
let padding = " ".repeat(padding_count);
|
||||
|
||||
let mut output = format!("[{}]{}\t", id + 1, symbol);
|
||||
for (i, cmd) in self.get_cmds().iter().enumerate() {
|
||||
let pid = if pids || init {
|
||||
let mut pid = self.get_pids().get(i).unwrap().to_string();
|
||||
pid.push(' ');
|
||||
pid
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
let job_stat = *self.get_stats().get(i).unwrap();
|
||||
let fmt_stat = DisplayWaitStatus(job_stat).to_string();
|
||||
|
||||
let mut stat_line = if init {
|
||||
"".to_string()
|
||||
} else {
|
||||
fmt_stat.clone()
|
||||
};
|
||||
stat_line = format!("{}{} ",pid,stat_line);
|
||||
stat_line = format!("{} {}", stat_line, cmd);
|
||||
stat_line = match job_stat {
|
||||
WtStat::Stopped(..) | WtStat::Signaled(..) => stat_line.styled(Style::Magenta),
|
||||
WtStat::Exited(_, code) => {
|
||||
match code {
|
||||
0 => stat_line.styled(Style::Green),
|
||||
_ => stat_line.styled(Style::Red),
|
||||
}
|
||||
}
|
||||
_ => stat_line.styled(Style::Cyan)
|
||||
};
|
||||
if i != self.get_cmds().len() - 1 {
|
||||
stat_line = format!("{} |",stat_line);
|
||||
}
|
||||
|
||||
let stat_final = if long {
|
||||
format!(
|
||||
"{}{} {}",
|
||||
if i != 0 { &padding } else { "" },
|
||||
self.get_pids().get(i).unwrap(),
|
||||
stat_line
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"{}{}",
|
||||
if i != 0 { &padding } else { "" },
|
||||
stat_line
|
||||
)
|
||||
};
|
||||
output.push_str(&stat_final);
|
||||
output.push('\n');
|
||||
}
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
pub fn term_ctlr() -> Pid {
|
||||
tcgetpgrp(borrow_fd(0)).unwrap_or(getpgrp())
|
||||
}
|
||||
|
||||
pub fn attach_tty(pgid: Pid) -> ShResult<()> {
|
||||
// If we aren't attached to a terminal, the pgid already controls it, or the process group does not exist
|
||||
// Then return ok
|
||||
if !isatty(0).unwrap_or(false) || pgid == term_ctlr() || killpg(pgid, None).is_err() {
|
||||
return Ok(())
|
||||
}
|
||||
flog!(TRACE, "Attaching tty to pgid: {}",pgid);
|
||||
|
||||
if pgid == getpgrp() && term_ctlr() != getpgrp() {
|
||||
kill(term_ctlr(), Signal::SIGTTOU).ok();
|
||||
}
|
||||
|
||||
let mut new_mask = SigSet::empty();
|
||||
let mut mask_bkup = SigSet::empty();
|
||||
|
||||
new_mask.add(Signal::SIGTSTP);
|
||||
new_mask.add(Signal::SIGTTIN);
|
||||
new_mask.add(Signal::SIGTTOU);
|
||||
|
||||
pthread_sigmask(SigmaskHow::SIG_BLOCK, Some(&mut new_mask), Some(&mut mask_bkup))?;
|
||||
|
||||
let result = tcsetpgrp(borrow_fd(0), pgid);
|
||||
|
||||
pthread_sigmask(SigmaskHow::SIG_SETMASK, Some(&mut mask_bkup), Some(&mut new_mask))?;
|
||||
|
||||
match result {
|
||||
Ok(_) => return Ok(()),
|
||||
Err(e) => {
|
||||
flog!(ERROR, "error while switching term control: {}",e);
|
||||
tcsetpgrp(borrow_fd(0), getpgrp())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,40 @@
|
||||
use std::{fmt::Display, str::FromStr};
|
||||
use std::{fmt::Display, ops::Range, str::FromStr};
|
||||
|
||||
use crate::{parse::lex::Span, prelude::*};
|
||||
|
||||
pub type ShResult<'s,T> = Result<T,ShErr<'s>>;
|
||||
pub type ShResult<T> = Result<T,ShErr>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ShErr<'s> {
|
||||
Simple { kind: ShErrKind, msg: String },
|
||||
Full { kind: ShErrKind, msg: String, span: Span<'s> }
|
||||
pub struct ErrSpan {
|
||||
range: Range<usize>,
|
||||
source: String
|
||||
}
|
||||
|
||||
impl<'s> ShErr<'s> {
|
||||
impl<'s> From<Span<'s>> for ErrSpan {
|
||||
fn from(value: Span<'s>) -> Self {
|
||||
let range = value.range();
|
||||
let source = value.get_source().to_string();
|
||||
Self { range, source }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ShErr {
|
||||
Simple { kind: ShErrKind, msg: String },
|
||||
Full { kind: ShErrKind, msg: String, span: ErrSpan }
|
||||
}
|
||||
|
||||
impl<'s> ShErr {
|
||||
pub fn simple(kind: ShErrKind, msg: impl Into<String>) -> Self {
|
||||
let msg = msg.into();
|
||||
Self::Simple { kind, msg }
|
||||
}
|
||||
pub fn full(kind: ShErrKind, msg: impl Into<String>, span: Span<'s>) -> Self {
|
||||
let msg = msg.into();
|
||||
let span = span.into();
|
||||
Self::Full { kind, msg, span }
|
||||
}
|
||||
pub fn unpack(self) -> (ShErrKind,String,Option<Span<'s>>) {
|
||||
pub fn unpack(self) -> (ShErrKind,String,Option<ErrSpan>) {
|
||||
match self {
|
||||
ShErr::Simple { kind, msg } => (kind,msg,None),
|
||||
ShErr::Full { kind, msg, span } => (kind,msg,Some(span))
|
||||
@@ -27,11 +42,12 @@ impl<'s> ShErr<'s> {
|
||||
}
|
||||
pub fn with_span(sherr: ShErr, span: Span<'s>) -> Self {
|
||||
let (kind,msg,_) = sherr.unpack();
|
||||
let span = span.into();
|
||||
Self::Full { kind, msg, span }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> Display for ShErr<'s> {
|
||||
impl Display for ShErr {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Simple { msg, kind: _ } => writeln!(f, "{}", msg),
|
||||
@@ -40,26 +56,26 @@ impl<'s> Display for ShErr<'s> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> From<std::io::Error> for ShErr<'s> {
|
||||
impl From<std::io::Error> for ShErr {
|
||||
fn from(_: std::io::Error) -> Self {
|
||||
let msg = std::io::Error::last_os_error();
|
||||
ShErr::simple(ShErrKind::IoErr, msg.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> From<std::env::VarError> for ShErr<'s> {
|
||||
impl From<std::env::VarError> for ShErr {
|
||||
fn from(value: std::env::VarError) -> Self {
|
||||
ShErr::simple(ShErrKind::InternalErr, &value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> From<rustyline::error::ReadlineError> for ShErr<'s> {
|
||||
impl From<rustyline::error::ReadlineError> for ShErr {
|
||||
fn from(value: rustyline::error::ReadlineError) -> Self {
|
||||
ShErr::simple(ShErrKind::ParseErr, &value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> From<Errno> for ShErr<'s> {
|
||||
impl From<Errno> for ShErr {
|
||||
fn from(value: Errno) -> Self {
|
||||
ShErr::simple(ShErrKind::Errno, &value.to_string())
|
||||
}
|
||||
|
||||
144
src/libsh/flog.rs
Normal file
144
src/libsh/flog.rs
Normal file
@@ -0,0 +1,144 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use super::term::{Style, Styled};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, PartialOrd, Ord, Eq , Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum FernLogLevel {
|
||||
NONE = 0,
|
||||
ERROR = 1,
|
||||
WARN = 2,
|
||||
INFO = 3,
|
||||
DEBUG = 4,
|
||||
TRACE = 5
|
||||
}
|
||||
|
||||
impl Display for FernLogLevel {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
use FernLogLevel::*;
|
||||
match self {
|
||||
ERROR => write!(f,"{}","ERROR".styled(Style::Red | Style::Bold)),
|
||||
WARN => write!(f,"{}","WARN".styled(Style::Yellow | Style::Bold)),
|
||||
INFO => write!(f,"{}","INFO".styled(Style::Green | Style::Bold)),
|
||||
DEBUG => write!(f,"{}","DEBUG".styled(Style::Magenta | Style::Bold)),
|
||||
TRACE => write!(f,"{}","TRACE".styled(Style::Blue | Style::Bold)),
|
||||
NONE => write!(f,"")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn log_level() -> FernLogLevel {
|
||||
use FernLogLevel::*;
|
||||
let level = std::env::var("FERN_LOG_LEVEL").unwrap_or_default();
|
||||
match level.to_lowercase().as_str() {
|
||||
"error" => ERROR,
|
||||
"warn" => WARN,
|
||||
"info" => INFO,
|
||||
"debug" => DEBUG,
|
||||
"trace" => TRACE,
|
||||
_ => NONE
|
||||
}
|
||||
}
|
||||
|
||||
/// A structured logging macro designed for `fern`.
|
||||
///
|
||||
/// `flog!` was implemented because `rustyline` uses `env_logger`, which clutters the debug output.
|
||||
/// This macro prints log messages in a structured format, including the log level, filename, and line number.
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// The macro supports three types of arguments:
|
||||
///
|
||||
/// ## 1. **Formatted Messages**
|
||||
/// Similar to `println!` or `format!`, allows embedding values inside a formatted string.
|
||||
///
|
||||
/// ```rust
|
||||
/// flog!(ERROR, "foo is {}", foo);
|
||||
/// ```
|
||||
/// **Output:**
|
||||
/// ```plaintext
|
||||
/// [ERROR][file.rs:10] foo is <value of foo>
|
||||
/// ```
|
||||
///
|
||||
/// ## 2. **Literals**
|
||||
/// Directly prints each literal argument as a separate line.
|
||||
///
|
||||
/// ```rust
|
||||
/// flog!(WARN, "foo", "bar");
|
||||
/// ```
|
||||
/// **Output:**
|
||||
/// ```plaintext
|
||||
/// [WARN][file.rs:10] foo
|
||||
/// [WARN][file.rs:10] bar
|
||||
/// ```
|
||||
///
|
||||
/// ## 3. **Expressions**
|
||||
/// Logs the evaluated result of each given expression, displaying both the expression and its value.
|
||||
///
|
||||
/// ```rust
|
||||
/// flog!(INFO, 1.min(2));
|
||||
/// ```
|
||||
/// **Output:**
|
||||
/// ```plaintext
|
||||
/// [INFO][file.rs:10] 1
|
||||
/// ```
|
||||
///
|
||||
/// # Considerations
|
||||
/// - This macro uses `eprintln!()` internally, so its formatting rules must be followed.
|
||||
/// - **Literals and formatted messages** require arguments that implement [`std::fmt::Display`].
|
||||
/// - **Expressions** require arguments that implement [`std::fmt::Debug`].
|
||||
#[macro_export]
|
||||
macro_rules! flog {
|
||||
($level:path, $fmt:literal, $($args:expr),+ $(,)?) => {{
|
||||
use $crate::libsh::flog::log_level;
|
||||
use $crate::libsh::term::Styled;
|
||||
use $crate::libsh::term::Style;
|
||||
|
||||
if $level <= log_level() {
|
||||
let file = file!().styled(Style::Cyan);
|
||||
let line = line!().to_string().styled(Style::Cyan);
|
||||
|
||||
eprintln!(
|
||||
"[{}][{}:{}] {}",
|
||||
$level, file, line, format!($fmt, $($args),+)
|
||||
);
|
||||
}
|
||||
}};
|
||||
|
||||
($level:path, $($val:expr),+ $(,)?) => {{
|
||||
use $crate::libsh::flog::log_level;
|
||||
use $crate::libsh::term::Styled;
|
||||
use $crate::libsh::term::Style;
|
||||
|
||||
if $level <= log_level() {
|
||||
let file = file!().styled(Style::Cyan);
|
||||
let line = line!().to_string().styled(Style::Cyan);
|
||||
|
||||
$(
|
||||
let val_name = stringify!($val);
|
||||
eprintln!(
|
||||
"[{}][{}:{}] {} = {:#?}",
|
||||
$level, file, line, val_name, &$val
|
||||
);
|
||||
)+
|
||||
}
|
||||
}};
|
||||
|
||||
($level:path, $($lit:literal),+ $(,)?) => {{
|
||||
use $crate::libsh::flog::log_level;
|
||||
use $crate::libsh::term::Styled;
|
||||
use $crate::libsh::term::Style;
|
||||
|
||||
if $level <= log_level() {
|
||||
let file = file!().styled(Style::Cyan);
|
||||
let line = line!().to_string().styled(Style::Cyan);
|
||||
|
||||
$(
|
||||
eprintln!(
|
||||
"[{}][{}:{}] {}",
|
||||
$level, file, line, $lit
|
||||
);
|
||||
)+
|
||||
}
|
||||
}};
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod error;
|
||||
pub mod term;
|
||||
pub mod flog;
|
||||
|
||||
@@ -2,9 +2,14 @@ use std::collections::VecDeque;
|
||||
|
||||
use nix::sys::wait::WaitPidFlag;
|
||||
|
||||
use crate::{libsh::error::ShResult, prelude::*, procio::{IoFrame, IoPipe, IoStack}, state};
|
||||
use crate::{builtin::echo::echo, libsh::error::ShResult, prelude::*, procio::{IoFrame, IoPipe, IoStack}, state::{self, write_vars}};
|
||||
|
||||
use super::{lex::Tk, ConjunctNode, ConjunctOp, NdRule, Node, Redir, RedirType};
|
||||
use super::{lex::{Tk, TkFlags}, AssignKind, ConjunctNode, ConjunctOp, NdRule, Node, Redir, RedirType};
|
||||
|
||||
pub enum AssignBehavior {
|
||||
Export,
|
||||
Set
|
||||
}
|
||||
|
||||
/// Arguments to the execvpe function
|
||||
pub struct ExecArgs {
|
||||
@@ -14,8 +19,9 @@ pub struct ExecArgs {
|
||||
}
|
||||
|
||||
impl ExecArgs {
|
||||
pub fn new(argv: Vec<String>) -> Self {
|
||||
pub fn new(argv: Vec<Tk>) -> Self {
|
||||
assert!(!argv.is_empty());
|
||||
let argv = prepare_argv(argv);
|
||||
let cmd = Self::get_cmd(&argv);
|
||||
let argv = Self::get_argv(argv);
|
||||
let envp = Self::get_envp();
|
||||
@@ -42,22 +48,33 @@ impl<'t> Dispatcher<'t> {
|
||||
let nodes = VecDeque::from(nodes);
|
||||
Self { nodes, io_stack: IoStack::new() }
|
||||
}
|
||||
pub fn begin_dispatch(&mut self) -> ShResult<'t,()> {
|
||||
pub fn begin_dispatch(&mut self) -> ShResult<()> {
|
||||
flog!(TRACE, "beginning dispatch");
|
||||
while let Some(list) = self.nodes.pop_front() {
|
||||
self.dispatch_node(list)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn dispatch_node(&mut self, node: Node<'t>) -> ShResult<'t,()> {
|
||||
pub fn dispatch_node(&mut self, node: Node<'t>) -> ShResult<()> {
|
||||
match node.class {
|
||||
NdRule::CmdList {..} => self.exec_conjunction(node)?,
|
||||
NdRule::Pipeline {..} => self.exec_pipeline(node)?,
|
||||
NdRule::Command {..} => self.exec_cmd(node)?,
|
||||
NdRule::Command {..} => self.dispatch_cmd(node)?,
|
||||
_ => unreachable!()
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn exec_conjunction(&mut self, conjunction: Node<'t>) -> ShResult<'t,()> {
|
||||
pub fn dispatch_cmd(&mut self, node: Node<'t>) -> ShResult<()> {
|
||||
let Some(cmd) = node.get_command() else {
|
||||
return self.exec_cmd(node)
|
||||
};
|
||||
if cmd.flags.contains(TkFlags::BUILTIN) {
|
||||
self.exec_builtin(node)
|
||||
} else {
|
||||
self.exec_cmd(node)
|
||||
}
|
||||
}
|
||||
pub fn exec_conjunction(&mut self, conjunction: Node<'t>) -> ShResult<()> {
|
||||
let NdRule::CmdList { elements } = conjunction.class else {
|
||||
unreachable!()
|
||||
};
|
||||
@@ -76,7 +93,7 @@ impl<'t> Dispatcher<'t> {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn exec_pipeline(&mut self, pipeline: Node<'t>) -> ShResult<'t,()> {
|
||||
pub fn exec_pipeline(&mut self, pipeline: Node<'t>) -> ShResult<()> {
|
||||
let NdRule::Pipeline { cmds, pipe_err } = pipeline.class else {
|
||||
unreachable!()
|
||||
};
|
||||
@@ -96,15 +113,45 @@ impl<'t> Dispatcher<'t> {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn exec_cmd(&mut self, cmd: Node<'t>) -> ShResult<'t,()> {
|
||||
pub fn exec_builtin(&mut self, mut cmd: Node<'t>) -> ShResult<()> {
|
||||
let NdRule::Command { ref mut assignments, argv } = &mut cmd.class else {
|
||||
unreachable!()
|
||||
};
|
||||
let env_vars_to_unset = self.set_assignments(mem::take(assignments), AssignBehavior::Export);
|
||||
let cmd_raw = cmd.get_command().unwrap();
|
||||
flog!(TRACE, "doing builtin");
|
||||
let result = match cmd_raw.span.as_str() {
|
||||
"echo" => echo(cmd, &mut self.io_stack),
|
||||
_ => unimplemented!("Have not yet added support for builtin '{}'", cmd_raw.span.as_str())
|
||||
};
|
||||
|
||||
for var in env_vars_to_unset {
|
||||
env::set_var(&var, "");
|
||||
}
|
||||
|
||||
Ok(result?)
|
||||
}
|
||||
pub fn exec_cmd(&mut self, cmd: Node<'t>) -> ShResult<()> {
|
||||
let NdRule::Command { assignments, argv } = cmd.class else {
|
||||
unreachable!()
|
||||
};
|
||||
let mut env_vars_to_unset = vec![];
|
||||
if !assignments.is_empty() {
|
||||
let assign_behavior = if argv.is_empty() {
|
||||
AssignBehavior::Set
|
||||
} else {
|
||||
AssignBehavior::Export
|
||||
};
|
||||
env_vars_to_unset = self.set_assignments(assignments, assign_behavior);
|
||||
}
|
||||
for redir in cmd.redirs {
|
||||
self.io_stack.push_to_frame(redir);
|
||||
}
|
||||
if argv.is_empty() {
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
let exec_args = ExecArgs::new(prepare_argv(argv));
|
||||
let exec_args = ExecArgs::new(argv);
|
||||
let io_frame = self.io_stack.pop_frame();
|
||||
run_fork(
|
||||
io_frame,
|
||||
@@ -113,8 +160,51 @@ impl<'t> Dispatcher<'t> {
|
||||
def_parent_action
|
||||
)?;
|
||||
|
||||
for var in env_vars_to_unset {
|
||||
std::env::set_var(&var, "");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
pub fn set_assignments(&self, assigns: Vec<Node<'t>>, behavior: AssignBehavior) -> Vec<String> {
|
||||
let mut new_env_vars = vec![];
|
||||
match behavior {
|
||||
AssignBehavior::Export => {
|
||||
for assign in assigns {
|
||||
let NdRule::Assignment { kind, var, val } = assign.class else {
|
||||
unreachable!()
|
||||
};
|
||||
let var = var.span.as_str();
|
||||
let val = val.span.as_str();
|
||||
match kind {
|
||||
AssignKind::Eq => std::env::set_var(var, val),
|
||||
AssignKind::PlusEq => todo!(),
|
||||
AssignKind::MinusEq => todo!(),
|
||||
AssignKind::MultEq => todo!(),
|
||||
AssignKind::DivEq => todo!(),
|
||||
}
|
||||
new_env_vars.push(var.to_string());
|
||||
}
|
||||
}
|
||||
AssignBehavior::Set => {
|
||||
for assign in assigns {
|
||||
let NdRule::Assignment { kind, var, val } = assign.class else {
|
||||
unreachable!()
|
||||
};
|
||||
let var = var.span.as_str();
|
||||
let val = val.span.as_str();
|
||||
match kind {
|
||||
AssignKind::Eq => write_vars(|v| v.new_var(var, val)),
|
||||
AssignKind::PlusEq => todo!(),
|
||||
AssignKind::MinusEq => todo!(),
|
||||
AssignKind::MultEq => todo!(),
|
||||
AssignKind::DivEq => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
new_env_vars
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepare_argv(argv: Vec<Tk>) -> Vec<String> {
|
||||
@@ -134,15 +224,20 @@ pub fn run_fork<'t,C,P>(
|
||||
exec_args: ExecArgs,
|
||||
child_action: C,
|
||||
parent_action: P,
|
||||
) -> ShResult<'t,()>
|
||||
) -> ShResult<()>
|
||||
where
|
||||
C: Fn(IoFrame,ExecArgs),
|
||||
P: Fn(IoFrame,Pid) -> ShResult<'t,()>
|
||||
C: Fn(IoFrame,ExecArgs) -> Errno,
|
||||
P: Fn(IoFrame,Pid) -> ShResult<()>
|
||||
{
|
||||
match unsafe { fork()? } {
|
||||
ForkResult::Child => {
|
||||
child_action(io_frame,exec_args);
|
||||
exit(1);
|
||||
let cmd = &exec_args.cmd.to_str().unwrap().to_string();
|
||||
let errno = child_action(io_frame,exec_args);
|
||||
match errno {
|
||||
Errno::ENOENT => eprintln!("Command not found: {}", cmd),
|
||||
_ => eprintln!("{errno}")
|
||||
}
|
||||
exit(errno as i32);
|
||||
}
|
||||
ForkResult::Parent { child } => {
|
||||
parent_action(io_frame,child)
|
||||
@@ -151,18 +246,21 @@ where
|
||||
}
|
||||
|
||||
/// The default behavior for the child process after forking
|
||||
pub fn def_child_action<'t>(mut io_frame: IoFrame, exec_args: ExecArgs) {
|
||||
io_frame.redirect().unwrap();
|
||||
execvpe(&exec_args.cmd, &exec_args.argv, &exec_args.envp).unwrap();
|
||||
pub fn def_child_action<'t>(mut io_frame: IoFrame, exec_args: ExecArgs) -> Errno {
|
||||
if let Err(e) = io_frame.redirect() {
|
||||
eprintln!("{e}");
|
||||
}
|
||||
let Err(e) = execvpe(&exec_args.cmd, &exec_args.argv, &exec_args.envp);
|
||||
e
|
||||
}
|
||||
|
||||
/// The default behavior for the parent process after forking
|
||||
pub fn def_parent_action<'t>(io_frame: IoFrame, child: Pid) -> ShResult<'t,()> {
|
||||
let status = waitpid(child, Some(WaitPidFlag::WSTOPPED))?;
|
||||
pub fn def_parent_action<'t>(io_frame: IoFrame, child: Pid) -> ShResult<()> {
|
||||
let status = waitpid(child, Some(WtFlag::WSTOPPED))?;
|
||||
match status {
|
||||
WaitStatus::Exited(_, status) => state::set_status(status),
|
||||
WtStat::Exited(_, status) => state::set_status(status),
|
||||
_ => unimplemented!()
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::{fmt::Display, ops::{Bound, Deref, Range, RangeBounds}};
|
||||
|
||||
use bitflags::bitflags;
|
||||
|
||||
use crate::{libsh::error::{ShErr, ShErrKind}, prelude::*};
|
||||
use crate::{builtin::BUILTINS, libsh::error::{ShErr, ShErrKind}, prelude::*};
|
||||
|
||||
pub const KEYWORDS: [&'static str;14] = [
|
||||
"if",
|
||||
@@ -48,6 +48,12 @@ impl<'s> Span<'s> {
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.source[self.start..self.end]
|
||||
}
|
||||
pub fn get_source(&'s self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
pub fn range(&self) -> Range<usize> {
|
||||
self.range.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows simple access to the underlying range wrapped by the span
|
||||
@@ -130,6 +136,9 @@ impl<'s> Tk<'s> {
|
||||
pub fn is_err(&self) -> bool {
|
||||
self.err_span.is_some()
|
||||
}
|
||||
pub fn source(&self) -> &'s str {
|
||||
self.span.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> Display for Tk<'s> {
|
||||
@@ -150,6 +159,8 @@ bitflags! {
|
||||
const OPENER = 0b0000000000000010;
|
||||
const IS_CMD = 0b0000000000000100;
|
||||
const IS_OP = 0b0000000000001000;
|
||||
const ASSIGN = 0b0000000000010000;
|
||||
const BUILTIN = 0b0000000000100000;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,6 +192,7 @@ bitflags! {
|
||||
|
||||
impl<'t> LexStream<'t> {
|
||||
pub fn new(source: &'t str, flags: LexFlags) -> Self {
|
||||
flog!(TRACE, "new lex stream");
|
||||
let flags = flags | LexFlags::FRESH | LexFlags::NEXT_IS_CMD;
|
||||
Self { source, cursor: 0, in_quote: false, flags }
|
||||
}
|
||||
@@ -346,8 +358,15 @@ impl<'t> LexStream<'t> {
|
||||
if self.flags.contains(LexFlags::NEXT_IS_CMD) {
|
||||
if KEYWORDS.contains(&new_tk.span.as_str()) {
|
||||
new_tk.flags |= TkFlags::KEYWORD;
|
||||
} else if is_assignment(&new_tk.span.as_str()) {
|
||||
new_tk.flags |= TkFlags::ASSIGN;
|
||||
} else {
|
||||
new_tk.flags |= TkFlags::IS_CMD;
|
||||
flog!(TRACE, new_tk.span.as_str());
|
||||
if BUILTINS.contains(&new_tk.span.as_str()) {
|
||||
new_tk.flags |= TkFlags::BUILTIN;
|
||||
}
|
||||
flog!(TRACE, new_tk.flags);
|
||||
self.next_is_not_cmd();
|
||||
}
|
||||
}
|
||||
@@ -480,6 +499,19 @@ pub fn get_char(src: &str, idx: usize) -> Option<char> {
|
||||
src.get(idx..)?.chars().next()
|
||||
}
|
||||
|
||||
pub fn is_assignment(text: &str) -> bool {
|
||||
let mut chars = text.chars();
|
||||
|
||||
while let Some(ch) = chars.next() {
|
||||
match ch {
|
||||
'\\' => { chars.next(); }
|
||||
'=' => return true,
|
||||
_ => continue
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Is '|', '&', '>', or '<'
|
||||
pub fn is_op(ch: char) -> bool {
|
||||
matches!(ch, '|' | '&' | '>' | '<')
|
||||
|
||||
234
src/parse/mod.rs
234
src/parse/mod.rs
@@ -1,7 +1,7 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use bitflags::bitflags;
|
||||
use lex::{Span, Tk, TkFlags, TkRule};
|
||||
use lex::{is_hard_sep, Span, Tk, TkFlags, TkRule};
|
||||
|
||||
use crate::{prelude::*, libsh::error::{ShErr, ShErrKind, ShResult}, procio::{IoFd, IoFile, IoInfo}};
|
||||
|
||||
@@ -17,6 +17,16 @@ pub struct Node<'t> {
|
||||
pub tokens: Vec<Tk<'t>>,
|
||||
}
|
||||
|
||||
impl<'t> Node<'t> {
|
||||
pub fn get_command(&'t self) -> Option<&'t Tk<'t>> {
|
||||
let NdRule::Command { assignments: _, argv } = &self.class else {
|
||||
return None
|
||||
};
|
||||
let command = argv.iter().find(|tk| tk.flags.contains(TkFlags::IS_CMD))?;
|
||||
Some(command)
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug)]
|
||||
pub struct NdFlags: u32 {
|
||||
@@ -48,8 +58,8 @@ impl RedirBldr {
|
||||
Default::default()
|
||||
}
|
||||
pub fn with_io_info(self, io_info: Box<dyn IoInfo>) -> Self {
|
||||
let Self { io_info: _, class, tgt_fd } = self;
|
||||
Self { io_info: Some(io_info), class, tgt_fd }
|
||||
let Self { io_info: _, class, tgt_fd } = self;
|
||||
Self { io_info: Some(io_info), class, tgt_fd }
|
||||
}
|
||||
pub fn with_class(self, class: RedirType) -> Self {
|
||||
let Self { io_info, class: _, tgt_fd } = self;
|
||||
@@ -127,8 +137,8 @@ impl FromStr for RedirBldr {
|
||||
let tgt_fd = tgt_fd.parse::<i32>().unwrap_or_else(|_| {
|
||||
match redir.class.unwrap() {
|
||||
RedirType::Input |
|
||||
RedirType::HereDoc |
|
||||
RedirType::HereString => 0,
|
||||
RedirType::HereDoc |
|
||||
RedirType::HereString => 0,
|
||||
_ => 1
|
||||
}
|
||||
});
|
||||
@@ -199,22 +209,12 @@ pub enum NdRule<'t> {
|
||||
LoopNode { kind: LoopKind, cond_block: CondNode<'t> },
|
||||
ForNode { vars: Vec<Tk<'t>>, arr: Vec<Tk<'t>>, body: Vec<Node<'t>> },
|
||||
CaseNode { pattern: Tk<'t>, case_blocks: Vec<CaseNode<'t>> },
|
||||
Command { assignments: Vec<Tk<'t>>, argv: Vec<Tk<'t>> },
|
||||
Command { assignments: Vec<Node<'t>>, argv: Vec<Tk<'t>> },
|
||||
Pipeline { cmds: Vec<Node<'t>>, pipe_err: bool },
|
||||
CmdList { elements: Vec<ConjunctNode<'t>> },
|
||||
Assignment { kind: AssignKind, var: Tk<'t>, val: Tk<'t> },
|
||||
}
|
||||
|
||||
/// If the given expression returns Some(), then return it
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ParseResult<'t> {
|
||||
NoMatch,
|
||||
Error(ShErr<'t>),
|
||||
Match(Node<'t>)
|
||||
}
|
||||
use ParseResult::*; // muh ergonomics
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParseStream<'t> {
|
||||
pub tokens: Vec<Tk<'t>>,
|
||||
@@ -254,11 +254,11 @@ impl<'t> ParseStream<'t> {
|
||||
assert!(num_consumed <= self.tokens.len());
|
||||
self.tokens = self.tokens[num_consumed..].to_vec();
|
||||
}
|
||||
fn parse_cmd_list(&mut self) -> ParseResult<'t> {
|
||||
fn parse_cmd_list(&mut self) -> ShResult<Option<Node<'t>>> {
|
||||
let mut elements = vec![];
|
||||
let mut node_tks = vec![];
|
||||
|
||||
while let Match(block) = self.parse_block(true) {
|
||||
while let Some(block) = self.parse_block(true)? {
|
||||
node_tks.append(&mut block.tokens.clone());
|
||||
let conjunct_op = match self.next_tk_class() {
|
||||
TkRule::And => ConjunctOp::And,
|
||||
@@ -275,35 +275,37 @@ impl<'t> ParseStream<'t> {
|
||||
break
|
||||
}
|
||||
}
|
||||
Match(Node {
|
||||
class: NdRule::CmdList { elements },
|
||||
flags: NdFlags::empty(),
|
||||
redirs: vec![],
|
||||
tokens: node_tks
|
||||
})
|
||||
if elements.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(Node {
|
||||
class: NdRule::CmdList { elements },
|
||||
flags: NdFlags::empty(),
|
||||
redirs: vec![],
|
||||
tokens: node_tks
|
||||
}))
|
||||
}
|
||||
}
|
||||
/// This tries to match on different stuff that can appear in a command position
|
||||
/// Matches shell commands like if-then-fi, pipelines, etc.
|
||||
/// Ordered from specialized to general, with more generally matchable stuff appearing at the bottom
|
||||
/// The check_pipelines parameter is used to prevent infinite recursion in parse_pipeline
|
||||
fn parse_block(&mut self, check_pipelines: bool) -> ParseResult<'t> {
|
||||
fn parse_block(&mut self, check_pipelines: bool) -> ShResult<Option<Node<'t>>> {
|
||||
if check_pipelines {
|
||||
match self.parse_pipeline() {
|
||||
NoMatch => { /* Continue */ }
|
||||
result => return result
|
||||
if let Some(node) = self.parse_pipeline()? {
|
||||
return Ok(Some(node))
|
||||
}
|
||||
} else {
|
||||
match self.parse_cmd() {
|
||||
NoMatch => { /* Continue */ }
|
||||
result => return result
|
||||
if let Some(node) = self.parse_cmd()? {
|
||||
return Ok(Some(node))
|
||||
}
|
||||
}
|
||||
NoMatch
|
||||
Ok(None)
|
||||
}
|
||||
fn parse_pipeline(&mut self) -> ParseResult<'t> {
|
||||
fn parse_pipeline(&mut self) -> ShResult<Option<Node<'t>>> {
|
||||
let mut cmds = vec![];
|
||||
let mut node_tks = vec![];
|
||||
while let Match(cmd) = self.parse_block(false) {
|
||||
while let Some(cmd) = self.parse_block(false)? {
|
||||
let is_punctuated = node_is_punctuated(&cmd.tokens);
|
||||
node_tks.append(&mut cmd.tokens.clone());
|
||||
cmds.push(cmd);
|
||||
@@ -318,18 +320,18 @@ impl<'t> ParseStream<'t> {
|
||||
}
|
||||
}
|
||||
if cmds.is_empty() {
|
||||
NoMatch
|
||||
Ok(None)
|
||||
} else {
|
||||
Match(Node {
|
||||
Ok(Some(Node {
|
||||
// TODO: implement pipe_err support
|
||||
class: NdRule::Pipeline { cmds, pipe_err: false },
|
||||
flags: NdFlags::empty(),
|
||||
redirs: vec![],
|
||||
tokens: node_tks
|
||||
})
|
||||
}))
|
||||
}
|
||||
}
|
||||
fn parse_cmd(&mut self) -> ParseResult<'t> {
|
||||
fn parse_cmd(&mut self) -> ShResult<Option<Node<'t>>> {
|
||||
let tk_slice = self.tokens.as_slice();
|
||||
let mut tk_iter = tk_slice.iter();
|
||||
let mut node_tks = vec![];
|
||||
@@ -337,25 +339,32 @@ impl<'t> ParseStream<'t> {
|
||||
let mut argv = vec![];
|
||||
let mut assignments = vec![];
|
||||
|
||||
let Some(cmd_tk) = tk_iter.next() else {
|
||||
return NoMatch
|
||||
};
|
||||
while let Some(prefix_tk) = tk_iter.next() {
|
||||
if prefix_tk.flags.contains(TkFlags::IS_CMD) {
|
||||
node_tks.push(prefix_tk.clone());
|
||||
argv.push(prefix_tk.clone());
|
||||
break
|
||||
} else if prefix_tk.flags.contains(TkFlags::ASSIGN) {
|
||||
let Some(assign) = self.parse_assignment(&prefix_tk) else {
|
||||
break
|
||||
};
|
||||
node_tks.push(prefix_tk.clone());
|
||||
assignments.push(assign)
|
||||
}
|
||||
}
|
||||
|
||||
if !cmd_tk.flags.contains(TkFlags::IS_CMD) {
|
||||
return NoMatch
|
||||
} else {
|
||||
node_tks.push(cmd_tk.clone());
|
||||
argv.push(cmd_tk.clone());
|
||||
if argv.is_empty() && assignments.is_empty() {
|
||||
return Ok(None)
|
||||
}
|
||||
|
||||
while let Some(tk) = tk_iter.next() {
|
||||
match tk.class {
|
||||
TkRule::EOI |
|
||||
TkRule::Pipe |
|
||||
TkRule::And |
|
||||
TkRule::Or => {
|
||||
break
|
||||
}
|
||||
TkRule::Pipe |
|
||||
TkRule::And |
|
||||
TkRule::Or => {
|
||||
break
|
||||
}
|
||||
TkRule::Sep => {
|
||||
node_tks.push(tk.clone());
|
||||
break
|
||||
@@ -368,9 +377,11 @@ impl<'t> ParseStream<'t> {
|
||||
node_tks.push(tk.clone());
|
||||
let redir_bldr = tk.span.as_str().parse::<RedirBldr>().unwrap();
|
||||
if redir_bldr.io_info.is_none() {
|
||||
let Some(path_tk) = tk_iter.next() else {
|
||||
let path_tk = tk_iter.next();
|
||||
|
||||
if path_tk.is_none_or(|tk| tk.class == TkRule::EOI) {
|
||||
self.flags |= ParseFlags::ERROR;
|
||||
return Error(
|
||||
return Err(
|
||||
ShErr::full(
|
||||
ShErrKind::ParseErr,
|
||||
"Expected a filename after this redirection",
|
||||
@@ -378,6 +389,8 @@ impl<'t> ParseStream<'t> {
|
||||
)
|
||||
)
|
||||
};
|
||||
|
||||
let path_tk = path_tk.unwrap();
|
||||
node_tks.push(path_tk.clone());
|
||||
|
||||
let Ok(file) = (match redir_bldr.class.unwrap() {
|
||||
@@ -403,7 +416,7 @@ impl<'t> ParseStream<'t> {
|
||||
_ => unreachable!()
|
||||
}) else {
|
||||
self.flags |= ParseFlags::ERROR;
|
||||
return Error(
|
||||
return Err(
|
||||
ShErr::full(
|
||||
ShErrKind::InternalErr,
|
||||
"Error opening file for redirection",
|
||||
@@ -423,17 +436,118 @@ impl<'t> ParseStream<'t> {
|
||||
}
|
||||
self.commit(node_tks.len());
|
||||
|
||||
Match(Node {
|
||||
Ok(Some(Node {
|
||||
class: NdRule::Command { assignments, argv },
|
||||
tokens: node_tks,
|
||||
flags: NdFlags::empty(),
|
||||
redirs,
|
||||
})
|
||||
}))
|
||||
}
|
||||
fn parse_assignment(&self, token: &Tk<'t>) -> Option<Node<'t>> {
|
||||
let mut chars = token.span.as_str().chars();
|
||||
let mut var_name = String::new();
|
||||
let mut name_range = token.span.start..token.span.start;
|
||||
let mut var_val = String::new();
|
||||
let mut val_range = token.span.end..token.span.end;
|
||||
let mut assign_kind = None;
|
||||
let mut pos = token.span.start;
|
||||
|
||||
while let Some(ch) = chars.next() {
|
||||
if !assign_kind.is_none() {
|
||||
match ch {
|
||||
'\\' => {
|
||||
pos += ch.len_utf8();
|
||||
var_val.push(ch);
|
||||
if let Some(esc_ch) = chars.next() {
|
||||
pos += esc_ch.len_utf8();
|
||||
var_val.push(esc_ch);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
pos += ch.len_utf8();
|
||||
var_val.push(ch);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match ch {
|
||||
'=' => {
|
||||
name_range.end = pos;
|
||||
pos += ch.len_utf8();
|
||||
val_range.start = pos;
|
||||
assign_kind = Some(AssignKind::Eq);
|
||||
}
|
||||
'-' => {
|
||||
name_range.end = pos;
|
||||
pos += ch.len_utf8();
|
||||
let Some('=') = chars.next() else {
|
||||
return None
|
||||
};
|
||||
pos += '='.len_utf8();
|
||||
val_range.start = pos;
|
||||
assign_kind = Some(AssignKind::MinusEq);
|
||||
}
|
||||
'+' => {
|
||||
name_range.end = pos;
|
||||
pos += ch.len_utf8();
|
||||
let Some('=') = chars.next() else {
|
||||
return None
|
||||
};
|
||||
pos += '='.len_utf8();
|
||||
val_range.start = pos;
|
||||
assign_kind = Some(AssignKind::PlusEq);
|
||||
}
|
||||
'/' => {
|
||||
name_range.end = pos;
|
||||
pos += ch.len_utf8();
|
||||
let Some('=') = chars.next() else {
|
||||
return None
|
||||
};
|
||||
pos += '='.len_utf8();
|
||||
val_range.start = pos;
|
||||
assign_kind = Some(AssignKind::DivEq);
|
||||
}
|
||||
'*' => {
|
||||
name_range.end = pos;
|
||||
pos += ch.len_utf8();
|
||||
let Some('=') = chars.next() else {
|
||||
return None
|
||||
};
|
||||
pos += '='.len_utf8();
|
||||
val_range.start = pos;
|
||||
assign_kind = Some(AssignKind::MultEq);
|
||||
}
|
||||
'\\' => {
|
||||
pos += ch.len_utf8();
|
||||
var_name.push(ch);
|
||||
if let Some(esc_ch) = chars.next() {
|
||||
pos += esc_ch.len_utf8();
|
||||
var_name.push(esc_ch);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
pos += ch.len_utf8();
|
||||
var_name.push(ch)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if assign_kind.is_none() || var_name.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let var = Tk::new(TkRule::Str, Span::new(name_range, token.source()));
|
||||
let val = Tk::new(TkRule::Str, Span::new(val_range, token.source()));
|
||||
Some(Node {
|
||||
class: NdRule::Assignment { kind: assign_kind.unwrap(), var, val },
|
||||
tokens: vec![token.clone()],
|
||||
flags: NdFlags::empty(),
|
||||
redirs: vec![]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'t> Iterator for ParseStream<'t> {
|
||||
type Item = ParseResult<'t>;
|
||||
type Item = ShResult<Node<'t>>;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
// Empty token vector or only SOI/EOI tokens, nothing to do
|
||||
if self.tokens.is_empty() || self.tokens.len() == 2 {
|
||||
@@ -451,9 +565,9 @@ impl<'t> Iterator for ParseStream<'t> {
|
||||
}
|
||||
}
|
||||
match self.parse_cmd_list() {
|
||||
NoMatch => None,
|
||||
Error(err) => Some(Error(err)),
|
||||
Match(cmdlist) => Some(Match(cmdlist))
|
||||
Ok(Some(node)) => return Some(Ok(node)),
|
||||
Ok(None) => return None,
|
||||
Err(e) => return Some(Err(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,21 +15,32 @@ pub use std::fs::{ self, File, OpenOptions };
|
||||
pub use std::path::{ Path, PathBuf };
|
||||
pub use std::ffi::{ CStr, CString };
|
||||
pub use std::process::exit;
|
||||
pub use std::time::Instant;
|
||||
pub use std::mem;
|
||||
pub use std::env;
|
||||
pub use std::fmt;
|
||||
|
||||
// Unix-specific IO abstractions
|
||||
pub use std::os::unix::io::{ AsRawFd, FromRawFd, IntoRawFd, OwnedFd, RawFd, };
|
||||
pub use std::os::unix::io::{ AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd, };
|
||||
|
||||
// Nix crate for POSIX APIs
|
||||
pub use nix::{
|
||||
errno::Errno,
|
||||
fcntl::{ open, OFlag },
|
||||
sys::{
|
||||
signal::{ self, kill, SigHandler, Signal },
|
||||
signal::{ self, kill, killpg, pthread_sigmask, SigSet, SigmaskHow, SigHandler, Signal },
|
||||
stat::Mode,
|
||||
wait::{ waitpid, WaitStatus },
|
||||
wait::{ waitpid, WaitPidFlag as WtFlag, WaitStatus as WtStat },
|
||||
},
|
||||
libc::{ STDIN_FILENO, STDERR_FILENO, STDOUT_FILENO },
|
||||
unistd::{ dup, read, write, close, dup2, execvpe, fork, pipe, Pid, ForkResult },
|
||||
unistd::{
|
||||
dup, read, isatty, write, close, setpgid, dup2, getpgrp,
|
||||
execvpe, tcgetpgrp, tcsetpgrp, fork, pipe, Pid, ForkResult
|
||||
},
|
||||
};
|
||||
pub use bitflags::bitflags;
|
||||
|
||||
pub use crate::flog;
|
||||
pub use crate::libsh::flog::FernLogLevel::*;
|
||||
|
||||
// Additional utilities, if needed, can be added here
|
||||
|
||||
104
src/procio.rs
104
src/procio.rs
@@ -1,6 +1,6 @@
|
||||
use std::{fmt::Debug, ops::{Deref, DerefMut}};
|
||||
|
||||
use crate::{libsh::error::ShResult, parse::Redir, prelude::*};
|
||||
use crate::{libsh::error::ShResult, parse::{Redir, RedirType}, prelude::*};
|
||||
|
||||
// Credit to fish-shell for many of the implementation ideas present in this module
|
||||
// https://fishshell.com/
|
||||
@@ -46,6 +46,8 @@ impl Debug for Box<dyn IoInfo> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A redirection to a raw fildesc
|
||||
/// e.g. `2>&1`
|
||||
#[derive(Debug)]
|
||||
pub struct IoFd {
|
||||
tgt_fd: RawFd,
|
||||
@@ -81,6 +83,8 @@ impl IoInfo for IoFd {
|
||||
}
|
||||
}
|
||||
|
||||
/// A redirection to a file
|
||||
/// e.g. `> file.txt`
|
||||
#[derive(Debug)]
|
||||
pub struct IoFile {
|
||||
tgt_fd: RawFd,
|
||||
@@ -112,6 +116,8 @@ impl IoInfo for IoFile {
|
||||
}
|
||||
}
|
||||
|
||||
/// A redirection to a pipe
|
||||
/// e.g. `echo foo | sed s/foo/bar/`
|
||||
#[derive(Debug)]
|
||||
pub struct IoPipe {
|
||||
tgt_fd: RawFd,
|
||||
@@ -150,9 +156,32 @@ impl IoInfo for IoPipe {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FdWriter {
|
||||
tgt: OwnedFd
|
||||
}
|
||||
|
||||
impl FdWriter {
|
||||
pub fn new(fd: i32) -> Self {
|
||||
let tgt = unsafe { OwnedFd::from_raw_fd(fd) };
|
||||
Self { tgt }
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for FdWriter {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
Ok(write(&self.tgt, buf)?)
|
||||
}
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A struct wrapping three fildescs representing `stdin`, `stdout`, and `stderr` respectively
|
||||
#[derive(Debug)]
|
||||
pub struct IoGroup(OwnedFd,OwnedFd,OwnedFd);
|
||||
|
||||
/// A single stack frame used with the IoStack
|
||||
/// Each stack frame represents the redirections of a single command
|
||||
#[derive(Default,Debug)]
|
||||
pub struct IoFrame {
|
||||
redirs: Vec<Redir>,
|
||||
@@ -163,6 +192,42 @@ impl<'e> IoFrame {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
pub fn from_redirs(redirs: Vec<Redir>) -> Self {
|
||||
Self { redirs, saved_io: None }
|
||||
}
|
||||
|
||||
/// This method returns a 2-tuple of `IoFrames`.
|
||||
/// This is to be used in the case of shell structures such as `if-then` and `while-do`.
|
||||
/// # Params
|
||||
/// * redirs: a vector of redirections
|
||||
///
|
||||
/// # Returns
|
||||
/// * An `IoFrame` containing all of the redirections which target stdin
|
||||
/// * An `IoFrame` containing all of the redirections which target stdout/stderr
|
||||
///
|
||||
/// # Purpose
|
||||
/// In the case of something like `if cat; then echo foo; fi < input.txt > output.txt`
|
||||
/// This will cleanly separate the redirections such that `cat` can receive the input from input.txt
|
||||
/// and `echo foo` can redirect it's output to output.txt
|
||||
pub fn cond_and_body(redirs: Vec<Redir>) -> (Self, Self) {
|
||||
let mut output_redirs = vec![];
|
||||
let mut input_redirs = vec![];
|
||||
for redir in redirs {
|
||||
match redir.class {
|
||||
RedirType::Input => input_redirs.push(redir),
|
||||
RedirType::Pipe => {
|
||||
match redir.io_info.tgt_fd() {
|
||||
STDIN_FILENO => input_redirs.push(redir),
|
||||
STDOUT_FILENO |
|
||||
STDERR_FILENO => output_redirs.push(redir),
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
_ => output_redirs.push(redir)
|
||||
}
|
||||
}
|
||||
(Self::from_redirs(input_redirs),Self::from_redirs(output_redirs))
|
||||
}
|
||||
pub fn save(&'e mut self) {
|
||||
unsafe {
|
||||
let saved_in = OwnedFd::from_raw_fd(dup(STDIN_FILENO).unwrap());
|
||||
@@ -171,7 +236,7 @@ impl<'e> IoFrame {
|
||||
self.saved_io = Some(IoGroup(saved_in,saved_out,saved_err));
|
||||
}
|
||||
}
|
||||
pub fn redirect(&'e mut self) -> ShResult<'e,()> {
|
||||
pub fn redirect(&mut self) -> ShResult<()> {
|
||||
self.save();
|
||||
for redir in &mut self.redirs {
|
||||
let io_info = &mut redir.io_info;
|
||||
@@ -182,12 +247,11 @@ impl<'e> IoFrame {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn restore(&'e mut self) -> ShResult<'e,()> {
|
||||
pub fn restore(&mut self) -> ShResult<()> {
|
||||
while let Some(mut redir) = self.pop() {
|
||||
redir.io_info.close().ok();
|
||||
redir.io_info.close()?;
|
||||
}
|
||||
if let Some(saved) = self.saved_io.take() {
|
||||
dbg!(&saved);
|
||||
dup2(saved.0.as_raw_fd(), STDIN_FILENO)?;
|
||||
dup2(saved.1.as_raw_fd(), STDOUT_FILENO)?;
|
||||
dup2(saved.2.as_raw_fd(), STDERR_FILENO)?;
|
||||
@@ -209,6 +273,12 @@ impl DerefMut for IoFrame {
|
||||
}
|
||||
}
|
||||
|
||||
/// A stack that maintains the current state of I/O for commands
|
||||
///
|
||||
/// This struct maintains the current state of I/O for the `Dispatcher` struct
|
||||
/// Each executed command requires an `IoFrame` in order to perform redirections.
|
||||
/// As nodes are walked through by the `Dispatcher`, it pushes new frames in certain contexts, and pops frames in others.
|
||||
/// Each command calls pop_frame() in order to get the current IoFrame in order to perform redirection
|
||||
#[derive(Default)]
|
||||
pub struct IoStack {
|
||||
stack: Vec<IoFrame>,
|
||||
@@ -229,13 +299,31 @@ impl<'e> IoStack {
|
||||
pub fn push_to_frame(&mut self, redir: Redir) {
|
||||
self.curr_frame_mut().push(redir)
|
||||
}
|
||||
/// Pop the current stack frame
|
||||
/// This differs from using `pop()` because it always returns a stack frame
|
||||
/// If `self.pop()` would empty the `IoStack`, it instead uses `std::mem::take()` to take the last frame
|
||||
/// There will always be at least one frame in the `IoStack`.
|
||||
pub fn pop_frame(&mut self) -> IoFrame {
|
||||
if self.stack.len() > 1 {
|
||||
self.stack.pop().unwrap()
|
||||
self.pop().unwrap()
|
||||
} else {
|
||||
std::mem::take(self.curr_frame_mut())
|
||||
}
|
||||
}
|
||||
/// Push a new stack frame.
|
||||
pub fn push_frame(&mut self, frame: IoFrame) {
|
||||
self.push(frame)
|
||||
}
|
||||
/// Flatten the `IoStack`
|
||||
/// All of the current stack frames will be flattened into a single one
|
||||
/// Not sure what use this will serve, but my gut said this was worthy of writing
|
||||
pub fn flatten(&mut self) {
|
||||
let mut flat_frame = IoFrame::new();
|
||||
while let Some(mut frame) = self.pop() {
|
||||
flat_frame.append(&mut frame)
|
||||
}
|
||||
self.push(flat_frame);
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for IoStack {
|
||||
@@ -250,3 +338,7 @@ impl DerefMut for IoStack {
|
||||
&mut self.stack
|
||||
}
|
||||
}
|
||||
|
||||
pub fn borrow_fd<'f>(fd: i32) -> BorrowedFd<'f> {
|
||||
unsafe { BorrowedFd::borrow_raw(fd) }
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ impl<'e> FernHist {
|
||||
pub fn new() -> Self {
|
||||
Self { file_path: None, entries: HistEntries::new(), max_len: 1000, flags: HistFlags::empty() }
|
||||
}
|
||||
pub fn from_path(file_path: PathBuf) -> ShResult<'e,Self> {
|
||||
pub fn from_path(file_path: PathBuf) -> ShResult<Self> {
|
||||
let mut new_hist = FernHist::new();
|
||||
new_hist.file_path = Some(file_path);
|
||||
new_hist.load_hist()?;
|
||||
@@ -76,14 +76,14 @@ impl<'e> FernHist {
|
||||
let id = self.len() + 1;
|
||||
HistEntry::new(body.to_string(), id)
|
||||
}
|
||||
pub fn init_hist_file(&mut self) -> ShResult<'e,()> {
|
||||
pub fn init_hist_file(&mut self) -> ShResult<()> {
|
||||
let Some(path) = self.file_path.clone() else {
|
||||
return Ok(());
|
||||
};
|
||||
self.save(&path)?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn load_hist(&mut self) -> ShResult<'e,()> {
|
||||
pub fn load_hist(&mut self) -> ShResult<()> {
|
||||
let Some(file_path) = self.file_path.clone() else {
|
||||
return Err(
|
||||
ShErr::simple(
|
||||
|
||||
@@ -9,16 +9,16 @@ use rustyline::{error::ReadlineError, history::{FileHistory, History}, Config, E
|
||||
|
||||
use crate::{libsh::{error::ShResult, term::{Style, Styled}}, prelude::*};
|
||||
|
||||
fn init_rl<'s>() -> ShResult<'s,Editor<FernReadline,FernHist>> {
|
||||
let hist = FernHist::default();
|
||||
fn init_rl<'s>() -> ShResult<Editor<FernReadline,FileHistory>> {
|
||||
let rl = FernReadline::new();
|
||||
let config = Config::default();
|
||||
let mut editor = Editor::with_history(config,hist)?;
|
||||
let mut editor = Editor::new()?;
|
||||
editor.set_helper(Some(rl));
|
||||
editor.load_history(&Path::new("/home/pagedmov/.fernhist"))?;
|
||||
Ok(editor)
|
||||
}
|
||||
|
||||
pub fn read_line<'s>() -> ShResult<'s,String> {
|
||||
pub fn read_line<'s>() -> ShResult<String> {
|
||||
assert!(isatty(STDIN_FILENO).unwrap());
|
||||
let mut editor = init_rl()?;
|
||||
let prompt = "$ ".styled(Style::Green | Style::Bold);
|
||||
match editor.readline(&prompt) {
|
||||
|
||||
@@ -47,7 +47,11 @@ impl Hint for FernHint {
|
||||
impl Hinter for FernReadline {
|
||||
type Hint = FernHint;
|
||||
fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<Self::Hint> {
|
||||
let ent = ctx.history().search(line, pos, rustyline::history::SearchDirection::Reverse).ok()??;
|
||||
let ent = ctx.history().search(
|
||||
line,
|
||||
ctx.history().len() - 1,
|
||||
rustyline::history::SearchDirection::Reverse
|
||||
).ok()??;
|
||||
let entry_raw = ent.entry.get(pos..)?.to_string();
|
||||
Some(FernHint::new(entry_raw))
|
||||
}
|
||||
|
||||
30
src/state.rs
30
src/state.rs
@@ -6,8 +6,6 @@ pub static JOB_TABLE: LazyLock<RwLock<JobTab>> = LazyLock::new(|| RwLock::new(Jo
|
||||
|
||||
pub static VAR_TABLE: LazyLock<RwLock<VarTab>> = LazyLock::new(|| RwLock::new(VarTab::new()));
|
||||
|
||||
pub static ENV_TABLE: LazyLock<RwLock<EnvTab>> = LazyLock::new(|| RwLock::new(EnvTab::new()));
|
||||
|
||||
pub struct JobTab {
|
||||
|
||||
}
|
||||
@@ -51,7 +49,11 @@ impl VarTab {
|
||||
&mut self.params
|
||||
}
|
||||
pub fn get_var(&self, var: &str) -> String {
|
||||
self.vars.get(var).map(|s| s.to_string()).unwrap_or_default()
|
||||
if let Some(var) = self.vars.get(var).map(|s| s.to_string()) {
|
||||
var
|
||||
} else {
|
||||
std::env::var(var).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
pub fn new_var(&mut self, var: &str, val: &str) {
|
||||
self.vars.insert(var.to_string(), val.to_string());
|
||||
@@ -64,16 +66,6 @@ impl VarTab {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EnvTab {
|
||||
|
||||
}
|
||||
|
||||
impl EnvTab {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Read from the job table
|
||||
pub fn read_jobs<T, F: FnOnce(RwLockReadGuard<JobTab>) -> T>(f: F) -> T {
|
||||
let lock = JOB_TABLE.read().unwrap();
|
||||
@@ -98,18 +90,6 @@ pub fn write_vars<T, F: FnOnce(&mut RwLockWriteGuard<VarTab>) -> T>(f: F) -> T {
|
||||
f(lock)
|
||||
}
|
||||
|
||||
/// Read from the environment table
|
||||
pub fn read_env<T, F: FnOnce(RwLockReadGuard<EnvTab>) -> T>(f: F) -> T {
|
||||
let lock = ENV_TABLE.read().unwrap();
|
||||
f(lock)
|
||||
}
|
||||
|
||||
/// Write to the environment table
|
||||
pub fn write_env<T, F: FnOnce(&mut RwLockWriteGuard<EnvTab>) -> T>(f: F) -> T {
|
||||
let lock = &mut ENV_TABLE.write().unwrap();
|
||||
f(lock)
|
||||
}
|
||||
|
||||
pub fn get_status() -> i32 {
|
||||
read_vars(|v| v.get_param('?')).parse::<i32>().unwrap()
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use expand::unescape_str;
|
||||
use parse::lex::{Tk, TkFlags, TkRule};
|
||||
use state::write_vars;
|
||||
use super::super::*;
|
||||
@@ -10,10 +11,18 @@ fn simple_expansion() {
|
||||
let mut tokens: Vec<Tk> = LexStream::new(varsub, LexFlags::empty())
|
||||
.filter(|tk| !matches!(tk.class, TkRule::EOI | TkRule::SOI))
|
||||
.collect();
|
||||
let var_tk = tokens.pop().unwrap();
|
||||
let var_tk = tokens.pop().unwrap();
|
||||
|
||||
let var_span = var_tk.span.clone();
|
||||
let exp_tk = var_tk.expand(var_span, TkFlags::empty());
|
||||
write_vars(|v| v.vars_mut().clear());
|
||||
insta::assert_debug_snapshot!(exp_tk.get_words())
|
||||
let var_span = var_tk.span.clone();
|
||||
let exp_tk = var_tk.expand(var_span, TkFlags::empty());
|
||||
write_vars(|v| v.vars_mut().clear());
|
||||
insta::assert_debug_snapshot!(exp_tk.get_words())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unescape_string() {
|
||||
let string = "echo $foo \\$bar";
|
||||
let unescaped = unescape_str(string);
|
||||
|
||||
insta::assert_snapshot!(unescaped)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
source: src/tests/expand.rs
|
||||
expression: unescaped
|
||||
---
|
||||
echo foo $bar
|
||||
@@ -3,7 +3,7 @@ source: src/tests/parser.rs
|
||||
expression: nodes
|
||||
---
|
||||
[
|
||||
Match(
|
||||
Ok(
|
||||
Node {
|
||||
class: CmdList {
|
||||
elements: [
|
||||
|
||||
@@ -3,7 +3,7 @@ source: src/tests/parser.rs
|
||||
expression: nodes
|
||||
---
|
||||
[
|
||||
Match(
|
||||
Ok(
|
||||
Node {
|
||||
class: CmdList {
|
||||
elements: [
|
||||
@@ -21,7 +21,7 @@ expression: nodes
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 0..4,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
@@ -33,7 +33,7 @@ expression: nodes
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 5..8,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -52,7 +52,7 @@ expression: nodes
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 0..4,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
@@ -64,7 +64,7 @@ expression: nodes
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 5..8,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -82,7 +82,7 @@ expression: nodes
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 11..14,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
@@ -93,8 +93,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 15..24,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 15..25,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -113,7 +113,7 @@ expression: nodes
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 11..14,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
@@ -124,8 +124,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 15..24,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 15..25,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -147,7 +147,7 @@ expression: nodes
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 0..4,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
@@ -159,7 +159,7 @@ expression: nodes
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 5..8,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -171,7 +171,7 @@ expression: nodes
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 9..10,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -183,7 +183,7 @@ expression: nodes
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 11..14,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
@@ -194,8 +194,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 15..24,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 15..25,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -218,8 +218,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 28..32,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 29..33,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
@@ -230,8 +230,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 33..36,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 34..37,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -249,8 +249,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 28..32,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 29..33,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
@@ -261,8 +261,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 33..36,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 34..37,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -279,8 +279,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 39..42,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 40..43,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
@@ -291,8 +291,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 43..52,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 44..54,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -310,8 +310,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 39..42,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 40..43,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
@@ -322,8 +322,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 43..52,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 44..54,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -344,8 +344,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 28..32,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 29..33,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
@@ -356,8 +356,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 33..36,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 34..37,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -368,8 +368,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 37..38,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 38..39,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -380,8 +380,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 39..42,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 40..43,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
@@ -392,8 +392,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 43..52,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 44..54,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -416,8 +416,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 56..60,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 58..62,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
@@ -428,8 +428,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 61..64,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 63..66,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -440,8 +440,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 65..68,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 67..70,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -459,8 +459,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 56..60,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 58..62,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
@@ -471,8 +471,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 61..64,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 63..66,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -483,8 +483,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 65..68,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 67..70,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -501,8 +501,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 71..74,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 73..76,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
@@ -513,8 +513,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 75..80,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 77..82,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -525,8 +525,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 81..88,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 83..90,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -537,8 +537,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 89..93,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 91..95,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -556,8 +556,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 71..74,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 73..76,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
@@ -568,8 +568,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 75..80,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 77..82,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -580,8 +580,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 81..88,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 83..90,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -592,8 +592,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 89..93,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 91..95,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -614,8 +614,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 56..60,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 58..62,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
@@ -626,8 +626,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 61..64,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 63..66,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -638,8 +638,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 65..68,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 67..70,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -650,8 +650,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 69..70,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 71..72,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -662,8 +662,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 71..74,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 73..76,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
@@ -674,8 +674,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 75..80,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 77..82,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -686,8 +686,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 81..88,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 83..90,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -698,8 +698,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 89..93,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 91..95,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -722,7 +722,7 @@ expression: nodes
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 0..4,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
@@ -734,7 +734,7 @@ expression: nodes
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 5..8,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -746,7 +746,7 @@ expression: nodes
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 9..10,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -758,7 +758,7 @@ expression: nodes
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 11..14,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
@@ -769,8 +769,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 15..24,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 15..25,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -781,8 +781,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 25..27,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 26..28,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -793,8 +793,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 28..32,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 29..33,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
@@ -805,8 +805,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 33..36,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 34..37,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -817,8 +817,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 37..38,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 38..39,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -829,8 +829,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 39..42,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 40..43,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
@@ -841,8 +841,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 43..52,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 44..54,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -853,8 +853,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 53..55,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 55..57,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -865,8 +865,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 56..60,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 58..62,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
@@ -877,8 +877,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 61..64,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 63..66,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -889,8 +889,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 65..68,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 67..70,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -901,8 +901,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 69..70,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 71..72,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -913,8 +913,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 71..74,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 73..76,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
@@ -925,8 +925,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 75..80,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 77..82,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -937,8 +937,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 81..88,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 83..90,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
@@ -949,8 +949,8 @@ expression: nodes
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 89..93,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
range: 91..95,
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
|
||||
@@ -3,7 +3,7 @@ source: src/tests/parser.rs
|
||||
expression: nodes
|
||||
---
|
||||
[
|
||||
Match(
|
||||
Ok(
|
||||
Node {
|
||||
class: CmdList {
|
||||
elements: [
|
||||
|
||||
@@ -3,7 +3,7 @@ source: src/tests/parser.rs
|
||||
expression: nodes
|
||||
---
|
||||
[
|
||||
Match(
|
||||
Ok(
|
||||
Node {
|
||||
class: CmdList {
|
||||
elements: [
|
||||
|
||||
Reference in New Issue
Block a user