/* Flesh: Flexible Shell * Copyright (C) 2021 Ava Hahn * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ use flesh::ast::{ start_trace, Ctr, Seg, Type, SymTable, Symbol, ValueType, Args, Traceback, }; use std::io::Write; use std::fs::{File, read_to_string, OpenOptions}; use std::rc::Rc; use std::path::Path; const READ_TO_STRING_DOCSTRING: &str = "Takes one input (filename). If file exists, returns a string containing file contents. If the file does not exist returns error."; fn read_to_string_callback(ast: &Seg, _syms: &mut SymTable) -> Result { if let Ctr::String(ref filename) = *ast.car { let res = read_to_string(filename); if let Ok(s) = res { Ok(Ctr::String(s)) } else { Err(start_trace( ("read-file", res.err().unwrap().to_string()) .into())) } } else { Err(start_trace(("read-file", "impossible arg").into())) } } const WRITE_TO_FILE_DOCSTRING: &str = "Takes two inputs: a filename and a string of content. Writes contents to the file and returns None."; fn write_to_file_callback(ast: &Seg, _syms: &mut SymTable) -> Result { if let Ctr::String(ref filename) = *ast.car { if let Ctr::Seg(ref next) = *ast.cdr { if let Ctr::String(ref body) = *next.car { let fres = OpenOptions::new() .create(true) .truncate(true) .write(true) .open(filename); if fres.is_err() { Err(start_trace( ("write-file", format!("couldn't open file: {}", fres.err().unwrap().to_string())) .into())) } else { if let Err(e) = write!(&mut fres.unwrap(), "{}", body) { Err(start_trace( ("write-file", format!("failed to write to file: {}", e)) .into())) } else { Ok(Ctr::None) } } } else { Err(start_trace(("write-file", "impossible arg").into())) } } else { Err(start_trace(("write-file", "not enough args").into())) } } else { Err(start_trace(("write-file", "impossible arg").into())) } } const APPEND_TO_FILE_DOCSTRING: &str = "Takes two inputs: a filename and a string of content. Appends content to the end of the file and returns None"; fn append_to_file_callback(ast: &Seg, _syms: &mut SymTable) -> Result { if let Ctr::String(ref filename) = *ast.car { if let Ctr::Seg(ref next) = *ast.cdr { if let Ctr::String(ref body) = *next.car { let fres = File::options().append(true).open(filename); if fres.is_err() { Err(start_trace( ("append-file", format!("couldn't open file: {}", fres.err().unwrap().to_string())) .into())) } else { if let Err(e) = write!(&mut fres.unwrap(), "{}", body) { Err(start_trace( ("append-file", format!("failed to write to file: {}", e)) .into())) } else { Ok(Ctr::None) } } } else { Err(start_trace(("append-file", "impossible arg").into())) } } else { Err(start_trace(("append-file", "not enough args").into())) } } else { Err(start_trace(("append-file", "impossible arg").into())) } } const IS_FILE_EXISTS_DOCSTRING: &str = "Takes one input: a filename. Returns true or false depending on if the file exists."; fn is_file_exists_callback(ast: &Seg, _syms: &mut SymTable) -> Result { if let Ctr::String(ref filename) = *ast.car { Ok(Ctr::Bool(Path::new(&filename).exists())) } else { Err(Traceback::new().with_trace(("exists?", "impossible arg").into())) } } pub fn add_file_lib(syms: &mut SymTable) { syms.insert( "read-file".to_string(), Symbol { name: String::from("read-file"), args: Args::Strict(vec![Type::String]), conditional_branches: false, docs: READ_TO_STRING_DOCSTRING.to_string(), value: ValueType::Internal(Rc::new(read_to_string_callback)), ..Default::default() } ); syms.insert( "write-file".to_string(), Symbol { name: String::from("write-file"), args: Args::Strict(vec![Type::String, Type::String]), conditional_branches: false, docs: WRITE_TO_FILE_DOCSTRING.to_string(), value: ValueType::Internal(Rc::new(write_to_file_callback)), ..Default::default() } ); syms.insert( "append-file".to_string(), Symbol { name: String::from("append-file"), args: Args::Strict(vec![Type::String, Type::String]), conditional_branches: false, docs: APPEND_TO_FILE_DOCSTRING.to_string(), value: ValueType::Internal(Rc::new(append_to_file_callback)), ..Default::default() } ); syms.insert( "exists?".to_string(), Symbol { name: String::from("exists?"), args: Args::Strict(vec![Type::String]), conditional_branches: false, docs: IS_FILE_EXISTS_DOCSTRING.to_string(), value: ValueType::Internal(Rc::new(is_file_exists_callback)), ..Default::default() } ); } #[cfg(test)] mod tests { use flesh::ast::{eval, lex, SymTable}; use flesh::stdlib::static_stdlib; use crate::stl::static_stdlib_overwrites; #[test] fn test_fexists() { let document = "(exists? \"/tmp\")"; let result = "true"; let mut syms = SymTable::new(); static_stdlib(&mut syms); static_stdlib_overwrites(&mut syms); assert_eq!( *eval(&lex(&document.to_string()).unwrap(), &mut syms) .unwrap() .to_string(), result.to_string(), ); } #[test] fn test_fexists_doesnt() { let document = "(exists? \"cargo.timtam\")"; let result = "false"; let mut syms = SymTable::new(); static_stdlib(&mut syms); static_stdlib_overwrites(&mut syms); assert_eq!( *eval(&lex(&document.to_string()).unwrap(), &mut syms) .unwrap() .to_string(), result.to_string(), ); } #[test] fn test_write_file() { let document = " (let ((s \"test\") (t \"/tmp/flesh-lib-test-file-1\")) (write-file t s) (echo (read-file t)) (eq? (read-file t) s))"; let result = "true"; let mut syms = SymTable::new(); static_stdlib(&mut syms); static_stdlib_overwrites(&mut syms); assert_eq!( *eval(&lex(&document.to_string()).unwrap(), &mut syms) .unwrap() .to_string(), result.to_string(), ); } #[test] fn test_append_file() { let document = " (let ((s \"test\") (t \"/tmp/flesh-lib-test-file-2\")) (write-file t s) (append-file t s) (eq? (read-file t) (concat s s)))"; let result = "true"; let mut syms = SymTable::new(); static_stdlib(&mut syms); static_stdlib_overwrites(&mut syms); assert_eq!( *eval(&lex(&document.to_string()).unwrap(), &mut syms) .unwrap() .to_string(), result.to_string(), ); } }