Asking about files, walking and copying folders, and taking paths apart.
Where File opens and reads single files, this module looks at the file system as a whole: file_exists,
is_directory and file_modtime_and_size examine a path, visit_files and file_list walk a folder,
copy_file and copy_directory copy, delete_directory_tree removes a folder with everything in it, and parse_path turns text into a Path of names that reduce_path,
path_contains and path_to_string work on without touching the disk. The procedures that take a path
answer false for one with a NUL byte in it. Strings that a procedure returns come from context.allocator and
are the caller's to free. On wasm only the directories the host pre-opened can be reached, and files have no owners or
permissions (see docs/native/wasm-target.md).
#import"Extensions/Environment";
#import"File";
base := environment_variable("TMPDIR");
if base == "" base = environment_variable("TEMP");
if base == "" base = "/tmp";
path := tprint("%/jai-doc-file-utilities.txt", base);
defer file_delete(path);
assert(!file_exists(path));
assert(write_entire_file(path, "x"));
assert(file_exists(path));
Writes path back as text in a new string that the caller frees: the drive, the leading slashes, the words with the
platform separator (/, or \ on Windows) and a closing separator when trailing_slash is set and there are words.
A deep copy of path: the drive, every word and the array are new allocations from context.allocator, so the copy stays valid after the
text it was parsed from is freed. Give it to free_permanent.
Calls proc with user_data for each entry of the directory dir_name, and returns true if every entry could be examined.
With recursive the walk goes into sub-directories too.
Whether path is a directory, as (is_directory, success). A link is followed. A path that cannot be examined (missing, no
permission, or a NUL byte in it) gives (false, false), so success tells a file from nothing at all.
#import"Extensions/Environment";
#import"File";
base := environment_variable("TMPDIR");
if base == "" base = environment_variable("TEMP");
if base == "" base = "/tmp";
root := tprint("%/jai-doc-is-directory", base);
file := tprint("%/file", root);
defer {
file_delete(file);
delete_directory(root);
}
assert(make_directory_if_it_does_not_exist(root));
assert(write_entire_file(file, "x"));
directory, success := is_directory(root);
assert(directory && success);
directory, success = is_directory(file);
assert(!directory && success);
directory, success = is_directory(tprint("%/missing", root));
assert(!directory && !success);
assert(file_exists(file) && !file_exists(tprint("%/missing", root)));
Fills in modification_time and size of info from info.full_name when the time is still APOLLO_TIME_INVALID.
Returns whether the details are known: false, with had_error set, when they cannot be read. A record that already has
a time is left alone and the answer is !had_error. visit_files fills these in itself on this target.
Whether something exists at path: a file, a directory or anything else the system can examine. A link is followed,
so a link whose target is missing does not count. false also for an empty path or one with a NUL byte.
The files below the folder path, as full paths: path joined with each name with /. Directories are not listed;
links to files and links with a missing target are. With recursive it also lists the files of every sub-folder.
follow_directory_symlinks is as in visit_files.
#import"Extensions/Environment";
#import"File";
base := environment_variable("TMPDIR");
if base == "" base = environment_variable("TEMP");
if base == "" base = "/tmp";
root := tprint("%/jai-doc-file-list", base);
defer {
file_delete(tprint("%/a.txt", root));
file_delete(tprint("%/sub/b.txt", root));
delete_directory(tprint("%/sub", root));
delete_directory(root);
}
assert(make_directory_if_it_does_not_exist(tprint("%/sub", root), recursive = true));
assert(write_entire_file(tprint("%/a.txt", root), "a"));
assert(write_entire_file(tprint("%/sub/b.txt", root), "b"));
top := file_list(root);
assert(top.count == 1);
for name: top free(name);
array_free(top);
all := file_list(root, recursive = true);
assert(all.count == 2);
for name: all free(name);
array_free(all);
How many leading words of path exist: (fully_exists, how_much_exists). It checks a, then a/b, then a/b/c and so on, and
stops at the first that is missing. The count is 0 when even the first is missing and the number of words when all of them exist.
An empty path is (true, 0).
Moves old_filename to new_filename, replacing a file already there. Returns whether it worked.
#import"Extensions/Environment";
#import"File";
base := environment_variable("TMPDIR");
if base == "" base = environment_variable("TEMP");
if base == "" base = "/tmp";
current := tprint("%/jai-doc-replace.txt", base);
fresh := tprint("%/jai-doc-replace.new", base);
defer {
file_delete(current);
file_delete(fresh);
}
assert(write_entire_file(current, "old"));
assert(write_entire_file(fresh, "new"));
assert(atomic_file_replace_or_just_rename_if_destination_file_does_not_exist(fresh, current));
assert(!file_exists(fresh));
text, ok := read_entire_file(current);
assert(ok && text == "new");
free(text);
Deletes the folder path and everything in it. Returns whether the folder is gone.
#import"Extensions/Environment";
#import"File";
base := environment_variable("TMPDIR");
if base == "" base = environment_variable("TEMP");
if base == "" base = "/tmp";
root := tprint("%/jai-doc-delete-tree", base);
assert(make_directory_if_it_does_not_exist(tprint("%/a/b", root), recursive = true));
assert(write_entire_file(tprint("%/a/b/leaf", root), "x"));
assert(!delete_directory(root)); // not emptyassert(delete_directory_tree(root));
assert(!file_exists(root));
assert(!delete_directory_tree(root)); // already gone