Skip to content

Commit e780d88

Browse files
committed
feat(core): add IO.file-exists? and IO.dir-exists?
1 parent 98a9e2e commit e780d88

File tree

3 files changed

+152
-0
lines changed

3 files changed

+152
-0
lines changed

core/IO.carp

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,24 @@
153153
(defn unlink! [file-name]
154154
(ignore (unlink file-name)))
155155

156+
(private rename-)
157+
(hidden rename-)
158+
(register rename- (Fn [(Ptr CChar) (Ptr CChar)] Int) "rename")
159+
160+
(doc rename
161+
"Renames a file or directory from `old-name` to `new-name`."
162+
"Returns an integer indicating success (0) or failure (-1)."
163+
"Wraps `rename` from the C standard library.")
164+
(defn rename [old-name new-name]
165+
(rename- (String.cstr old-name) (String.cstr new-name)))
166+
167+
(doc rename!
168+
"Renames a file via [`rename`](#rename), but discards the result,"
169+
"making this function appropriate for use as a side effect in"
170+
"`do` forms.")
171+
(defn rename! [old-name new-name]
172+
(ignore (rename old-name new-name)))
173+
156174
(doc fseek
157175
"Sets the position indicator of a [FILE](#file) based on a given"
158176
"reference position and offset. The position indicator will be set to an"
@@ -204,8 +222,30 @@
204222
"given [FILE](#file)."
205223
"Wraps `ferror` from the C standard library.")
206224
(register ferror (Fn [(Ptr FILE)] Bool) "ferror")
225+
226+
(doc list-dir
227+
"Returns an array containing the names of the entries in the directory"
228+
"designated by `path`, skipping `.` and `..`.")
229+
(register list-dir (Fn [&String] (Array String)) "IO_Raw_list_MINUS_dir")
207230
)
208231

232+
(doc file-exists? "Checks if a file exists at the given path.")
233+
(register file-exists? (Fn [&String] Bool))
234+
235+
(doc dir-exists? "Checks if a directory exists at the given path.")
236+
(register dir-exists? (Fn [&String] Bool))
237+
238+
(doc list-dir
239+
"Returns an array containing the names of the entries in the directory"
240+
"designated by `path`, skipping `.` and `..`."
241+
""
242+
"Returns a (Result (Array String) String) indicating success or failure.")
243+
(defn list-dir [path]
244+
(let [res (IO.Raw.list-dir path)]
245+
(if (Int.= (Array.length &res) -1)
246+
(Result.Error (fmt "Failed to list directory '%s': %s" path &(System.error-text)))
247+
(Result.Success res))))
248+
209249
(doc println
210250
"Prints a String ref to [`stdout`](#stdout), appends a newline.")
211251
(register println (Fn [(Ref String)] ()))
@@ -329,6 +369,32 @@
329369
(Result.Success true)
330370
(Result.Error (fmt "only %d of %d bytes were written" bytes-written bytes2write)))))))
331371

372+
(doc move-file
373+
"Moves or renames a file or directory from `old-name` to `new-name`."
374+
""
375+
"Returns a (Result Bool String) indicating success or failure.")
376+
(defn move-file [old-name new-name]
377+
(if (Int.= (IO.Raw.rename old-name new-name) 0)
378+
(Result.Success true)
379+
(Result.Error (fmt "Failed to move '%s' to '%s': %s" old-name new-name &(System.error-text)))))
380+
381+
(doc remove-file
382+
"Deletes a file from the filesystem."
383+
""
384+
"Returns a (Result Bool String) indicating success or failure.")
385+
(defn remove-file [filename]
386+
(if (Int.= (IO.Raw.unlink filename) 0)
387+
(Result.Success true)
388+
(Result.Error (fmt "Failed to remove file '%s': %s" filename &(System.error-text)))))
389+
390+
(doc copy-file
391+
"Copies a file from `src` to `dest`. Overwrites `dest` if it already exists."
392+
""
393+
"Returns a (Result Bool String) indicating success or failure.")
394+
(defn copy-file [src dest]
395+
(Result.and-then (read-file src)
396+
&(fn [content] (write-file &content dest))))
397+
332398
(private getenv-)
333399
(hidden getenv-)
334400
(doc getenv- "gets the value of an environment variable (thin wrapper for the C standard library)")

core/carp_io.h

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
#include <sys/stat.h>
2+
#include <dirent.h>
3+
14
void IO_println(String* s) {
25
puts(*s);
36
}
@@ -53,3 +56,42 @@ String IO_unsafe_MINUS_read_MINUS_file(const String* filename) {
5356

5457
return buffer;
5558
}
59+
60+
bool IO_file_MINUS_exists_QMARK_(const String* path) {
61+
struct stat st;
62+
if (stat(*path, &st) == 0) {
63+
return S_ISREG(st.st_mode);
64+
}
65+
return false;
66+
}
67+
68+
bool IO_dir_MINUS_exists_QMARK_(const String* path) {
69+
struct stat st;
70+
if (stat(*path, &st) == 0) {
71+
return S_ISDIR(st.st_mode);
72+
}
73+
return false;
74+
}
75+
76+
Array IO_Raw_list_MINUS_dir(const String* path) {
77+
DIR *d;
78+
struct dirent *dir;
79+
d = opendir(*path);
80+
Array result = {0, 0, NULL};
81+
if (d) {
82+
while ((dir = readdir(d)) != NULL) {
83+
if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0) {
84+
continue;
85+
}
86+
String name = CARP_MALLOC(strlen(dir->d_name) + 1);
87+
strcpy(name, dir->d_name);
88+
result.len++;
89+
result.data = CARP_REALLOC(result.data, sizeof(String) * result.len);
90+
((String*)result.data)[result.len - 1] = name;
91+
}
92+
closedir(d);
93+
} else {
94+
result.len = -1;
95+
}
96+
return result;
97+
}

test/io_extensions.carp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
(load "Test.carp")
2+
(use Test)
3+
4+
(deftest test-existence
5+
(let-do [t1 (assert-true test-existence (IO.file-exists? "core/IO.carp") "file-exists? works for existing file")
6+
t2 (assert-false (ref t1) (IO.file-exists? "nonexistent_file_xyz.txt") "file-exists? returns false for non-existent file")
7+
t3 (assert-false (ref t2) (IO.file-exists? "core") "file-exists? returns false for directory")
8+
t4 (assert-true (ref t3) (IO.dir-exists? "core") "dir-exists? works for existing directory")
9+
t5 (assert-false (ref t4) (IO.dir-exists? "core/IO.carp") "dir-exists? returns false for file")
10+
t6 (assert-false (ref t5) (IO.dir-exists? "nonexistent_dir_xyz") "dir-exists? returns false for non-existent directory")]
11+
t6))
12+
13+
(deftest test-move-remove
14+
(let-do [old-file "test_move_old.txt"
15+
new-file "test_move_new.txt"
16+
_1 (IO.write-file "test move content" old-file)
17+
move-res (IO.move-file old-file new-file)
18+
t1 (assert-true test-move-remove (Result.success? &move-res) "move-file success")
19+
t2 (assert-true (ref t1) (IO.file-exists? new-file) "new file exists after move")
20+
t3 (assert-false (ref t2) (IO.file-exists? old-file) "old file does not exist after move")
21+
remove-res (IO.remove-file new-file)
22+
t4 (assert-true (ref t3) (Result.success? &remove-res) "remove-file success")
23+
t5 (assert-false (ref t4) (IO.file-exists? new-file) "file gone after remove")]
24+
t5))
25+
26+
(deftest test-copy
27+
(let-do [src-file "test_copy_src.txt"
28+
dest-file "test_copy_dest.txt"
29+
_1 (IO.write-file "test copy content" src-file)
30+
copy-res (IO.copy-file src-file dest-file)
31+
t1 (assert-true test-copy (Result.success? &copy-res) "copy-file success")
32+
t2 (assert-true (ref t1) (IO.file-exists? dest-file) "dest file exists after copy")
33+
t3 (assert-true (ref t2) (IO.file-exists? src-file) "src file still exists after copy")
34+
_2 (IO.remove-file src-file)
35+
_3 (IO.remove-file dest-file)]
36+
t3))
37+
38+
(deftest test-list-dir
39+
(let-do [dir-res (IO.list-dir "core")
40+
t1 (assert-true test-list-dir (Result.success? &dir-res) "list-dir success")]
41+
(if (Result.success? &dir-res)
42+
(let [entries (Result.unsafe-from-success dir-res)]
43+
(assert-true (ref t1) (Array.contains? &entries &@"IO.carp") "list-dir contains IO.carp"))
44+
t1)))

0 commit comments

Comments
 (0)