use crate::errors::{error, nil, New};
use crate::types::{int, slice, string};
pub mod exec;
#[allow(non_snake_case)]
pub fn Args() -> slice<string> {
std::env::args().map(string::from).collect()
}
#[allow(non_snake_case)]
pub fn Getenv(key: impl AsRef<str>) -> string {
std::env::var(key.as_ref()).map(string::from).unwrap_or_default()
}
#[allow(non_snake_case)]
pub fn LookupEnv(key: impl AsRef<str>) -> (string, bool) {
match std::env::var(key.as_ref()) {
Ok(v) => (v.into(), true),
Err(_) => ("".into(), false),
}
}
#[allow(non_snake_case)]
pub fn Setenv(key: impl AsRef<str>, val: impl AsRef<str>) -> error {
unsafe {
std::env::set_var(key.as_ref(), val.as_ref());
}
nil
}
#[allow(non_snake_case)]
pub fn Unsetenv(key: impl AsRef<str>) -> error {
unsafe {
std::env::remove_var(key.as_ref());
}
nil
}
#[allow(non_snake_case)]
pub fn Exit(code: int) -> ! {
std::process::exit(code as i32)
}
#[allow(non_snake_case)]
pub fn Environ() -> slice<string> {
std::env::vars().map(|(k, v)| string::from(format!("{}={}", k, v))).collect()
}
#[allow(non_snake_case)]
pub fn Clearenv() {
let keys: Vec<String> = std::env::vars().map(|(k, _)| k).collect();
for k in keys {
unsafe { std::env::remove_var(k); }
}
}
#[allow(non_snake_case)]
pub fn Expand(s: impl AsRef<str>, mapping: impl Fn(&str) -> string) -> string {
let s = s.as_ref();
let bytes = s.as_bytes();
let mut out = std::string::String::with_capacity(s.len());
let mut i = 0;
while i < bytes.len() {
if bytes[i] == b'$' && i + 1 < bytes.len() {
let (name, w) = get_shell_name(&bytes[i + 1..]);
if w == 0 {
out.push('$');
i += 1;
continue;
}
if !name.is_empty() {
out.push_str(&mapping(&name));
}
i += 1 + w;
continue;
}
out.push(bytes[i] as char);
i += 1;
}
out.into()
}
#[allow(non_snake_case)]
pub fn ExpandEnv(s: impl AsRef<str>) -> string {
Expand(s, |k| Getenv(k))
}
fn get_shell_name(s: &[u8]) -> (String, usize) {
if s.is_empty() { return (String::new(), 0); }
if s[0] == b'{' {
if s.len() < 2 { return (String::new(), 1); }
if s[1] == b'}' { return (String::new(), 2); } let mut i = 1;
while i < s.len() && s[i] != b'}' {
if !is_shell_special_var(s[i]) && !is_alpha_num(s[i]) {
return (String::new(), i + 1);
}
i += 1;
}
if i >= s.len() {
return (String::new(), s.len());
}
let name = std::str::from_utf8(&s[1..i]).unwrap_or("").into();
(name, i + 1)
} else if is_shell_special_var(s[0]) {
let name = (s[0] as char).into();
(name, 1)
} else if is_alpha_num(s[0]) {
let mut i = 0;
while i < s.len() && is_alpha_num(s[i]) { i += 1; }
let name = std::str::from_utf8(&s[..i]).unwrap_or("").into();
(name, i)
} else {
(String::new(), 0)
}
}
fn is_shell_special_var(c: u8) -> bool {
matches!(c, b'*' | b'#' | b'$' | b'@' | b'!' | b'?' | b'-') || (c >= b'0' && c <= b'9')
}
fn is_alpha_num(c: u8) -> bool {
c == b'_' || (c >= b'a' && c <= b'z') || (c >= b'A' && c <= b'Z') || (c >= b'0' && c <= b'9')
}
#[allow(non_snake_case)]
pub fn Hostname() -> (string, error) {
if let Ok(v) = std::env::var("HOSTNAME") {
if !v.is_empty() {
return (v.into(), nil);
}
}
match std::fs::read_to_string("/etc/hostname") {
Ok(s) => (s.trim().into(), nil),
Err(e) => ("".into(), New(&format!("os.Hostname: {}", e))),
}
}
#[allow(non_snake_case)]
pub fn Getwd() -> (string, error) {
match std::env::current_dir() {
Ok(p) => (p.to_string_lossy().into_owned().into(), nil),
Err(e) => ("".into(), New(&format!("os.Getwd: {}", e))),
}
}
#[allow(non_snake_case)]
pub fn Chdir(path: impl AsRef<str>) -> error {
match std::env::set_current_dir(path.as_ref()) {
Ok(()) => nil,
Err(e) => New(&format!("os.Chdir: {}", e)),
}
}
#[allow(non_snake_case)]
pub fn ReadFile(path: impl AsRef<str>) -> (Vec<crate::types::byte>, error) {
match std::fs::read(path.as_ref()) {
Ok(b) => (b, nil),
Err(e) => (Vec::new(), New(&format!("os.ReadFile: {}", e))),
}
}
#[allow(non_snake_case)]
pub fn WriteFile(path: impl AsRef<str>, data: &[crate::types::byte], _perm: u32) -> error {
match std::fs::write(path.as_ref(), data) {
Ok(()) => nil,
Err(e) => New(&format!("os.WriteFile: {}", e)),
}
}
#[allow(non_snake_case)]
pub fn Remove(path: impl AsRef<str>) -> error {
let p = path.as_ref();
match std::fs::remove_file(p) {
Ok(()) => nil,
Err(_) => match std::fs::remove_dir(p) {
Ok(()) => nil,
Err(e) => New(&format!("os.Remove: {}", e)),
},
}
}
#[allow(non_snake_case)]
pub fn RemoveAll(path: impl AsRef<str>) -> error {
let p = path.as_ref();
let md = match std::fs::metadata(p) {
Ok(m) => m,
Err(_) => return nil, };
let r = if md.is_dir() {
std::fs::remove_dir_all(p)
} else {
std::fs::remove_file(p)
};
match r {
Ok(()) => nil,
Err(e) => New(&format!("os.RemoveAll: {}", e)),
}
}
#[allow(non_snake_case)]
pub fn Mkdir(path: impl AsRef<str>, _perm: u32) -> error {
match std::fs::create_dir(path.as_ref()) {
Ok(()) => nil,
Err(e) => New(&format!("os.Mkdir: {}", e)),
}
}
#[allow(non_snake_case)]
pub fn MkdirAll(path: impl AsRef<str>, _perm: u32) -> error {
match std::fs::create_dir_all(path.as_ref()) {
Ok(()) => nil,
Err(e) => New(&format!("os.MkdirAll: {}", e)),
}
}
#[allow(non_snake_case)]
pub fn TempDir() -> string {
std::env::temp_dir().to_string_lossy().into_owned().into()
}
use std::io::{Read as _, Seek as _, Write as _};
pub struct File {
inner: Option<std::fs::File>,
name: string,
}
impl File {
#[allow(non_snake_case)]
pub fn Name(&self) -> string {
self.name.clone()
}
#[allow(non_snake_case)]
pub fn Read(&mut self, buf: &mut [crate::types::byte]) -> (int, error) {
let f = match self.inner.as_mut() {
Some(f) => f,
None => return (0, New("os.File: already closed")),
};
match f.read(buf) {
Ok(0) if !buf.is_empty() => (0, New("EOF")),
Ok(n) => (n as int, nil),
Err(e) => (0, New(&format!("os.File.Read: {}", e))),
}
}
#[allow(non_snake_case)]
pub fn Write(&mut self, data: &[crate::types::byte]) -> (int, error) {
let f = match self.inner.as_mut() {
Some(f) => f,
None => return (0, New("os.File: already closed")),
};
match f.write(data) {
Ok(n) => (n as int, nil),
Err(e) => (0, New(&format!("os.File.Write: {}", e))),
}
}
#[allow(non_snake_case)]
pub fn Close(&mut self) -> error {
self.inner = None;
nil
}
#[allow(non_snake_case)]
pub fn Sync(&self) -> error {
match &self.inner {
Some(f) => match f.sync_all() {
Ok(()) => nil,
Err(e) => New(&format!("os.File.Sync: {}", e)),
},
None => New("os.File: already closed"),
}
}
#[allow(non_snake_case)]
pub fn Seek(&mut self, offset: int, whence: int) -> (crate::types::int64, error) {
let f = match self.inner.as_mut() {
Some(f) => f,
None => return (0, New("os.File: already closed")),
};
let pos = match whence {
0 => std::io::SeekFrom::Start(offset as u64),
1 => std::io::SeekFrom::Current(offset as i64),
2 => std::io::SeekFrom::End(offset as i64),
_ => return (0, New(&format!("os.File.Seek: invalid whence {}", whence))),
};
match f.seek(pos) {
Ok(n) => (n as crate::types::int64, nil),
Err(e) => (0, New(&format!("os.File.Seek: {}", e))),
}
}
}
impl std::io::Read for File {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
match self.inner.as_mut() {
Some(f) => f.read(buf),
None => Err(std::io::Error::new(std::io::ErrorKind::Other, "closed")),
}
}
}
impl std::io::Write for File {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
match self.inner.as_mut() {
Some(f) => f.write(buf),
None => Err(std::io::Error::new(std::io::ErrorKind::Other, "closed")),
}
}
fn flush(&mut self) -> std::io::Result<()> {
match self.inner.as_mut() {
Some(f) => f.flush(),
None => Ok(()),
}
}
}
#[allow(non_snake_case)]
pub fn Open(path: impl AsRef<str>) -> (File, error) {
let p = path.as_ref();
match std::fs::File::open(p) {
Ok(f) => (File { inner: Some(f), name: p.into() }, nil),
Err(e) => (
File { inner: None, name: p.into() },
New(&format!("os.Open: {}", e)),
),
}
}
#[allow(non_snake_case)]
pub fn Create(path: impl AsRef<str>) -> (File, error) {
let p = path.as_ref();
match std::fs::File::create(p) {
Ok(f) => (File { inner: Some(f), name: p.into() }, nil),
Err(e) => (
File { inner: None, name: p.into() },
New(&format!("os.Create: {}", e)),
),
}
}
#[allow(non_snake_case)]
pub fn Stdin() -> std::io::StdinLock<'static> {
std::io::stdin().lock()
}
#[allow(non_snake_case)]
pub fn Stdout() -> std::io::Stdout {
std::io::stdout()
}
#[allow(non_snake_case)]
pub fn Stderr() -> std::io::Stderr {
std::io::stderr()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn args_has_at_least_program_name() {
let a = Args();
assert!(!a.is_empty());
}
#[test]
fn getenv_missing_returns_empty() {
let v = Getenv("GOISH_DEFINITELY_NOT_SET_12345");
assert_eq!(v, "");
}
#[test]
fn setenv_getenv_roundtrip() {
let key = "GOISH_TEST_SETENV_1";
let _ = Setenv(key, "hello");
assert_eq!(Getenv(key), "hello");
let _ = Unsetenv(key);
assert_eq!(Getenv(key), "");
}
#[test]
fn lookupenv_ok_flag() {
let key = "GOISH_TEST_LOOKUP_1";
let (_, ok) = LookupEnv(key);
assert!(!ok);
let _ = Setenv(key, "x");
let (v, ok) = LookupEnv(key);
assert!(ok);
assert_eq!(v, "x");
let _ = Unsetenv(key);
}
#[test]
fn getwd_not_empty() {
let (dir, err) = Getwd();
assert!(err == nil);
assert!(!dir.is_empty());
}
#[test]
fn write_read_remove_file() {
let tmp = TempDir();
let path = format!("{}/goish_test_os_rw.txt", tmp);
let err = WriteFile(&path, b"hello goish", 0o644);
assert!(err == nil);
let (data, err) = ReadFile(&path);
assert!(err == nil);
assert_eq!(data, b"hello goish");
let err = Remove(&path);
assert!(err == nil);
}
#[test]
fn mkdir_removeall_roundtrip() {
let tmp = TempDir();
let path = format!("{}/goish_test_os_mkdir/nested/deep", tmp);
let err = MkdirAll(&path, 0o755);
assert!(err == nil);
let top = format!("{}/goish_test_os_mkdir", tmp);
let err = RemoveAll(&top);
assert!(err == nil);
}
#[test]
fn file_create_write_open_read_remove() {
let tmp = TempDir();
let path = format!("{}/goish_test_file.txt", tmp);
let (mut f, err) = Create(&path);
assert!(err == nil);
assert_eq!(f.Name(), path);
let (n, err) = f.Write(b"hello file");
assert!(err == nil);
assert_eq!(n, 10);
let err = f.Close();
assert!(err == nil);
let (mut f, err) = Open(&path);
assert!(err == nil);
let mut buf = [0u8; 20];
let (n, err) = f.Read(&mut buf);
assert!(err == nil);
assert_eq!(&buf[..n as usize], b"hello file");
let _ = f.Close();
let _ = Remove(&path);
}
#[test]
fn file_seek() {
let tmp = TempDir();
let path = format!("{}/goish_test_seek.txt", tmp);
let (mut f, _) = Create(&path);
let _ = f.Write(b"0123456789");
let (pos, err) = f.Seek(3, 0); assert!(err == nil);
assert_eq!(pos, 3);
let _ = f.Write(b"XY");
let _ = f.Close();
let (mut f, _) = Open(&path);
let mut buf = [0u8; 10];
let (_, _) = f.Read(&mut buf);
assert_eq!(&buf, b"012XY56789");
let _ = f.Close();
let _ = Remove(&path);
}
}