1use std::collections::HashMap;
15
16use crate::value::{RuntimeError, Value};
17
18pub fn register(global: &mut HashMap<String, Value>) {
19 let mut members = HashMap::new();
20 for method in &[
21 "readText",
22 "writeText",
23 "appendText",
24 "exists",
25 "delete",
26 "deleteDir",
27 "listDir",
28 "makeDir",
29 ] {
30 members.insert(
31 method.to_string(),
32 Value::Builtin(format!("Disk.{}", method)),
33 );
34 }
35 global.insert(
36 "Disk".to_string(),
37 Value::Namespace {
38 name: "Disk".to_string(),
39 members,
40 },
41 );
42}
43
44pub fn effects(name: &str) -> &'static [&'static str] {
45 match name {
46 "Disk.readText" | "Disk.writeText" | "Disk.appendText" | "Disk.exists" | "Disk.delete"
47 | "Disk.deleteDir" | "Disk.listDir" | "Disk.makeDir" => &["Disk"],
48 _ => &[],
49 }
50}
51
52pub fn call(name: &str, args: &[Value]) -> Option<Result<Value, RuntimeError>> {
54 match name {
55 "Disk.readText" => Some(read_text(&args)),
56 "Disk.writeText" => Some(write_text(&args)),
57 "Disk.appendText" => Some(append_text(&args)),
58 "Disk.exists" => Some(exists(&args)),
59 "Disk.delete" => Some(delete(&args)),
60 "Disk.deleteDir" => Some(delete_dir(&args)),
61 "Disk.listDir" => Some(list_dir(&args)),
62 "Disk.makeDir" => Some(make_dir(&args)),
63 _ => None,
64 }
65}
66
67fn read_text(args: &[Value]) -> Result<Value, RuntimeError> {
70 let path = one_str_arg("Disk.readText", args)?;
71 match std::fs::read_to_string(&path) {
72 Ok(text) => Ok(Value::Ok(Box::new(Value::Str(text)))),
73 Err(e) => Ok(Value::Err(Box::new(Value::Str(e.to_string())))),
74 }
75}
76
77fn write_text(args: &[Value]) -> Result<Value, RuntimeError> {
78 let (path, content) = two_str_args("Disk.writeText", args)?;
79 match std::fs::write(&path, &content) {
80 Ok(_) => Ok(Value::Ok(Box::new(Value::Unit))),
81 Err(e) => Ok(Value::Err(Box::new(Value::Str(e.to_string())))),
82 }
83}
84
85fn append_text(args: &[Value]) -> Result<Value, RuntimeError> {
86 use std::io::Write;
87 let (path, content) = two_str_args("Disk.appendText", args)?;
88 match std::fs::OpenOptions::new()
89 .create(true)
90 .append(true)
91 .open(&path)
92 {
93 Ok(mut f) => match f.write_all(content.as_bytes()) {
94 Ok(_) => Ok(Value::Ok(Box::new(Value::Unit))),
95 Err(e) => Ok(Value::Err(Box::new(Value::Str(e.to_string())))),
96 },
97 Err(e) => Ok(Value::Err(Box::new(Value::Str(e.to_string())))),
98 }
99}
100
101fn exists(args: &[Value]) -> Result<Value, RuntimeError> {
102 let path = one_str_arg("Disk.exists", args)?;
103 Ok(Value::Bool(std::path::Path::new(&path).exists()))
104}
105
106fn delete(args: &[Value]) -> Result<Value, RuntimeError> {
107 let path = one_str_arg("Disk.delete", args)?;
108 let p = std::path::Path::new(&path);
109 if p.is_dir() {
110 return Ok(Value::Err(Box::new(Value::Str(
111 "Disk.delete: path is a directory — use Disk.deleteDir to remove directories"
112 .to_string(),
113 ))));
114 }
115 match std::fs::remove_file(p) {
116 Ok(_) => Ok(Value::Ok(Box::new(Value::Unit))),
117 Err(e) => Ok(Value::Err(Box::new(Value::Str(e.to_string())))),
118 }
119}
120
121fn delete_dir(args: &[Value]) -> Result<Value, RuntimeError> {
122 let path = one_str_arg("Disk.deleteDir", args)?;
123 let p = std::path::Path::new(&path);
124 if !p.is_dir() {
125 return Ok(Value::Err(Box::new(Value::Str(
126 "Disk.deleteDir: path is not a directory — use Disk.delete to remove files".to_string(),
127 ))));
128 }
129 match std::fs::remove_dir_all(p) {
130 Ok(_) => Ok(Value::Ok(Box::new(Value::Unit))),
131 Err(e) => Ok(Value::Err(Box::new(Value::Str(e.to_string())))),
132 }
133}
134
135fn list_dir(args: &[Value]) -> Result<Value, RuntimeError> {
136 let path = one_str_arg("Disk.listDir", args)?;
137 match std::fs::read_dir(&path) {
138 Ok(entries) => {
139 let mut names = Vec::new();
140 for entry in entries {
141 match entry {
142 Ok(e) => names.push(Value::Str(e.file_name().to_string_lossy().into_owned())),
143 Err(e) => return Ok(Value::Err(Box::new(Value::Str(e.to_string())))),
144 }
145 }
146 Ok(Value::Ok(Box::new(Value::List(names))))
147 }
148 Err(e) => Ok(Value::Err(Box::new(Value::Str(e.to_string())))),
149 }
150}
151
152fn make_dir(args: &[Value]) -> Result<Value, RuntimeError> {
153 let path = one_str_arg("Disk.makeDir", args)?;
154 match std::fs::create_dir_all(&path) {
155 Ok(_) => Ok(Value::Ok(Box::new(Value::Unit))),
156 Err(e) => Ok(Value::Err(Box::new(Value::Str(e.to_string())))),
157 }
158}
159
160fn one_str_arg(fn_name: &str, args: &[Value]) -> Result<String, RuntimeError> {
163 match args {
164 [Value::Str(s)] => Ok(s.clone()),
165 [_] => Err(RuntimeError::Error(format!(
166 "{}: path must be a String",
167 fn_name
168 ))),
169 _ => Err(RuntimeError::Error(format!(
170 "{}() takes 1 argument (path), got {}",
171 fn_name,
172 args.len()
173 ))),
174 }
175}
176
177fn two_str_args(fn_name: &str, args: &[Value]) -> Result<(String, String), RuntimeError> {
178 match args {
179 [Value::Str(a), Value::Str(b)] => Ok((a.clone(), b.clone())),
180 [_, _] => Err(RuntimeError::Error(format!(
181 "{}: both arguments must be Strings",
182 fn_name
183 ))),
184 _ => Err(RuntimeError::Error(format!(
185 "{}() takes 2 arguments (path, content), got {}",
186 fn_name,
187 args.len()
188 ))),
189 }
190}