diff options
-rw-r--r-- | doc/jim_tcl.txt | 15 | ||||
-rw-r--r-- | jim-file.c | 69 | ||||
-rw-r--r-- | tests/filedir.test | 65 |
3 files changed, 134 insertions, 15 deletions
diff --git a/doc/jim_tcl.txt b/doc/jim_tcl.txt index aef6083..185acc8 100644 --- a/doc/jim_tcl.txt +++ b/doc/jim_tcl.txt @@ -1866,9 +1866,9 @@ abbreviation for *option* is acceptable. The valid options are: Copies file *source* to file *target*. The source file must exist. The target file must not exist, unless *-force* is specified. -+*file delete* 'name'+:: - Deletes file *name*. If the file doesn't exist, nothing happens. - If the file can't be deleted, an error is generated. ++*file delete* 'name ...'+:: + Deletes file or directory *name*. If the file or directory doesn't exist, nothing happens. + If it can't be deleted, an error is generated. Non-empty directories will not be deleted. +*file dirname* 'name'+:: Return all of the characters in *name* up to but not including @@ -1911,8 +1911,13 @@ abbreviation for *option* is acceptable. The valid options are: as the 'stat' option. +*file mkdir* 'dir1 ?dir2? ...'+:: - Creates the given directories. *Note* unlike Tcl 7.x, intermediate - directories are *not* created as necessary. + Creates each directory specified. For each pathname *dir* specified, + this command will create all non-existing parent directories + as well as *dir* itself. If an existing directory is specified, + then no action is taken and no error is returned. Trying to + overwrite an existing file with a directory will result in an + error. Arguments are processed in the order specified, halting + at the first error, if any. +*file mtime* 'name'+:: Return a decimal string giving the time at which file *name* @@ -318,25 +318,74 @@ static int file_cmd_delete(Jim_Interp *interp, int argc, Jim_Obj *const *argv) while (argc--) { const char *path = Jim_GetString(argv[0], NULL); if (unlink(path) == -1 && errno != ENOENT) { - Jim_SetResultFormatted(interp, "couldn't delete file \"%s\": %s", path, strerror(errno)); - return JIM_ERR; + if (rmdir(path) == -1) { + Jim_SetResultFormatted(interp, "couldn't delete file \"%s\": %s", path, strerror(errno)); + return JIM_ERR; + } } argv++; } return JIM_OK; } -static int mkdir_all(const char *path) +/** + * Create directory, creating all intermediate paths if necessary. + * + * Returns 0 if OK or -1 on failure (and sets errno) + * + * Note: The path may be modified. + */ +static int mkdir_all(char *path) { - /* REVISIT: create intermediate dirs if necessary */ - mkdir(path, 0755); - return 0; + int ok = 1; + + /* First time just try to make the dir */ + goto first; + + while (ok--) { + /* Must have failed the first time, so recursively make the parent and try again */ + char *slash = strrchr(path, '/'); + if (slash && slash != path) { + *slash = 0; + if (mkdir_all(path) != 0) { + return -1; + } + *slash = '/'; + } +first: + if (mkdir(path, 0755) == 0) { + return 0; + } + if (errno == ENOENT) { + /* Create the parent and try again */ + continue; + } + /* Maybe it already exists as a directory */ + if (errno == EEXIST) { + struct stat sb; + + if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) { + return 0; + } + /* Restore errno */ + errno = EEXIST; + } + /* Failed */ + break; + } + return -1; } static int file_cmd_mkdir(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { while (argc--) { - mkdir_all(Jim_GetString(argv[0], NULL)); + char *path = Jim_StrDup(Jim_GetString(argv[0], NULL)); + int rc = mkdir_all(path); + Jim_Free(path); + if (rc != 0) { + Jim_SetResultFormatted(interp, "can't create directory \"%#s\": %s", argv[0], strerror(errno)); + return JIM_ERR; + } argv++; } return JIM_OK; @@ -455,7 +504,7 @@ static int file_cmd_size(Jim_Interp *interp, int argc, Jim_Obj *const *argv) struct stat sb; if (file_stat(interp, argv[0], &sb) != JIM_OK) { - return JIM_ERR; + return JIM_ERR; } Jim_SetResultInt(interp, sb.st_size); return JIM_OK; @@ -467,7 +516,7 @@ static int file_cmd_isdirectory(Jim_Interp *interp, int argc, Jim_Obj *const *ar int ret = 0; if (file_stat(interp, argv[0], &sb) == JIM_OK) { - ret = S_ISDIR(sb.st_mode); + ret = S_ISDIR(sb.st_mode); } Jim_SetResultInt(interp, ret); return JIM_OK; @@ -643,7 +692,7 @@ static const jim_subcmd_type command_table[] = { .function = file_cmd_delete, .minargs = 1, .maxargs = -1, - .description = "Deletes the file(s)" + .description = "Deletes the files or empty directories" }, { .cmd = "mkdir", .args = "dir ...", diff --git a/tests/filedir.test b/tests/filedir.test new file mode 100644 index 0000000..cb1f3a7 --- /dev/null +++ b/tests/filedir.test @@ -0,0 +1,65 @@ +source testing.tcl + +catch { + exec rm -rf tmp + exec mkdir tmp + exec touch tmp/file + exec mkdir tmp/dir +} + +test mkdir-1.1 "Simple dir" { + file mkdir tmp/abc + file isdir tmp/abc +} {1} + +test mkdir-1.2 "Create missing parents" { + file mkdir tmp/def/ghi/jkl + file isdir tmp/def/ghi/jkl +} {1} + +test mkdir-1.3 "Existing dir" { + file mkdir tmp/dir + file isdir tmp/dir +} {1} + +test mkdir-1.4 "Child of existing dir" { + file mkdir tmp/dir/child + file isdir tmp/dir/child +} {1} + +test mkdir-1.5 "Create dir over existing file" { + list [catch {file mkdir tmp/file} msg] [file isdir tmp/file] +} {1 0} + +test mkdir-1.6 "Create dir below existing file" { + list [catch {file mkdir tmp/file/dir} msg] [file isdir tmp/file/dir] +} {1 0} + +test mkdir-1.8 "Multiple dirs" { + file mkdir tmp/1 tmp/2 tmp/3 + list [file isdir tmp/1] [file isdir tmp/2] [file isdir tmp/3] +} {1 1 1} + +test mkdir-1.7 "Stop on failure" { + catch {file mkdir tmp/4 tmp/file tmp/5} + list [file isdir tmp/4] [file isdir tmp/5] +} {1 0} + +test rmdir-2.0 "Remove existing dir" { + file delete tmp/1 + file isdir tmp/1 +} {0} + +test rmdir-2.1 "Remove missing dir" { + file delete tmp/1 +} {} + +test rmdir-2.2 "Remove non-empty dir" { + catch {file delete tmp/def} +} {1} + +catch { + exec rm -rf tmp +} + +testreport |