Compare commits

...

2 Commits

21 changed files with 364 additions and 238 deletions

38
Cargo.lock generated
View File

@@ -194,25 +194,6 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "fern"
version = "0.3.0"
dependencies = [
"bitflags",
"clap",
"env_logger",
"glob",
"insta",
"log",
"nix",
"pretty_assertions",
"regex",
"tempfile",
"unicode-segmentation",
"unicode-width",
"vte",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.3.4" version = "0.3.4"
@@ -438,6 +419,25 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "shed"
version = "0.3.0"
dependencies = [
"bitflags",
"clap",
"env_logger",
"glob",
"insta",
"log",
"nix",
"pretty_assertions",
"regex",
"tempfile",
"unicode-segmentation",
"unicode-width",
"vte",
]
[[package]] [[package]]
name = "similar" name = "similar"
version = "2.7.0" version = "2.7.0"

View File

@@ -1,5 +1,5 @@
[package] [package]
name = "fern" name = "shed"
description = "A linux shell written in rust" description = "A linux shell written in rust"
publish = false publish = false
version = "0.3.0" version = "0.3.0"
@@ -27,4 +27,4 @@ pretty_assertions = "1.4.1"
tempfile = "3.24.0" tempfile = "3.24.0"
[[bin]] [[bin]]
name = "fern" name = "shed"

View File

@@ -1,2 +1,2 @@
# fern # shed
A shell program written in Rust for Unix based operating systems. This is mostly a pet project, still largely a work in progress, though it is being designed with end-users in mind. Will add more to this readme as features are implemented. A shell program written in Rust for Unix based operating systems. This is mostly a pet project, still largely a work in progress, though it is being designed with end-users in mind. Will add more to this readme as features are implemented.

View File

@@ -13,7 +13,7 @@
in in
{ {
packages.default = pkgs.rustPlatform.buildRustPackage { packages.default = pkgs.rustPlatform.buildRustPackage {
pname = "fern"; pname = "shed";
version = "0.3.0"; version = "0.3.0";
src = self; src = self;
@@ -23,22 +23,22 @@
}; };
doCheck = false; doCheck = false;
passthru.shellPath = "/bin/fern"; passthru.shellPath = "/bin/shed";
meta = with pkgs.lib; { meta = with pkgs.lib; {
description = "A Linux shell written in Rust"; description = "A Linux shell written in Rust";
homepage = "https://github.com/km-clay/fern"; homepage = "https://github.com/km-clay/shed";
license = licenses.mit; license = licenses.mit;
maintainers = [ ]; maintainers = [ ];
platforms = platforms.linux; platforms = platforms.linux;
}; };
}; };
}) // { }) // {
nixosModules.fern = import ./nix/module.nix; nixosModules.shed = import ./nix/module.nix;
homeModules.fern = import ./nix/hm-module.nix; homeModules.shed = import ./nix/hm-module.nix;
overlays.default = final: prev: { overlays.default = final: prev: {
fern = self.packages.${final.stdenv.hostPlatform.system}.default; shed = self.packages.${final.stdenv.hostPlatform.system}.default;
}; };
}; };
} }

View File

@@ -1,30 +1,30 @@
{ config, lib, pkgs, ... }: { config, lib, pkgs, ... }:
let let
cfg = config.programs.fern; cfg = config.programs.shed;
boolToString = b: boolToString = b:
if b then "true" else "false"; if b then "true" else "false";
in in
{ {
options.programs.fern = { options.programs.shed = {
enable = lib.mkEnableOption "fern shell"; enable = lib.mkEnableOption "shed shell";
package = lib.mkOption { package = lib.mkOption {
type = lib.types.package; type = lib.types.package;
default = pkgs.fern; default = pkgs.shed;
description = "The fern package to use"; description = "The shed package to use";
}; };
aliases = lib.mkOption { aliases = lib.mkOption {
type = lib.types.attrsOf lib.types.str; type = lib.types.attrsOf lib.types.str;
default = {}; default = {};
description = "The command name to use for the fern shell"; description = "Aliases to set when shed starts (e.g. ls='ls --color=auto')";
}; };
environmentVars = lib.mkOption { environmentVars = lib.mkOption {
type = lib.types.attrsOf lib.types.str; type = lib.types.attrsOf lib.types.str;
default = {}; default = {};
description = "Environment variables to set when fern starts"; description = "Environment variables to set when shed starts";
}; };
settings = { settings = {
@@ -61,7 +61,7 @@ in
bellEnabled = lib.mkOption { bellEnabled = lib.mkOption {
type = lib.types.bool; type = lib.types.bool;
default = true; default = true;
description = "Whether to allow fern to ring the terminal bell on certain events (e.g. command completion, errors, etc.)"; description = "Whether to allow shed to ring the terminal bell on certain events (e.g. command completion, errors, etc.)";
}; };
maxRecurseDepth = lib.mkOption { maxRecurseDepth = lib.mkOption {
type = lib.types.int; type = lib.types.int;
@@ -92,12 +92,12 @@ in
extraPostConfig = lib.mkOption { extraPostConfig = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = ""; default = "";
description = "Additional configuration to append to the fern configuration file"; description = "Additional configuration to append to the shed configuration file";
}; };
extraPreConfig = lib.mkOption { extraPreConfig = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = ""; default = "";
description = "Additional configuration to prepend to the fern configuration file"; description = "Additional configuration to prepend to the shed configuration file";
}; };
}; };
}; };
@@ -105,7 +105,7 @@ in
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
home.packages = [ cfg.package ]; home.packages = [ cfg.package ];
home.file.".fernrc".text = lib.concatLines [ home.file.".shedrc".text = lib.concatLines [
cfg.settings.extraPreConfig cfg.settings.extraPreConfig
(lib.concatLines (lib.mapAttrsToList (name: value: "export ${name}=\"${value}\"") cfg.environmentVars)) (lib.concatLines (lib.mapAttrsToList (name: value: "export ${name}=\"${value}\"") cfg.environmentVars))
(lib.concatLines (lib.mapAttrsToList (name: value: "alias ${name}=\"${value}\"") cfg.aliases)) (lib.concatLines (lib.mapAttrsToList (name: value: "alias ${name}=\"${value}\"") cfg.aliases))

View File

@@ -1,16 +1,16 @@
{ config, lib, pkgs, ... }: { config, lib, pkgs, ... }:
let let
cfg = config.programs.fern; cfg = config.programs.shed;
in in
{ {
options.programs.fern = { options.programs.shed = {
enable = lib.mkEnableOption "fern shell"; enable = lib.mkEnableOption "shed shell";
package = lib.mkOption { package = lib.mkOption {
type = lib.types.package; type = lib.types.package;
default = pkgs.fern; default = pkgs.shed;
description = "The fern package to use"; description = "The shed package to use";
}; };
}; };

View File

@@ -1,87 +0,0 @@
use crate::{
jobs::JobBldr,
libsh::error::ShResult,
parse::{NdRule, Node},
prelude::*,
procio::{borrow_fd, IoStack},
state::{self, read_vars, write_vars, VarFlags},
};
use super::setup_builtin;
pub fn export(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
let NdRule::Command {
assignments: _,
argv,
} = node.class
else {
unreachable!()
};
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
if argv.is_empty() {
// Display the environment variables
let mut env_output = env::vars()
.map(|var| format!("{}={}", var.0, var.1)) // Get all of them, zip them into one string
.collect::<Vec<_>>();
env_output.sort(); // Sort them alphabetically
let mut env_output = env_output.join("\n"); // Join them with newlines
env_output.push('\n'); // Push a final newline
let stdout = borrow_fd(STDOUT_FILENO);
write(stdout, env_output.as_bytes())?; // Write it
} else {
for (arg, _) in argv {
if let Some((var, val)) = arg.split_once('=') {
write_vars(|v| v.set_var(var, val, VarFlags::EXPORT)); // Export an assignment like
// 'foo=bar'
} else {
write_vars(|v| v.export_var(&arg)); // Export an existing variable, if
// any
}
}
}
state::set_status(0);
Ok(())
}
pub fn local(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
let NdRule::Command {
assignments: _,
argv,
} = node.class
else {
unreachable!()
};
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
if argv.is_empty() {
// Display the local variables
let vars_output = read_vars(|v| {
let mut vars = v
.flatten_vars()
.into_iter()
.map(|(k, v)| format!("{}={}", k, v))
.collect::<Vec<String>>();
vars.sort();
let mut vars_joined = vars.join("\n");
vars_joined.push('\n');
vars_joined
});
let stdout = borrow_fd(STDOUT_FILENO);
write(stdout, vars_output.as_bytes())?; // Write it
} else {
for (arg, _) in argv {
if let Some((var, val)) = arg.split_once('=') {
write_vars(|v| v.set_var(var, val, VarFlags::LOCAL));
} else {
write_vars(|v| v.set_var(&arg, "", VarFlags::LOCAL)); // Create an uninitialized local variable
}
}
}
state::set_status(0);
Ok(())
}

View File

@@ -8,13 +8,13 @@ use crate::{
execute::prepare_argv, execute::prepare_argv,
lex::{Span, Tk}, lex::{Span, Tk},
}, },
procio::{IoFrame, IoStack, RedirGuard}, procio::{IoFrame, IoStack, RedirGuard}, state,
}; };
pub mod alias; pub mod alias;
pub mod cd; pub mod cd;
pub mod echo; pub mod echo;
pub mod export; pub mod varcmds;
pub mod flowctl; pub mod flowctl;
pub mod jobctl; pub mod jobctl;
pub mod pwd; pub mod pwd;
@@ -29,10 +29,10 @@ pub mod dirstack;
pub mod exec; pub mod exec;
pub mod eval; pub mod eval;
pub const BUILTINS: [&str; 28] = [ pub const BUILTINS: [&str; 33] = [
"echo", "cd", "read", "export", "local", "pwd", "source", "shift", "jobs", "fg", "bg", "disown", "alias", "unalias", "echo", "cd", "read", "export", "local", "pwd", "source", "shift", "jobs", "fg", "bg", "disown", "alias", "unalias",
"return", "break", "continue", "exit", "zoltraak", "shopt", "builtin", "command", "trap", "return", "break", "continue", "exit", "zoltraak", "shopt", "builtin", "command", "trap",
"pushd", "popd", "dirs", "exec", "eval" "pushd", "popd", "dirs", "exec", "eval", "true", "false", ":", "readonly", "unset"
]; ];
/// Sets up a builtin command /// Sets up a builtin command
@@ -93,3 +93,18 @@ pub fn setup_builtin(
// io_frame.restore() // io_frame.restore()
Ok((argv, guard)) Ok((argv, guard))
} }
pub fn true_builtin() -> ShResult<()> {
state::set_status(0);
Ok(())
}
pub fn false_builtin() -> ShResult<()> {
state::set_status(1);
Ok(())
}
pub fn noop_builtin() -> ShResult<()> {
state::set_status(0);
Ok(())
}

163
src/builtin/varcmds.rs Normal file
View File

@@ -0,0 +1,163 @@
use crate::{
jobs::JobBldr,
libsh::error::{ShErr, ShErrKind, ShResult},
parse::{NdRule, Node},
prelude::*,
procio::{IoStack, borrow_fd},
state::{self, VarFlags, read_vars, write_vars},
};
use super::setup_builtin;
pub fn readonly(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
let NdRule::Command {
assignments: _,
argv,
} = node.class
else {
unreachable!()
};
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
if argv.is_empty() {
// Display the local variables
let vars_output = read_vars(|v| {
let mut vars = v
.flatten_vars()
.into_iter()
.filter(|(_, v)| v.flags().contains(VarFlags::READONLY))
.map(|(k, v)| format!("{}={}", k, v))
.collect::<Vec<String>>();
vars.sort();
let mut vars_joined = vars.join("\n");
vars_joined.push('\n');
vars_joined
});
let stdout = borrow_fd(STDOUT_FILENO);
write(stdout, vars_output.as_bytes())?; // Write it
} else {
for (arg, _) in argv {
if let Some((var, val)) = arg.split_once('=') {
write_vars(|v| v.set_var(var, val, VarFlags::READONLY))?;
} else {
write_vars(|v| v.set_var(&arg, "", VarFlags::READONLY))?;
}
}
}
state::set_status(0);
Ok(())
}
pub fn unset(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
let blame = node.get_span().clone();
let NdRule::Command {
assignments: _,
argv,
} = node.class
else {
unreachable!()
};
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
if argv.is_empty() {
return Err(ShErr::full(
ShErrKind::SyntaxErr,
"unset: Expected at least one argument",
blame
));
}
for (arg,span) in argv {
if !read_vars(|v| v.var_exists(&arg)) {
return Err(ShErr::full(
ShErrKind::ExecFail,
format!("unset: No such variable '{arg}'"),
span
));
}
write_vars(|v| v.unset_var(&arg))?;
}
state::set_status(0);
Ok(())
}
pub fn export(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
let NdRule::Command {
assignments: _,
argv,
} = node.class
else {
unreachable!()
};
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
if argv.is_empty() {
// Display the environment variables
let mut env_output = env::vars()
.map(|var| format!("{}={}", var.0, var.1)) // Get all of them, zip them into one string
.collect::<Vec<_>>();
env_output.sort(); // Sort them alphabetically
let mut env_output = env_output.join("\n"); // Join them with newlines
env_output.push('\n'); // Push a final newline
let stdout = borrow_fd(STDOUT_FILENO);
write(stdout, env_output.as_bytes())?; // Write it
} else {
for (arg, _) in argv {
if let Some((var, val)) = arg.split_once('=') {
write_vars(|v| v.set_var(var, val, VarFlags::EXPORT))?;
} else {
write_vars(|v| v.export_var(&arg)); // Export an existing variable, if
// any
}
}
}
state::set_status(0);
Ok(())
}
pub fn local(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
let NdRule::Command {
assignments: _,
argv,
} = node.class
else {
unreachable!()
};
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
if argv.is_empty() {
// Display the local variables
let vars_output = read_vars(|v| {
let mut vars = v
.flatten_vars()
.into_iter()
.map(|(k, v)| format!("{}={}", k, v))
.collect::<Vec<String>>();
vars.sort();
let mut vars_joined = vars.join("\n");
vars_joined.push('\n');
vars_joined
});
let stdout = borrow_fd(STDOUT_FILENO);
write(stdout, vars_output.as_bytes())?; // Write it
} else {
for (arg, _) in argv {
if let Some((var, val)) = arg.split_once('=') {
write_vars(|v| v.set_var(var, val, VarFlags::LOCAL))?;
} else {
write_vars(|v| v.set_var(&arg, "", VarFlags::LOCAL))?;
}
}
}
state::set_status(0);
Ok(())
}

View File

@@ -2002,7 +2002,7 @@ pub fn expand_prompt(raw: &str) -> ShResult<String> {
result.push_str(&hostname); result.push_str(&hostname);
} }
PromptTk::HostnameShort => todo!(), PromptTk::HostnameShort => todo!(),
PromptTk::ShellName => result.push_str("fern"), PromptTk::ShellName => result.push_str("shed"),
PromptTk::Username => { PromptTk::Username => {
let username = std::env::var("USER").unwrap(); let username = std::env::var("USER").unwrap();
result.push_str(&username); result.push_str(&username);

View File

@@ -4,7 +4,7 @@ use super::term::{Style, Styled};
#[derive(Clone, Copy, PartialEq, PartialOrd, Ord, Eq, Debug)] #[derive(Clone, Copy, PartialEq, PartialOrd, Ord, Eq, Debug)]
#[repr(u8)] #[repr(u8)]
pub enum FernLogLevel { pub enum ShedLogLevel {
NONE = 0, NONE = 0,
ERROR = 1, ERROR = 1,
WARN = 2, WARN = 2,
@@ -13,9 +13,9 @@ pub enum FernLogLevel {
TRACE = 5, TRACE = 5,
} }
impl Display for FernLogLevel { impl Display for ShedLogLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use FernLogLevel::*; use ShedLogLevel::*;
match self { match self {
ERROR => write!(f, "{}", "ERROR".styled(Style::Red | Style::Bold)), ERROR => write!(f, "{}", "ERROR".styled(Style::Red | Style::Bold)),
WARN => write!(f, "{}", "WARN".styled(Style::Yellow | Style::Bold)), WARN => write!(f, "{}", "WARN".styled(Style::Yellow | Style::Bold)),
@@ -27,8 +27,8 @@ impl Display for FernLogLevel {
} }
} }
pub fn log_level() -> FernLogLevel { pub fn log_level() -> ShedLogLevel {
use FernLogLevel::*; use ShedLogLevel::*;
let level = std::env::var("FERN_LOG_LEVEL").unwrap_or_default(); let level = std::env::var("FERN_LOG_LEVEL").unwrap_or_default();
match level.to_lowercase().as_str() { match level.to_lowercase().as_str() {
"error" => ERROR, "error" => ERROR,
@@ -40,7 +40,7 @@ pub fn log_level() -> FernLogLevel {
} }
} }
/// A structured logging macro designed for `fern`. /// A structured logging macro designed for `shed`.
/// ///
/// `flog!` was implemented because `rustyline` uses `env_logger`, which /// `flog!` was implemented because `rustyline` uses `env_logger`, which
/// clutters the debug output. This macro prints log messages in a structured /// clutters the debug output. This macro prints log messages in a structured

View File

@@ -33,14 +33,14 @@ use crate::parse::execute::exec_input;
use crate::prelude::*; use crate::prelude::*;
use crate::prompt::get_prompt; use crate::prompt::get_prompt;
use crate::prompt::readline::term::{RawModeGuard, raw_mode}; use crate::prompt::readline::term::{RawModeGuard, raw_mode};
use crate::prompt::readline::{FernVi, ReadlineEvent}; use crate::prompt::readline::{ShedVi, ReadlineEvent};
use crate::signal::{QUIT_CODE, check_signals, sig_setup, signals_pending}; use crate::signal::{QUIT_CODE, check_signals, sig_setup, signals_pending};
use crate::state::{read_logic, source_rc, write_jobs, write_meta}; use crate::state::{read_logic, source_rc, write_jobs, write_meta};
use clap::Parser; use clap::Parser;
use state::{read_vars, write_vars}; use state::{read_vars, write_vars};
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
struct FernArgs { struct ShedArgs {
script: Option<String>, script: Option<String>,
#[arg(short)] #[arg(short)]
@@ -77,8 +77,8 @@ fn kickstart_lazy_evals() {
fn setup_panic_handler() { fn setup_panic_handler() {
let default_panic_hook = std::panic::take_hook(); let default_panic_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| { std::panic::set_hook(Box::new(move |info| {
let _ = state::FERN.try_with(|fern| { let _ = state::FERN.try_with(|shed| {
if let Ok(mut jobs) = fern.jobs.try_borrow_mut() { if let Ok(mut jobs) = shed.jobs.try_borrow_mut() {
jobs.hang_up(); jobs.hang_up();
} }
}); });
@@ -92,14 +92,14 @@ fn main() -> ExitCode {
kickstart_lazy_evals(); kickstart_lazy_evals();
setup_panic_handler(); setup_panic_handler();
let mut args = FernArgs::parse(); let mut args = ShedArgs::parse();
if env::args().next().is_some_and(|a| a.starts_with('-')) { if env::args().next().is_some_and(|a| a.starts_with('-')) {
// first arg is '-fern' // first arg is '-shed'
// meaning we are in a login shell // meaning we are in a login shell
args.login_shell = true; args.login_shell = true;
} }
if args.version { if args.version {
println!("fern {} ({} {})", env!("CARGO_PKG_VERSION"), std::env::consts::ARCH, std::env::consts::OS); println!("shed {} ({} {})", env!("CARGO_PKG_VERSION"), std::env::consts::ARCH, std::env::consts::OS);
return ExitCode::SUCCESS; return ExitCode::SUCCESS;
} }
@@ -108,14 +108,14 @@ fn main() -> ExitCode {
} else if let Some(cmd) = args.command { } else if let Some(cmd) = args.command {
exec_input(cmd, None, false) exec_input(cmd, None, false)
} else { } else {
fern_interactive() shed_interactive()
} { } {
eprintln!("fern: {e}"); eprintln!("shed: {e}");
}; };
if let Some(trap) = read_logic(|l| l.get_trap(TrapTarget::Exit)) if let Some(trap) = read_logic(|l| l.get_trap(TrapTarget::Exit))
&& let Err(e) = exec_input(trap, None, false) { && let Err(e) = exec_input(trap, None, false) {
eprintln!("fern: error running EXIT trap: {e}"); eprintln!("shed: error running EXIT trap: {e}");
} }
write_jobs(|j| j.hang_up()); write_jobs(|j| j.hang_up());
@@ -125,7 +125,7 @@ fn main() -> ExitCode {
fn run_script<P: AsRef<Path>>(path: P, args: Vec<String>) -> ShResult<()> { fn run_script<P: AsRef<Path>>(path: P, args: Vec<String>) -> ShResult<()> {
let path = path.as_ref(); let path = path.as_ref();
if !path.is_file() { if !path.is_file() {
eprintln!("fern: Failed to open input file: {}", path.display()); eprintln!("shed: Failed to open input file: {}", path.display());
QUIT_CODE.store(1, Ordering::SeqCst); QUIT_CODE.store(1, Ordering::SeqCst);
return Err(ShErr::simple( return Err(ShErr::simple(
ShErrKind::CleanExit(1), ShErrKind::CleanExit(1),
@@ -133,7 +133,7 @@ fn run_script<P: AsRef<Path>>(path: P, args: Vec<String>) -> ShResult<()> {
)); ));
} }
let Ok(input) = fs::read_to_string(path) else { let Ok(input) = fs::read_to_string(path) else {
eprintln!("fern: Failed to read input file: {}", path.display()); eprintln!("shed: Failed to read input file: {}", path.display());
QUIT_CODE.store(1, Ordering::SeqCst); QUIT_CODE.store(1, Ordering::SeqCst);
return Err(ShErr::simple( return Err(ShErr::simple(
ShErrKind::CleanExit(1), ShErrKind::CleanExit(1),
@@ -152,7 +152,7 @@ fn run_script<P: AsRef<Path>>(path: P, args: Vec<String>) -> ShResult<()> {
exec_input(input, None, false) exec_input(input, None, false)
} }
fn fern_interactive() -> ShResult<()> { fn shed_interactive() -> ShResult<()> {
let _raw_mode = raw_mode(); // sets raw mode, restores termios on drop let _raw_mode = raw_mode(); // sets raw mode, restores termios on drop
sig_setup(); sig_setup();
@@ -161,7 +161,7 @@ fn fern_interactive() -> ShResult<()> {
} }
// Create readline instance with initial prompt // Create readline instance with initial prompt
let mut readline = match FernVi::new(get_prompt().ok(), *TTY_FILENO) { let mut readline = match ShedVi::new(get_prompt().ok(), *TTY_FILENO) {
Ok(rl) => rl, Ok(rl) => rl,
Err(e) => { Err(e) => {
eprintln!("Failed to initialize readline: {e}"); eprintln!("Failed to initialize readline: {e}");

View File

@@ -2,7 +2,7 @@ use std::{collections::{HashSet, VecDeque}, os::unix::fs::PermissionsExt};
use crate::{ use crate::{
builtin::{ builtin::{
alias::{alias, unalias}, cd::cd, dirstack::{dirs, popd, pushd}, echo::echo, eval, exec, export::{export, local}, flowctl::flowctl, jobctl::{JobBehavior, continue_job, disown, jobs}, pwd::pwd, read::read_builtin, shift::shift, shopt::shopt, source::source, test::double_bracket_test, trap::{TrapTarget, trap}, zoltraak::zoltraak alias::{alias, unalias}, cd::cd, dirstack::{dirs, popd, pushd}, echo::echo, eval, exec, flowctl::flowctl, jobctl::{JobBehavior, continue_job, disown, jobs}, pwd::pwd, read::read_builtin, shift::shift, shopt::shopt, source::source, test::double_bracket_test, trap::{TrapTarget, trap}, true_builtin, varcmds::{export, local, readonly, unset}, zoltraak::zoltraak
}, },
expand::{expand_aliases, glob_to_regex}, expand::{expand_aliases, glob_to_regex},
jobs::{ChildProc, JobStack, dispatch_job}, jobs::{ChildProc, JobStack, dispatch_job},
@@ -90,7 +90,7 @@ impl Drop for VarCtxGuard {
fn drop(&mut self) { fn drop(&mut self) {
write_vars(|v| { write_vars(|v| {
for var in &self.vars { for var in &self.vars {
v.unset_var(var); v.unset_var(var).ok();
} }
}); });
} }
@@ -569,7 +569,7 @@ impl Dispatcher {
.zip(chunk.iter().chain(std::iter::repeat(&empty))); .zip(chunk.iter().chain(std::iter::repeat(&empty)));
for (var, val) in chunk_iter { for (var, val) in chunk_iter {
write_vars(|v| v.set_var(&var.to_string(), &val.to_string(), VarFlags::NONE)); write_vars(|v| v.set_var(&var.to_string(), &val.to_string(), VarFlags::NONE))?;
for_guard.vars.insert(var.to_string()); for_guard.vars.insert(var.to_string());
} }
@@ -769,6 +769,16 @@ impl Dispatcher {
"dirs" => dirs(cmd, io_stack_mut, curr_job_mut), "dirs" => dirs(cmd, io_stack_mut, curr_job_mut),
"exec" => exec::exec_builtin(cmd, io_stack_mut, curr_job_mut), "exec" => exec::exec_builtin(cmd, io_stack_mut, curr_job_mut),
"eval" => eval::eval(cmd, io_stack_mut, curr_job_mut), "eval" => eval::eval(cmd, io_stack_mut, curr_job_mut),
"readonly" => readonly(cmd, io_stack_mut, curr_job_mut),
"unset" => unset(cmd, io_stack_mut, curr_job_mut),
"true" | ":" => {
state::set_status(0);
Ok(())
},
"false" => {
state::set_status(1);
Ok(())
},
_ => unimplemented!( _ => unimplemented!(
"Have not yet added support for builtin '{}'", "Have not yet added support for builtin '{}'",
cmd_raw cmd_raw
@@ -885,7 +895,7 @@ impl Dispatcher {
let var = var.span.as_str(); let var = var.span.as_str();
let val = val.expand()?.get_words().join(" "); let val = val.expand()?.get_words().join(" ");
match kind { match kind {
AssignKind::Eq => write_vars(|v| v.set_var(var, &val, VarFlags::EXPORT)), AssignKind::Eq => write_vars(|v| v.set_var(var, &val, VarFlags::EXPORT))?,
AssignKind::PlusEq => todo!(), AssignKind::PlusEq => todo!(),
AssignKind::MinusEq => todo!(), AssignKind::MinusEq => todo!(),
AssignKind::MultEq => todo!(), AssignKind::MultEq => todo!(),
@@ -902,7 +912,7 @@ impl Dispatcher {
let var = var.span.as_str(); let var = var.span.as_str();
let val = val.expand()?.get_words().join(" "); let val = val.expand()?.get_words().join(" ");
match kind { match kind {
AssignKind::Eq => write_vars(|v| v.set_var(var, &val, VarFlags::NONE)), AssignKind::Eq => write_vars(|v| v.set_var(var, &val, VarFlags::NONE))?,
AssignKind::PlusEq => todo!(), AssignKind::PlusEq => todo!(),
AssignKind::MinusEq => todo!(), AssignKind::MinusEq => todo!(),
AssignKind::MultEq => todo!(), AssignKind::MultEq => todo!(),

View File

@@ -34,6 +34,6 @@ pub use nix::{
}; };
pub use crate::flog; pub use crate::flog;
pub use crate::libsh::flog::FernLogLevel::*; pub use crate::libsh::flog::ShedLogLevel::*;
// Additional utilities, if needed, can be added here // Additional utilities, if needed, can be added here

View File

@@ -226,7 +226,7 @@ impl History {
let max_hist = crate::state::read_shopts(|s| s.core.max_hist); let max_hist = crate::state::read_shopts(|s| s.core.max_hist);
let path = PathBuf::from(env::var("FERNHIST").unwrap_or({ let path = PathBuf::from(env::var("FERNHIST").unwrap_or({
let home = env::var("HOME").unwrap(); let home = env::var("HOME").unwrap();
format!("{home}/.fern_history") format!("{home}/.shed_history")
})); }));
let mut entries = read_hist_file(&path)?; let mut entries = read_hist_file(&path)?;
// Enforce max_hist limit on loaded entries // Enforce max_hist limit on loaded entries

View File

@@ -101,7 +101,7 @@ pub enum ReadlineEvent {
Pending, Pending,
} }
pub struct FernVi { pub struct ShedVi {
pub reader: PollReader, pub reader: PollReader,
pub writer: Box<dyn LineWriter>, pub writer: Box<dyn LineWriter>,
@@ -120,7 +120,7 @@ pub struct FernVi {
pub needs_redraw: bool, pub needs_redraw: bool,
} }
impl FernVi { impl ShedVi {
pub fn new(prompt: Option<String>, tty: RawFd) -> ShResult<Self> { pub fn new(prompt: Option<String>, tty: RawFd) -> ShResult<Self> {
let mut new = Self { let mut new = Self {
reader: PollReader::new(), reader: PollReader::new(),

View File

@@ -23,7 +23,7 @@ use crate::{
sys::TTY_FILENO, sys::TTY_FILENO,
}, },
prompt::readline::keys::{KeyCode, ModKeys}, prompt::readline::keys::{KeyCode, ModKeys},
shopt::FernBellStyle, shopt::ShedBellStyle,
state::read_shopts, state::read_shopts,
}; };
use crate::{ use crate::{

View File

@@ -6,13 +6,13 @@ use crate::{
}; };
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum FernBellStyle { pub enum ShedBellStyle {
Audible, Audible,
Visible, Visible,
Disable, Disable,
} }
impl FromStr for FernBellStyle { impl FromStr for ShedBellStyle {
type Err = ShErr; type Err = ShErr;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() { match s.to_ascii_lowercase().as_str() {
@@ -28,13 +28,13 @@ impl FromStr for FernBellStyle {
} }
#[derive(Default, Clone, Copy, Debug)] #[derive(Default, Clone, Copy, Debug)]
pub enum FernEditMode { pub enum ShedEditMode {
#[default] #[default]
Vi, Vi,
Emacs, Emacs,
} }
impl FromStr for FernEditMode { impl FromStr for ShedEditMode {
type Err = ShErr; type Err = ShErr;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() { match s.to_ascii_lowercase().as_str() {
@@ -48,11 +48,11 @@ impl FromStr for FernEditMode {
} }
} }
impl Display for FernEditMode { impl Display for ShedEditMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
FernEditMode::Vi => write!(f, "vi"), ShedEditMode::Vi => write!(f, "vi"),
FernEditMode::Emacs => write!(f, "emacs"), ShedEditMode::Emacs => write!(f, "emacs"),
} }
} }
} }
@@ -277,7 +277,7 @@ impl ShOptCore {
} }
"max_hist" => { "max_hist" => {
let mut output = String::from( let mut output = String::from(
"Maximum number of entries in the command history file (default '.fernhist')\n", "Maximum number of entries in the command history file (default '.shedhist')\n",
); );
output.push_str(&format!("{}", self.max_hist)); output.push_str(&format!("{}", self.max_hist));
Ok(Some(output)) Ok(Some(output))
@@ -295,7 +295,7 @@ impl ShOptCore {
Ok(Some(output)) Ok(Some(output))
} }
"bell_enabled" => { "bell_enabled" => {
let mut output = String::from("Whether or not to allow fern to trigger the terminal bell"); let mut output = String::from("Whether or not to allow shed to trigger the terminal bell");
output.push_str(&format!("{}", self.bell_enabled)); output.push_str(&format!("{}", self.bell_enabled));
Ok(Some(output)) Ok(Some(output))
} }
@@ -366,7 +366,7 @@ impl Default for ShOptCore {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ShOptPrompt { pub struct ShOptPrompt {
pub trunc_prompt_path: usize, pub trunc_prompt_path: usize,
pub edit_mode: FernEditMode, pub edit_mode: ShedEditMode,
pub comp_limit: usize, pub comp_limit: usize,
pub highlight: bool, pub highlight: bool,
pub auto_indent: bool, pub auto_indent: bool,
@@ -386,7 +386,7 @@ impl ShOptPrompt {
self.trunc_prompt_path = val; self.trunc_prompt_path = val;
} }
"edit_mode" => { "edit_mode" => {
let Ok(val) = val.parse::<FernEditMode>() else { let Ok(val) = val.parse::<ShedEditMode>() else {
return Err(ShErr::simple( return Err(ShErr::simple(
ShErrKind::SyntaxErr, ShErrKind::SyntaxErr,
"shopt: expected 'vi' or 'emacs' for edit_mode value", "shopt: expected 'vi' or 'emacs' for edit_mode value",
@@ -545,7 +545,7 @@ impl Default for ShOptPrompt {
fn default() -> Self { fn default() -> Self {
ShOptPrompt { ShOptPrompt {
trunc_prompt_path: 4, trunc_prompt_path: 4,
edit_mode: FernEditMode::Vi, edit_mode: ShedEditMode::Vi,
comp_limit: 100, comp_limit: 100,
highlight: true, highlight: true,
auto_indent: true, auto_indent: true,

View File

@@ -16,7 +16,7 @@ use crate::{
}, parse::{ConjunctNode, NdRule, Node, ParsedSrc}, prelude::*, shopt::ShOpts }, parse::{ConjunctNode, NdRule, Node, ParsedSrc}, prelude::*, shopt::ShOpts
}; };
pub struct Fern { pub struct Shed {
pub jobs: RefCell<JobTab>, pub jobs: RefCell<JobTab>,
pub var_scopes: RefCell<ScopeStack>, pub var_scopes: RefCell<ScopeStack>,
pub meta: RefCell<MetaTab>, pub meta: RefCell<MetaTab>,
@@ -24,7 +24,7 @@ pub struct Fern {
pub shopts: RefCell<ShOpts>, pub shopts: RefCell<ShOpts>,
} }
impl Fern { impl Shed {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
jobs: RefCell::new(JobTab::new()), jobs: RefCell::new(JobTab::new()),
@@ -36,7 +36,7 @@ impl Fern {
} }
} }
impl Default for Fern { impl Default for Shed {
fn default() -> Self { fn default() -> Self {
Self::new() Self::new()
} }
@@ -123,7 +123,7 @@ impl ScopeStack {
new.scopes.push(VarTab::new()); new.scopes.push(VarTab::new());
let shell_name = std::env::args() let shell_name = std::env::args()
.next() .next()
.unwrap_or_else(|| "fern".to_string()); .unwrap_or_else(|| "shed".to_string());
new new
.global_params .global_params
.insert(ShellParam::ShellName.to_string(), shell_name); .insert(ShellParam::ShellName.to_string(), shell_name);
@@ -151,13 +151,16 @@ impl ScopeStack {
pub fn cur_scope_mut(&mut self) -> &mut VarTab { pub fn cur_scope_mut(&mut self) -> &mut VarTab {
self.scopes.last_mut().unwrap() self.scopes.last_mut().unwrap()
} }
pub fn unset_var(&mut self, var_name: &str) { pub fn unset_var(&mut self, var_name: &str) -> ShResult<()> {
for scope in self.scopes.iter_mut().rev() { for scope in self.scopes.iter_mut().rev() {
if scope.var_exists(var_name) { if scope.var_exists(var_name) {
scope.unset_var(var_name); return scope.unset_var(var_name);
return;
} }
} }
Err(ShErr::simple(
ShErrKind::ExecFail,
format!("Variable '{}' not found", var_name)
))
} }
pub fn export_var(&mut self, var_name: &str) { pub fn export_var(&mut self, var_name: &str) {
for scope in self.scopes.iter_mut().rev() { for scope in self.scopes.iter_mut().rev() {
@@ -187,22 +190,26 @@ impl ScopeStack {
} }
flat_vars flat_vars
} }
pub fn set_var(&mut self, var_name: &str, val: &str, flags: VarFlags) { pub fn set_var(&mut self, var_name: &str, val: &str, flags: VarFlags) -> ShResult<()> {
let is_local = self.is_local_var(var_name); let is_local = self.is_local_var(var_name);
if flags.contains(VarFlags::LOCAL) || is_local { if flags.contains(VarFlags::LOCAL) || is_local {
self.set_var_local(var_name, val, flags); self.set_var_local(var_name, val, flags)
} else { } else {
self.set_var_global(var_name, val, flags); self.set_var_global(var_name, val, flags)
} }
} }
fn set_var_global(&mut self, var_name: &str, val: &str, flags: VarFlags) { fn set_var_global(&mut self, var_name: &str, val: &str, flags: VarFlags) -> ShResult<()> {
if let Some(scope) = self.scopes.first_mut() { if let Some(scope) = self.scopes.first_mut() {
scope.set_var(var_name, val, flags); scope.set_var(var_name, val, flags)
} else {
Ok(())
} }
} }
fn set_var_local(&mut self, var_name: &str, val: &str, flags: VarFlags) { fn set_var_local(&mut self, var_name: &str, val: &str, flags: VarFlags) -> ShResult<()> {
if let Some(scope) = self.scopes.last_mut() { if let Some(scope) = self.scopes.last_mut() {
scope.set_var(var_name, val, flags); scope.set_var(var_name, val, flags)
} else {
Ok(())
} }
} }
pub fn get_var(&self, var_name: &str) -> String { pub fn get_var(&self, var_name: &str) -> String {
@@ -266,7 +273,7 @@ impl ScopeStack {
} }
thread_local! { thread_local! {
pub static FERN: Fern = Fern::new(); pub static FERN: Shed = Shed::new();
} }
/// A shell function /// A shell function
@@ -477,6 +484,9 @@ impl Var {
pub fn mark_for_export(&mut self) { pub fn mark_for_export(&mut self) {
self.flags.set(VarFlags::EXPORT, true); self.flags.set(VarFlags::EXPORT, true);
} }
pub fn flags(&self) -> VarFlags {
self.flags
}
} }
impl Display for Var { impl Display for Var {
@@ -559,8 +569,8 @@ impl VarTab {
env::set_var("OLDPWD", pathbuf_to_string(std::env::current_dir())); env::set_var("OLDPWD", pathbuf_to_string(std::env::current_dir()));
env::set_var("HOME", home.clone()); env::set_var("HOME", home.clone());
env::set_var("SHELL", pathbuf_to_string(std::env::current_exe())); env::set_var("SHELL", pathbuf_to_string(std::env::current_exe()));
env::set_var("FERN_HIST", format!("{}/.fernhist", home)); env::set_var("FERN_HIST", format!("{}/.shedhist", home));
env::set_var("FERN_RC", format!("{}/.fernrc", home)); env::set_var("FERN_RC", format!("{}/.shedrc", home));
} }
} }
pub fn init_sh_argv(&mut self) { pub fn init_sh_argv(&mut self) {
@@ -654,13 +664,27 @@ impl VarTab {
pub fn get_var_flags(&self, var_name: &str) -> Option<VarFlags> { pub fn get_var_flags(&self, var_name: &str) -> Option<VarFlags> {
self.vars.get(var_name).map(|var| var.flags) self.vars.get(var_name).map(|var| var.flags)
} }
pub fn unset_var(&mut self, var_name: &str) { pub fn unset_var(&mut self, var_name: &str) -> ShResult<()> {
if let Some(var) = self.vars.get(var_name) && var.flags.contains(VarFlags::READONLY) {
return Err(ShErr::simple(
ShErrKind::ExecFail,
format!("cannot unset readonly variable '{}'", var_name)
));
}
self.vars.remove(var_name); self.vars.remove(var_name);
unsafe { env::remove_var(var_name) }; unsafe { env::remove_var(var_name) };
Ok(())
} }
pub fn set_var(&mut self, var_name: &str, val: &str, flags: VarFlags) { pub fn set_var(&mut self, var_name: &str, val: &str, flags: VarFlags) -> ShResult<()> {
if let Some(var) = self.vars.get_mut(var_name) { if let Some(var) = self.vars.get_mut(var_name) {
if var.flags.contains(VarFlags::READONLY) && !flags.contains(VarFlags::READONLY) {
return Err(ShErr::simple(
ShErrKind::ExecFail,
format!("Variable '{}' is readonly", var_name)
));
}
var.kind = VarKind::Str(val.to_string()); var.kind = VarKind::Str(val.to_string());
var.flags |= flags;
if var.flags.contains(VarFlags::EXPORT) || flags.contains(VarFlags::EXPORT) { if var.flags.contains(VarFlags::EXPORT) || flags.contains(VarFlags::EXPORT) {
if flags.contains(VarFlags::EXPORT) && !var.flags.contains(VarFlags::EXPORT) { if flags.contains(VarFlags::EXPORT) && !var.flags.contains(VarFlags::EXPORT) {
var.mark_for_export(); var.mark_for_export();
@@ -675,6 +699,7 @@ impl VarTab {
} }
self.vars.insert(var_name.to_string(), var); self.vars.insert(var_name.to_string(), var);
} }
Ok(())
} }
pub fn var_exists(&self, var_name: &str) -> bool { pub fn var_exists(&self, var_name: &str) -> bool {
if let Ok(param) = var_name.parse::<ShellParam>() { if let Ok(param) = var_name.parse::<ShellParam>() {
@@ -778,49 +803,49 @@ impl MetaTab {
/// Read from the job table /// Read from the job table
pub fn read_jobs<T, F: FnOnce(&JobTab) -> T>(f: F) -> T { pub fn read_jobs<T, F: FnOnce(&JobTab) -> T>(f: F) -> T {
FERN.with(|fern| f(&fern.jobs.borrow())) FERN.with(|shed| f(&shed.jobs.borrow()))
} }
/// Write to the job table /// Write to the job table
pub fn write_jobs<T, F: FnOnce(&mut JobTab) -> T>(f: F) -> T { pub fn write_jobs<T, F: FnOnce(&mut JobTab) -> T>(f: F) -> T {
FERN.with(|fern| f(&mut fern.jobs.borrow_mut())) FERN.with(|shed| f(&mut shed.jobs.borrow_mut()))
} }
/// Read from the var scope stack /// Read from the var scope stack
pub fn read_vars<T, F: FnOnce(&ScopeStack) -> T>(f: F) -> T { pub fn read_vars<T, F: FnOnce(&ScopeStack) -> T>(f: F) -> T {
FERN.with(|fern| f(&fern.var_scopes.borrow())) FERN.with(|shed| f(&shed.var_scopes.borrow()))
} }
/// Write to the variable table /// Write to the variable table
pub fn write_vars<T, F: FnOnce(&mut ScopeStack) -> T>(f: F) -> T { pub fn write_vars<T, F: FnOnce(&mut ScopeStack) -> T>(f: F) -> T {
FERN.with(|fern| f(&mut fern.var_scopes.borrow_mut())) FERN.with(|shed| f(&mut shed.var_scopes.borrow_mut()))
} }
pub fn read_meta<T, F: FnOnce(&MetaTab) -> T>(f: F) -> T { pub fn read_meta<T, F: FnOnce(&MetaTab) -> T>(f: F) -> T {
FERN.with(|fern| f(&fern.meta.borrow())) FERN.with(|shed| f(&shed.meta.borrow()))
} }
/// Write to the meta table /// Write to the meta table
pub fn write_meta<T, F: FnOnce(&mut MetaTab) -> T>(f: F) -> T { pub fn write_meta<T, F: FnOnce(&mut MetaTab) -> T>(f: F) -> T {
FERN.with(|fern| f(&mut fern.meta.borrow_mut())) FERN.with(|shed| f(&mut shed.meta.borrow_mut()))
} }
/// Read from the logic table /// Read from the logic table
pub fn read_logic<T, F: FnOnce(&LogTab) -> T>(f: F) -> T { pub fn read_logic<T, F: FnOnce(&LogTab) -> T>(f: F) -> T {
FERN.with(|fern| f(&fern.logic.borrow())) FERN.with(|shed| f(&shed.logic.borrow()))
} }
/// Write to the logic table /// Write to the logic table
pub fn write_logic<T, F: FnOnce(&mut LogTab) -> T>(f: F) -> T { pub fn write_logic<T, F: FnOnce(&mut LogTab) -> T>(f: F) -> T {
FERN.with(|fern| f(&mut fern.logic.borrow_mut())) FERN.with(|shed| f(&mut shed.logic.borrow_mut()))
} }
pub fn read_shopts<T, F: FnOnce(&ShOpts) -> T>(f: F) -> T { pub fn read_shopts<T, F: FnOnce(&ShOpts) -> T>(f: F) -> T {
FERN.with(|fern| f(&fern.shopts.borrow())) FERN.with(|shed| f(&shed.shopts.borrow()))
} }
pub fn write_shopts<T, F: FnOnce(&mut ShOpts) -> T>(f: F) -> T { pub fn write_shopts<T, F: FnOnce(&mut ShOpts) -> T>(f: F) -> T {
FERN.with(|fern| f(&mut fern.shopts.borrow_mut())) FERN.with(|shed| f(&mut shed.shopts.borrow_mut()))
} }
pub fn descend_scope(argv: Option<Vec<String>>) { pub fn descend_scope(argv: Option<Vec<String>>) {
@@ -852,10 +877,10 @@ pub fn source_rc() -> ShResult<()> {
PathBuf::from(&path) PathBuf::from(&path)
} else { } else {
let home = env::var("HOME").unwrap(); let home = env::var("HOME").unwrap();
PathBuf::from(format!("{home}/.fernrc")) PathBuf::from(format!("{home}/.shedrc"))
}; };
if !path.exists() { if !path.exists() {
return Err(ShErr::simple(ShErrKind::InternalErr, ".fernrc not found")); return Err(ShErr::simple(ShErrKind::InternalErr, ".shedrc not found"));
} }
source_file(path) source_file(path)
} }

View File

@@ -11,7 +11,7 @@ use crate::{
linebuf::LineBuf, linebuf::LineBuf,
term::{raw_mode, KeyReader, LineWriter}, term::{raw_mode, KeyReader, LineWriter},
vimode::{ViInsert, ViMode, ViNormal}, vimode::{ViInsert, ViMode, ViNormal},
FernVi, ShedVi,
}, },
}; };
@@ -180,11 +180,11 @@ impl LineWriter for TestWriter {
} }
} }
// NOTE: FernVi structure has changed significantly and readline() method no // NOTE: ShedVi structure has changed significantly and readline() method no
// longer exists These test helpers are disabled until they can be properly // longer exists These test helpers are disabled until they can be properly
// updated // updated
/* /*
impl FernVi { impl ShedVi {
pub fn new_test(prompt: Option<String>, input: &str, initial: &str) -> Self { pub fn new_test(prompt: Option<String>, input: &str, initial: &str) -> Self {
Self { Self {
reader: Box::new(TestReader::new().with_initial(input.as_bytes())), reader: Box::new(TestReader::new().with_initial(input.as_bytes())),
@@ -200,10 +200,10 @@ impl FernVi {
} }
} }
fn fernvi_test(input: &str, initial: &str) -> String { fn shedvi_test(input: &str, initial: &str) -> String {
let mut fernvi = FernVi::new_test(None, input, initial); let mut shedvi = ShedVi::new_test(None, input, initial);
let raw_mode = raw_mode(); let raw_mode = raw_mode();
let line = fernvi.readline().unwrap(); let line = shedvi.readline().unwrap();
std::mem::drop(raw_mode); std::mem::drop(raw_mode);
line line
} }
@@ -642,24 +642,24 @@ fn editor_f_char_from_position_zero() {
); );
} }
// NOTE: These tests disabled because fernvi_test() helper is commented out // NOTE: These tests disabled because shedvi_test() helper is commented out
/* /*
#[test] #[test]
fn fernvi_test_simple() { fn shedvi_test_simple() {
assert_eq!(fernvi_test("foo bar\x1bbdw\r", ""), "foo ") assert_eq!(shedvi_test("foo bar\x1bbdw\r", ""), "foo ")
} }
#[test] #[test]
fn fernvi_test_mode_change() { fn shedvi_test_mode_change() {
assert_eq!( assert_eq!(
fernvi_test("foo bar biz buzz\x1bbbb2cwbiz buzz bar\r", ""), shedvi_test("foo bar biz buzz\x1bbbb2cwbiz buzz bar\r", ""),
"foo biz buzz bar buzz" "foo biz buzz bar buzz"
) )
} }
#[test] #[test]
fn fernvi_test_lorem_ipsum_1() { fn shedvi_test_lorem_ipsum_1() {
assert_eq!(fernvi_test( assert_eq!(shedvi_test(
"\x1bwwwwwwww5dWdBdBjjdwjdwbbbcwasdasdasdasd\x1b\r", "\x1bwwwwwwww5dWdBdBjjdwjdwbbbcwasdasdasdasd\x1b\r",
LOREM_IPSUM), LOREM_IPSUM),
"Lorem ipsum dolor sit amet, incididunt ut labore et dolore magna aliqua.\nUt enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\nDuis aute irure dolor in repin voluptate velit esse cillum dolore eu fugiat nulla pariatur.\nExcepteur asdasdasdasd occaecat cupinon proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\nCurabitur pretium tincidunt lacus. Nulla gravida orci a odio. Nullam varius, turpis et commodo pharetra." "Lorem ipsum dolor sit amet, incididunt ut labore et dolore magna aliqua.\nUt enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\nDuis aute irure dolor in repin voluptate velit esse cillum dolore eu fugiat nulla pariatur.\nExcepteur asdasdasdasd occaecat cupinon proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\nCurabitur pretium tincidunt lacus. Nulla gravida orci a odio. Nullam varius, turpis et commodo pharetra."
@@ -667,9 +667,9 @@ fn fernvi_test_lorem_ipsum_1() {
} }
#[test] #[test]
fn fernvi_test_lorem_ipsum_undo() { fn shedvi_test_lorem_ipsum_undo() {
assert_eq!( assert_eq!(
fernvi_test( shedvi_test(
"\x1bwwwwwwwwainserting some characters now...\x1bu\r", "\x1bwwwwwwwwainserting some characters now...\x1bu\r",
LOREM_IPSUM LOREM_IPSUM
), ),
@@ -678,8 +678,8 @@ fn fernvi_test_lorem_ipsum_undo() {
} }
#[test] #[test]
fn fernvi_test_lorem_ipsum_ctrl_w() { fn shedvi_test_lorem_ipsum_ctrl_w() {
assert_eq!(fernvi_test( assert_eq!(shedvi_test(
"\x1bj5wiasdasdkjhaksjdhkajshd\x17wordswordswords\x17somemorewords\x17\x1b[D\x1b[D\x17\x1b\r", "\x1bj5wiasdasdkjhaksjdhkajshd\x17wordswordswords\x17somemorewords\x17\x1b[D\x1b[D\x17\x1b\r",
LOREM_IPSUM), LOREM_IPSUM),
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\nUt enim ad minim am, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\nExcepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\nCurabitur pretium tincidunt lacus. Nulla gravida orci a odio. Nullam varius, turpis et commodo pharetra." "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\nUt enim ad minim am, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\nExcepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\nCurabitur pretium tincidunt lacus. Nulla gravida orci a odio. Nullam varius, turpis et commodo pharetra."

View File

@@ -4,25 +4,25 @@ use pretty_assertions::assert_eq;
use super::super::*; use super::super::*;
fn get_script_output(name: &str, args: &[&str]) -> Output { fn get_script_output(name: &str, args: &[&str]) -> Output {
// Resolve the path to the fern binary. // Resolve the path to the shed binary.
// Do not question me. // Do not question me.
let mut fern_path = env::current_exe().expect("Failed to get test executable"); // The path to the test executable let mut shed_path = env::current_exe().expect("Failed to get test executable"); // The path to the test executable
fern_path.pop(); // Hocus pocus shed_path.pop(); // Hocus pocus
fern_path.pop(); shed_path.pop();
fern_path.push("fern"); // Abra Kadabra shed_path.push("shed"); // Abra Kadabra
if !fern_path.is_file() { if !shed_path.is_file() {
fern_path.pop(); shed_path.pop();
fern_path.pop(); shed_path.pop();
fern_path.push("release"); shed_path.push("release");
fern_path.push("fern"); shed_path.push("shed");
} }
if !fern_path.is_file() { if !shed_path.is_file() {
panic!("where the hell is the binary") panic!("where the hell is the binary")
} }
process::Command::new(fern_path) // Alakazam process::Command::new(shed_path) // Alakazam
.arg(name) .arg(name)
.args(args) .args(args)
.output() .output()