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().collect()
}
#[allow(non_snake_case)]
pub fn Getenv(key: impl AsRef<str>) -> string {
std::env::var(key.as_ref()).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, true),
Err(_) => (String::new(), 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 Hostname() -> (string, error) {
if let Ok(v) = std::env::var("HOSTNAME") {
if !v.is_empty() {
return (v, nil);
}
}
match std::fs::read_to_string("/etc/hostname") {
Ok(s) => (s.trim().to_string(), nil),
Err(e) => (String::new(), 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(), nil),
Err(e) => (String::new(), 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()
}
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.to_string() }, nil),
Err(e) => (
File { inner: None, name: p.to_string() },
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.to_string() }, nil),
Err(e) => (
File { inner: None, name: p.to_string() },
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);
}
}