Skip to content

Commit 3e53929

Browse files
committed
feat(core): add more IO extensions (create-dir, remove-dir, get-cwd, set-cwd)
Adds cross-platform wrappers for mkdir, rmdir, getcwd, and chdir to IO.Raw and high-level Result-returning versions to the IO module. Also fixes several typos in existing IO documentation and updates tests to cover new functionality.
1 parent e780d88 commit 3e53929

File tree

4 files changed

+208
-14
lines changed

4 files changed

+208
-14
lines changed

core/IO.carp

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@
9797
(register fread- (Fn [a Int Int (Ptr FILE)] Int) "fread")
9898

9999
(doc fread
100-
"Reads a given numebr of bytes from a file into C-String."
100+
"Reads a given number of bytes from a file into C-String."
101101
"Wraps `fread` from the C standard library."
102102
"Consider using [`read-file`](#read-file) or"
103103
"[`unsafe-read-file`](#unsafe-read-file) instead.")
@@ -115,7 +115,7 @@
115115

116116
(doc fflush!
117117
"Flushes buffered data via [`fflush`](#fflush)"
118-
", but discards the result, making this function appropraite for use as"
118+
", but discards the result, making this function appropriate for use as"
119119
"a side effect in `do` forms.")
120120
(defn fflush! [file]
121121
(ignore (fflush file)))
@@ -227,6 +227,21 @@
227227
"Returns an array containing the names of the entries in the directory"
228228
"designated by `path`, skipping `.` and `..`.")
229229
(register list-dir (Fn [&String] (Array String)) "IO_Raw_list_MINUS_dir")
230+
231+
(doc mkdir
232+
"Creates a directory at the given path."
233+
"Returns an integer indicating success (0) or failure (-1)."
234+
"Wraps `mkdir` from the C standard library.")
235+
(register mkdir (Fn [&String] Int) "IO_mkdir")
236+
237+
(doc rmdir
238+
"Removes a directory at the given path."
239+
"Returns an integer indicating success (0) or failure (-1)."
240+
"Wraps `rmdir` from the C standard library.")
241+
(register rmdir (Fn [&String] Int) "IO_rmdir")
242+
243+
(register get-cwd (Fn [] (Ptr CChar)) "IO_Raw_get_MINUS_cwd")
244+
(register set-cwd (Fn [&String] Int) "IO_Raw_set_MINUS_cwd")
230245
)
231246

232247
(doc file-exists? "Checks if a file exists at the given path.")
@@ -235,6 +250,46 @@
235250
(doc dir-exists? "Checks if a directory exists at the given path.")
236251
(register dir-exists? (Fn [&String] Bool))
237252

253+
(doc get-cwd
254+
"Returns the current working directory."
255+
""
256+
"Returns a (Result String String) indicating success or failure.")
257+
(defn get-cwd []
258+
(let [res (IO.Raw.get-cwd)]
259+
(if (null? res)
260+
(Result.Error (fmt "Failed to get current working directory: %s" &(System.error-text)))
261+
(let [s (String.from-cstr res)]
262+
(do
263+
(Pointer.free res)
264+
(Result.Success s))))))
265+
266+
(doc set-cwd
267+
"Sets the current working directory to `path`."
268+
""
269+
"Returns a (Result Bool String) indicating success or failure.")
270+
(defn set-cwd [path]
271+
(if (Int.= (IO.Raw.set-cwd path) 0)
272+
(Result.Success true)
273+
(Result.Error (fmt "Failed to set current working directory to '%s': %s" path &(System.error-text)))))
274+
275+
(doc create-dir
276+
"Creates a directory at the given path."
277+
""
278+
"Returns a (Result Bool String) indicating success or failure.")
279+
(defn create-dir [path]
280+
(if (Int.= (IO.Raw.mkdir path) 0)
281+
(Result.Success true)
282+
(Result.Error (fmt "Failed to create directory '%s': %s" path &(System.error-text)))))
283+
284+
(doc remove-dir
285+
"Removes a directory at the given path."
286+
""
287+
"Returns a (Result Bool String) indicating success or failure.")
288+
(defn remove-dir [path]
289+
(if (Int.= (IO.Raw.rmdir path) 0)
290+
(Result.Success true)
291+
(Result.Error (fmt "Failed to remove directory '%s': %s" path &(System.error-text)))))
292+
238293
(doc list-dir
239294
"Returns an array containing the names of the entries in the directory"
240295
"designated by `path`, skipping `.` and `..`."
@@ -278,7 +333,7 @@
278333

279334
(doc open-file
280335
"Opens a [FILE](#file) with the given name using a designated mode"
281-
"(e.g. [r]ead, [w]rite, [a]ppend), [rb] read binary...)."
336+
"(e.g. [r]ead, [w]rite, [a]ppend), [rb] read binary...."
282337
"See `fopen` from the C standard library for a description of valid mode parameters.")
283338
(defn open-file [filename mode]
284339
(let [ptr (IO.Raw.fopen filename mode)]
@@ -423,7 +478,7 @@
423478
`(IO.println %(build-str* forms)))
424479

425480
(doc print*
426-
"Prints any number of values to [`stdout`](#stdout), using thier"
481+
"Prints any number of values to [`stdout`](#stdout), using their"
427482
"`str` implementations."
428483
""
429484
"```"

core/carp_io.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
#include <sys/stat.h>
22
#include <dirent.h>
3+
#ifdef _WIN32
4+
#include <direct.h>
5+
#define getcwd _getcwd
6+
#define chdir _chdir
7+
#else
8+
#include <unistd.h>
9+
#endif
310

411
void IO_println(String* s) {
512
puts(*s);
@@ -95,3 +102,34 @@ Array IO_Raw_list_MINUS_dir(const String* path) {
95102
}
96103
return result;
97104
}
105+
106+
int IO_mkdir(const String* path) {
107+
#ifdef _WIN32
108+
return _mkdir(*path);
109+
#else
110+
return mkdir(*path, 0777);
111+
#endif
112+
}
113+
114+
int IO_rmdir(const String* path) {
115+
#ifdef _WIN32
116+
return _rmdir(*path);
117+
#else
118+
return rmdir(*path);
119+
#endif
120+
}
121+
122+
String IO_Raw_get_MINUS_cwd() {
123+
char* buffer = getcwd(NULL, 0);
124+
if (buffer) {
125+
String result = CARP_MALLOC(strlen(buffer) + 1);
126+
strcpy(result, buffer);
127+
free(buffer);
128+
return result;
129+
}
130+
return NULL;
131+
}
132+
133+
int IO_Raw_set_MINUS_cwd(const String* path) {
134+
return chdir(*path);
135+
}

test/io.carp

Lines changed: 85 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
(IO.Raw.unlink! file-name)
1010
(if (Result.error? &read?)
1111
(Result.unsafe-from-error read?)
12-
(Result.unsafe-from-success read?) )))))
12+
(let [res (Result.unsafe-from-success read?)]
13+
res))))))
1314

1415
(deftest test
1516
(assert-nothing test
@@ -21,20 +22,94 @@
2122
"getenv works on existant variable"
2223
)
2324
(let [ data "Icke\n\tdette\n\tkieke mal,\nOochen, Flesch und Beene." ] ; include \n in test data!
24-
(assert-equal test
25-
data
26-
&(write-then-read data "io_carp_testdata.txt")
27-
"write-file then read-file" ))
25+
(let [res (write-then-read data "io_carp_testdata.txt")]
26+
(assert-equal test
27+
data
28+
&res
29+
"write-file then read-file" )))
2830
(let [ file-name "io_carp_append_testdata.txt"
2931
first "hello"
3032
second " world" ]
3133
(do
3234
(ignore (IO.write-file first file-name))
3335
(ignore (IO.append-file second file-name))
3436
(let-do [ result (IO.read-file file-name) ]
35-
(IO.Raw.unlink! file-name)
36-
(assert-equal test
37-
"hello world"
38-
&(Result.unsafe-from-success result)
39-
"append-file appends to existing file"))))
37+
(do
38+
(IO.Raw.unlink! file-name)
39+
(let [s (Result.unsafe-from-success result)]
40+
(assert-equal test
41+
"hello world"
42+
&s
43+
"append-file appends to existing file"))))))
44+
45+
(assert-true test (IO.file-exists? "core/IO.carp") "file-exists? works for existing file")
46+
(assert-false test (IO.file-exists? "nonexistent_file_xyz.txt") "file-exists? returns false for non-existent file")
47+
(assert-true test (IO.dir-exists? "core") "dir-exists? works for existing directory")
48+
(assert-false test (IO.dir-exists? "nonexistent_dir_xyz") "dir-exists? returns false for non-existent directory")
49+
50+
(let [old-file "test_move_old.txt"
51+
new-file "test_move_new.txt"]
52+
(do
53+
(ignore (IO.write-file "test move content" old-file))
54+
(let [move-res (IO.move-file old-file new-file)]
55+
(let-do [t1 (assert-true test (Result.success? &move-res) "move-file success")
56+
t2 (assert-true (ref t1) (IO.file-exists? new-file) "new file exists after move")
57+
t3 (assert-false (ref t2) (IO.file-exists? old-file) "old file does not exist after move")]
58+
(do
59+
(ignore (IO.remove-file new-file))
60+
t3)))))
61+
62+
(let [src-file "test_copy_src.txt"
63+
dest-file "test_copy_dest.txt"]
64+
(do
65+
(ignore (IO.write-file "test copy content" src-file))
66+
(let [copy-res (IO.copy-file src-file dest-file)]
67+
(let-do [t1 (assert-true test (Result.success? &copy-res) "copy-file success")
68+
t2 (assert-true (ref t1) (IO.file-exists? dest-file) "dest file exists after copy")
69+
t3 (assert-true (ref t2) (IO.file-exists? src-file) "src file still exists after copy")]
70+
(do
71+
(ignore (IO.remove-file src-file))
72+
(ignore (IO.remove-file dest-file))
73+
t3)))))
74+
75+
(let [dir-res (IO.list-dir "core")]
76+
(do
77+
(let [t1 (assert-true test (Result.success? &dir-res) "list-dir success")]
78+
(if (Result.success? &dir-res)
79+
(let [entries (Result.unsafe-from-success dir-res)]
80+
(assert-true (ref t1) (Array.contains? &entries &@"IO.carp") "list-dir contains IO.carp"))
81+
t1))))
82+
83+
(let [dir-path "test_dir_xyz"]
84+
(do
85+
(let [create-res (IO.create-dir dir-path)]
86+
(let-do [t1 (assert-true test (Result.success? &create-res) "create-dir success")
87+
t2 (assert-true (ref t1) (IO.dir-exists? dir-path) "dir exists after create")]
88+
(let [remove-res (IO.remove-dir dir-path)]
89+
(let-do [t3 (assert-true (ref t2) (Result.success? &remove-res) "remove-dir success")
90+
t4 (assert-false (ref t3) (IO.dir-exists? dir-path) "dir gone after remove")]
91+
t4))))))
92+
93+
(let [cwd-res (IO.get-cwd)]
94+
(do
95+
(let [t1 (assert-true test (Result.success? &cwd-res) "get-cwd success")]
96+
(if (Result.success? &cwd-res)
97+
(let [cwd (Result.unsafe-from-success cwd-res)]
98+
(assert-true (ref t1) (Int.> (String.length &cwd) 0) "cwd not empty"))
99+
t1))))
100+
101+
(let [old-cwd (Result.unsafe-from-success (IO.get-cwd))
102+
new-dir "test_cwd_change"]
103+
(do
104+
(ignore (IO.create-dir new-dir))
105+
(let [set-res (IO.set-cwd new-dir)]
106+
(let-do [t1 (assert-true test (Result.success? &set-res) "set-cwd success")
107+
curr-cwd-res (IO.get-cwd)
108+
t2 (assert-true (ref t1) (Result.success? &curr-cwd-res) "get-cwd after set success")]
109+
(let [curr-cwd (Result.unsafe-from-success curr-cwd-res)
110+
t3 (assert-true (ref t2) (String.ends-with? &curr-cwd new-dir) "cwd changed")]
111+
(do
112+
(ignore (IO.set-cwd &old-cwd))
113+
(ignore (IO.remove-dir new-dir))
114+
t3))))))
40115
)

test/io_extensions.carp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,29 @@
4242
(let [entries (Result.unsafe-from-success dir-res)]
4343
(assert-true (ref t1) (Array.contains? &entries &@"IO.carp") "list-dir contains IO.carp"))
4444
t1)))
45+
46+
(deftest test-dir-ops
47+
(let-do [dir-path "test_dir_xyz"
48+
create-res (IO.create-dir dir-path)
49+
t1 (assert-true test-dir-ops (Result.success? &create-res) "create-dir success")
50+
t2 (assert-true (ref t1) (IO.dir-exists? dir-path) "dir exists after create")
51+
remove-res (IO.remove-dir dir-path)
52+
t3 (assert-true (ref t2) (Result.success? &remove-res) "remove-dir success")
53+
t4 (assert-false (ref t3) (IO.dir-exists? dir-path) "dir gone after remove")]
54+
t4))
55+
56+
(deftest test-cwd
57+
(let-do [cwd-res (IO.get-cwd)
58+
t1 (assert-true test-cwd (Result.success? &cwd-res) "get-cwd success")
59+
old-cwd (Result.unsafe-from-success cwd-res)
60+
new-dir "test_cwd_change"
61+
_1 (IO.create-dir new-dir)
62+
set-res (IO.set-cwd new-dir)
63+
t2 (assert-true (ref t1) (Result.success? &set-res) "set-cwd success")
64+
curr-cwd-res (IO.get-cwd)
65+
t3 (assert-true (ref t2) (Result.success? &curr-cwd-res) "get-cwd after set success")
66+
curr-cwd (Result.unsafe-from-success curr-cwd-res)
67+
t4 (assert-true (ref t3) (String.ends-with? &curr-cwd new-dir) "cwd changed")
68+
_2 (IO.set-cwd &old-cwd)
69+
_3 (IO.remove-dir new-dir)]
70+
t4))

0 commit comments

Comments
 (0)