aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSteve Bennett <steveb@workware.net.au>2010-03-03 15:54:53 +1000
committerSteve Bennett <steveb@workware.net.au>2010-10-15 11:02:48 +1000
commitdaf20891972d1698d2ee74d5ad75349661a8c9ba (patch)
treed28e9889b8dcabb80bbf4f9895d718bb44a6e6f2
parent6a9fcd338b28fe76cb980867632068dd2bec533c (diff)
downloadjimtcl-daf20891972d1698d2ee74d5ad75349661a8c9ba.zip
jimtcl-daf20891972d1698d2ee74d5ad75349661a8c9ba.tar.gz
jimtcl-daf20891972d1698d2ee74d5ad75349661a8c9ba.tar.bz2
Improvements to 'file mkdir', 'file delete'
file mkdir will now create intermediate directories file delete can now also delete empty directories Signed-off-by: Steve Bennett <steveb@workware.net.au>
-rw-r--r--doc/jim_tcl.txt15
-rw-r--r--jim-file.c69
-rw-r--r--tests/filedir.test65
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*
diff --git a/jim-file.c b/jim-file.c
index 74e4254..db7c465 100644
--- a/jim-file.c
+++ b/jim-file.c
@@ -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