summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordkf <donal.k.fellows@manchester.ac.uk>2019-05-25 08:17:42 (GMT)
committerdkf <donal.k.fellows@manchester.ac.uk>2019-05-25 08:17:42 (GMT)
commit815f8822018df40f65d4dc2ce7029a9223da2ab6 (patch)
treee1b6ac62c11e8c4b03a5255b3ce9aec0641ba2d2
parente09e63e37e690f36ffc3b95ea85e966809c4b305 (diff)
parent9290e6c1ace5bd8e3074900948ea3ad677f73832 (diff)
downloadtcl-815f8822018df40f65d4dc2ce7029a9223da2ab6.zip
tcl-815f8822018df40f65d4dc2ce7029a9223da2ab6.tar.gz
tcl-815f8822018df40f65d4dc2ce7029a9223da2ab6.tar.bz2
Merge 8.7
-rw-r--r--doc/coroutine.n106
-rw-r--r--doc/file.n30
-rw-r--r--generic/tclBasic.c286
-rw-r--r--generic/tclCmdAH.c1
-rw-r--r--generic/tclFCmd.c147
-rw-r--r--generic/tclInt.decls6
-rw-r--r--generic/tclInt.h1
-rw-r--r--generic/tclIntDecls.h6
-rw-r--r--generic/tclStubInit.c1
-rw-r--r--tests/cmdAH.test60
-rw-r--r--tests/coroutine.test153
-rw-r--r--tests/interp.test2
-rw-r--r--unix/tclUnixFCmd.c79
-rw-r--r--win/tclWinFCmd.c115
14 files changed, 963 insertions, 30 deletions
diff --git a/doc/coroutine.n b/doc/coroutine.n
index 52775ef..3c1cf6c 100644
--- a/doc/coroutine.n
+++ b/doc/coroutine.n
@@ -14,10 +14,13 @@ coroutine, yield, yieldto \- Create and produce values from coroutines
.nf
\fBcoroutine \fIname command\fR ?\fIarg...\fR?
\fByield\fR ?\fIvalue\fR?
-.VS TIP396
\fByieldto\fR \fIcommand\fR ?\fIarg...\fR?
\fIname\fR ?\fIvalue...\fR?
-.VE TIP396
+.sp
+.VS "8.7, TIP383"
+\fBcoroinject \fIcoroName command\fR ?\fIarg...\fR?
+\fBcoroprobe \fIcoroName command\fR ?\fIarg...\fR?
+.VE "8.7, TIP383"
.fi
.BE
.SH DESCRIPTION
@@ -39,7 +42,6 @@ the context to be suspended. If the coroutine context never yields and instead
returns conventionally, the result of the \fBcoroutine\fR command will be the
result of the evaluation of the context.
.PP
-.VS TIP396
The coroutine may also suspend its execution by use of the \fByieldto\fR
command, which instead of returning, cedes execution to some command called
\fIcommand\fR (resolved in the context of the coroutine) and to which \fIany
@@ -58,11 +60,10 @@ with multiple arguments is by using \fByieldto\fR and the \fBreturn\fR
command, like this:
.PP
.CS
-proc yieldm {value} {
- \fByieldto\fR return -level 0 $value
+proc yieldMultiple {value} {
+ tailcall \fByieldto\fR string cat $value
}
.CE
-.VE TIP396
.PP
The coroutine can also be deleted by destroying the command \fIname\fR, and
the name of the current coroutine can be retrieved by using
@@ -75,6 +76,51 @@ At the point when \fIcommand\fR is called, the current namespace will be the
global namespace and there will be no stack frames above it (in the sense of
\fBupvar\fR and \fBuplevel\fR). However, which command to call will be
determined in the namespace that the \fBcoroutine\fR command was called from.
+.PP
+.VS "8.7, TIP383"
+A suspended coroutine (i.e., one that has \fByield\fRed or \fByieldto\fR-d)
+may have its state inspected (or modified) at that point by using
+\fBcoroprobe\fR to run a command at the point where the coroutine is at. The
+command takes the name of the coroutine to run the command in, \fIcoroName\fR,
+and the name of a command (any any arguments it requires) to immediately run
+at that point. The result of that command is the result of the \fBcoroprobe\fR
+command, and the gross state of the coroutine remains the same afterwards
+(i.e., the coroutine is still expecting the results of a \fByield\fR or
+\fByieldto\fR as before) though variables may have been changed.
+.PP
+Similarly, the \fBcoroinject\fR command may be used to place a command to be
+run inside a suspended coroutine (when it is resumed) to process arguments,
+with quite a bit of similarity to \fBcoroprobe\fR. However, with
+\fBcoroinject\fR there are several key differences:
+.VE "8.7, TIP383"
+.IP \(bu
+.VS "8.7, TIP383"
+The coroutine is not immediately resumed after the injection has been done. A
+consequence of this is that multiple injections may be done before the
+coroutine is resumed. There injected commands are performed in \fIreverse
+order of definition\fR (that is, they are internally stored on a stack).
+.VE "8.7, TIP383"
+.IP \(bu
+.VS "8.7, TIP383"
+An additional two arguments are appended to the list of arguments to be run
+(that is, the \fIcommand\fR and its \fIargs\fR are extended by two elements).
+The first is the name of the command that suspended the coroutine (\fByield\fR
+or \fByieldto\fR), and the second is the argument (or list of arguments, in
+the case of \fByieldto\fR) that is the current resumption value.
+.VE "8.7, TIP383"
+.IP \(bu
+.VS "8.7, TIP383"
+The result of the injected command is used as the result of the \fByield\fR or
+\fByieldto\fR that caused the coroutine to become suspended. Where there are
+multiple injected commands, the result of one becomes the resumption value
+processed by the next.
+.PP
+The injection is a one-off. It is not retained once it has been executed. It
+may \fByield\fR or \fByieldto\fR as part of its execution.
+.PP
+Note that running coroutines may be neither probed nor injected; the
+operations may only be applied to
+.VE "8.7, TIP383"
.SH EXAMPLES
.PP
This example shows a coroutine that will produce an infinite sequence of
@@ -138,7 +184,6 @@ for {set i 1} {$i <= 20} {incr i} {
}
.CE
.PP
-.VS TIP396
This example shows how a value can be passed around a group of three
coroutines that yield to each other:
.PP
@@ -150,14 +195,57 @@ proc juggler {name target {value ""}} {
while {$value ne ""} {
puts "$name : $value"
set value [string range $value 0 end-1]
- lassign [\fByieldto\fR $target $value] value
+ lassign [\fByieldto\fR \fI$target\fR $value] value
}
}
\fBcoroutine\fR j1 juggler Larry [
\fBcoroutine\fR j2 juggler Curly [
\fBcoroutine\fR j3 juggler Moe j1]] "Nyuck!Nyuck!Nyuck!"
.CE
-.VE TIP396
+.PP
+.VS "8.7, TIP383"
+This example shows a simple coroutine that collects non-empty values and
+returns a list of them when not given an argument. It also shows how we can
+look inside the coroutine to find out what it is doing, and how we can modify
+the input on a one-off basis.
+.PP
+.CS
+proc collectorImpl {} {
+ set me [info coroutine]
+ set accumulator {}
+ for {set val [\fByield\fR $me]} {$val ne ""} {set val [\fByield\fR]} {
+ lappend accumulator $val
+ }
+ return $accumulator
+}
+
+\fBcoroutine\fR collect collectorImpl
+\fIcollect\fR 123
+\fIcollect\fR "abc def"
+\fIcollect\fR 456
+
+puts [\fBcoroprobe \fIcollect\fR set accumulator]
+# ==> 123 {abc def} 456
+
+\fIcollect\fR "pqr"
+
+\fBcoroinject \fIcollect\fR apply {{type value} {
+ puts "Received '$value' at a $type in [info coroutine]"
+ return [string toupper $value]
+}}
+
+\fIcollect\fR rst
+# ==> Received 'rst' at a yield in ::collect
+\fIcollect\fR xyz
+
+puts [\fIcollect\fR]
+# ==> 123 {abc def} 456 pqr RST xyz
+.CE
+.PP
+This example shows a simple coroutine that collects non-empty values and
+returns a list of them when not given an argument. It also shows how we can
+look inside the coroutine to find out what it is doing.
+.VE "8.7, TIP383"
.SS "DETAILED SEMANTICS"
.PP
This example demonstrates that coroutines start from the global namespace, and
diff --git a/doc/file.n b/doc/file.n
index ad35dd5..6f97f0b 100644
--- a/doc/file.n
+++ b/doc/file.n
@@ -433,6 +433,36 @@ If \fIname\fR contains no separators then returns \fIname\fR. So,
\fBfile tail a/b\fR, \fBfile tail a/b/\fR and \fBfile tail b\fR all
return \fBb\fR.
.TP
+\fBfile tempdir\fR ?\fItemplate\fR?
+.VS "8.7, TIP 431"
+Creates a temporary directory (guaranteed to be newly created and writable by
+the current script) and returns its name. If \fItemplate\fR is given, it
+specifies one of or both of the existing directory (on a filesystem controlled
+by the operating system) to contain the temporary directory, and the base part
+of the directory name; it is considered to have the location of the directory
+if there is a directory separator in the name, and the base part is everything
+after the last directory separator (if non-empty). The default containing
+directory is determined by system-specific operations, and the default base
+name prefix is
+.QW \fBtcl\fR .
+.RS
+.PP
+The following output is typical and illustrative; the actual output will vary
+between platforms:
+.PP
+.CS
+% \fBfile tempdir\fR
+/var/tmp/tcl_u0kuy5
+ % \fBfile tempdir\fR /tmp/myapp
+/tmp/myapp_8o7r9L
+% \fBfile tempdir\fR /tmp/
+/tmp/tcl_1mOJHD
+% \fBfile tempdir\fR myapp
+/var/tmp/myapp_0ihS0n
+.CE
+.RE
+.VE "8.7, TIP 431"
+.TP
\fBfile tempfile\fR ?\fInameVar\fR? ?\fItemplate\fR?
'\" TIP #210
.VS 8.6
diff --git a/generic/tclBasic.c b/generic/tclBasic.c
index ac32293..bb7819a 100644
--- a/generic/tclBasic.c
+++ b/generic/tclBasic.c
@@ -155,9 +155,13 @@ static Tcl_NRPostProc TEOV_RunLeaveTraces;
static Tcl_NRPostProc EvalObjvCore;
static Tcl_NRPostProc Dispatch;
-static Tcl_ObjCmdProc NRCoroInjectObjCmd;
+static Tcl_ObjCmdProc NRInjectObjCmd;
static Tcl_NRPostProc NRPostInvoke;
static Tcl_ObjCmdProc CoroTypeObjCmd;
+static Tcl_ObjCmdProc TclNRCoroInjectObjCmd;
+static Tcl_ObjCmdProc TclNRCoroProbeObjCmd;
+static Tcl_NRPostProc InjectHandler;
+static Tcl_NRPostProc InjectHandlerPostCall;
MODULE_SCOPE const TclStubs tclStubs;
@@ -224,6 +228,8 @@ static const CmdInfo builtInCmds[] = {
{"catch", Tcl_CatchObjCmd, TclCompileCatchCmd, TclNRCatchObjCmd, CMD_IS_SAFE},
{"concat", Tcl_ConcatObjCmd, TclCompileConcatCmd, NULL, CMD_IS_SAFE},
{"continue", Tcl_ContinueObjCmd, TclCompileContinueCmd, NULL, CMD_IS_SAFE},
+ {"coroinject", NULL, NULL, TclNRCoroInjectObjCmd, CMD_IS_SAFE},
+ {"coroprobe", NULL, NULL, TclNRCoroProbeObjCmd, CMD_IS_SAFE},
{"coroutine", NULL, NULL, TclNRCoroutineObjCmd, CMD_IS_SAFE},
{"error", Tcl_ErrorObjCmd, TclCompileErrorCmd, NULL, CMD_IS_SAFE},
{"eval", Tcl_EvalObjCmd, NULL, TclNREvalObjCmd, CMD_IS_SAFE},
@@ -347,6 +353,7 @@ static const UnsafeEnsembleInfo unsafeEnsembleCommands[] = {
{"file", "size"},
{"file", "stat"},
{"file", "tail"},
+ {"file", "tempdir"},
{"file", "tempfile"},
{"file", "type"},
{"file", "volumes"},
@@ -940,7 +947,7 @@ Tcl_CreateInterp(void)
/* Coroutine monkeybusiness */
Tcl_NRCreateCommand(interp, "::tcl::unsupported::inject", NULL,
- NRCoroInjectObjCmd, NULL, NULL);
+ NRInjectObjCmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "::tcl::unsupported::corotype",
CoroTypeObjCmd, NULL, NULL);
@@ -8527,27 +8534,47 @@ CoroTypeObjCmd(
/*
*----------------------------------------------------------------------
*
- * NRCoroInjectObjCmd --
+ * TclNRCoroInjectObjCmd, TclNRCoroProbeObjCmd --
*
- * Implementation of [::tcl::unsupported::inject] command.
+ * Implementation of [coroinject] and [coroprobe] commands.
*
*----------------------------------------------------------------------
*/
+static inline CoroutineData *
+GetCoroutineFromObj(
+ Tcl_Interp *interp,
+ Tcl_Obj *objPtr,
+ const char *errMsg)
+{
+ /*
+ * How to get a coroutine from its handle.
+ */
+
+ Command *cmdPtr = (Command *) Tcl_GetCommandFromObj(interp, objPtr);
+
+ if ((!cmdPtr) || (cmdPtr->nreProc != TclNRInterpCoroutine)) {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(errMsg, -1));
+ Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "COROUTINE",
+ TclGetString(objPtr), NULL);
+ return NULL;
+ }
+ return cmdPtr->objClientData;
+}
+
static int
-NRCoroInjectObjCmd(
+TclNRCoroInjectObjCmd(
ClientData clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *const objv[])
{
- Command *cmdPtr;
CoroutineData *corPtr;
ExecEnv *savedEEPtr = iPtr->execEnvPtr;
/*
* Usage more or less like tailcall:
- * inject coroName cmd ?arg1 arg2 ...?
+ * coroinject coroName cmd ?arg1 arg2 ...?
*/
if (objc < 3) {
@@ -8555,16 +8582,249 @@ NRCoroInjectObjCmd(
return TCL_ERROR;
}
- cmdPtr = (Command *) Tcl_GetCommandFromObj(interp, objv[1]);
- if ((!cmdPtr) || (cmdPtr->nreProc != TclNRInterpCoroutine)) {
+ corPtr = GetCoroutineFromObj(interp, objv[1],
+ "can only inject a command into a coroutine");
+ if (!corPtr) {
+ return TCL_ERROR;
+ }
+ if (!COR_IS_SUSPENDED(corPtr)) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
- "can only inject a command into a coroutine", -1));
- Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "COROUTINE",
- TclGetString(objv[1]), NULL);
+ "can only inject a command into a suspended coroutine", -1));
+ Tcl_SetErrorCode(interp, "TCL", "COROUTINE", "ACTIVE", NULL);
return TCL_ERROR;
}
- corPtr = cmdPtr->objClientData;
+ /*
+ * Add the callback to the coro's execEnv, so that it is the first thing
+ * to happen when the coro is resumed.
+ */
+
+ iPtr->execEnvPtr = corPtr->eePtr;
+ TclNRAddCallback(interp, InjectHandler, corPtr,
+ Tcl_NewListObj(objc - 2, objv + 2), INT2PTR(corPtr->nargs), NULL);
+ iPtr->execEnvPtr = savedEEPtr;
+
+ return TCL_OK;
+}
+
+static int
+TclNRCoroProbeObjCmd(
+ ClientData clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *const objv[])
+{
+ CoroutineData *corPtr;
+ ExecEnv *savedEEPtr = iPtr->execEnvPtr;
+ int numLevels, unused;
+ int *stackLevel = &unused;
+
+ /*
+ * Usage more or less like tailcall:
+ * coroprobe coroName cmd ?arg1 arg2 ...?
+ */
+
+ if (objc < 3) {
+ Tcl_WrongNumArgs(interp, 1, objv, "coroName cmd ?arg1 arg2 ...?");
+ return TCL_ERROR;
+ }
+
+ corPtr = GetCoroutineFromObj(interp, objv[1],
+ "can only inject a probe command into a coroutine");
+ if (!corPtr) {
+ return TCL_ERROR;
+ }
+ if (!COR_IS_SUSPENDED(corPtr)) {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(
+ "can only inject a probe command into a suspended coroutine",
+ -1));
+ Tcl_SetErrorCode(interp, "TCL", "COROUTINE", "ACTIVE", NULL);
+ return TCL_ERROR;
+ }
+
+ /*
+ * Add the callback to the coro's execEnv, so that it is the first thing
+ * to happen when the coro is resumed.
+ */
+
+ iPtr->execEnvPtr = corPtr->eePtr;
+ TclNRAddCallback(interp, InjectHandler, corPtr,
+ Tcl_NewListObj(objc - 2, objv + 2), INT2PTR(corPtr->nargs), corPtr);
+ iPtr->execEnvPtr = savedEEPtr;
+
+ /*
+ * Now we immediately transfer control to the coroutine to run our probe.
+ * TRICKY STUFF copied from the [yield] implementation.
+ *
+ * Push the callback to restore the caller's context on yield back.
+ */
+
+ TclNRAddCallback(interp, NRCoroutineCallerCallback, corPtr,
+ NULL, NULL, NULL);
+
+ /*
+ * Record the stackLevel at which the resume is happening, then swap
+ * the interp's environment to make it suitable to run this coroutine.
+ */
+
+ corPtr->stackLevel = stackLevel;
+ numLevels = corPtr->auxNumLevels;
+ corPtr->auxNumLevels = iPtr->numLevels;
+
+ /*
+ * Do the actual stack swap.
+ */
+
+ SAVE_CONTEXT(corPtr->caller);
+ corPtr->callerEEPtr = iPtr->execEnvPtr;
+ RESTORE_CONTEXT(corPtr->running);
+ iPtr->execEnvPtr = corPtr->eePtr;
+ iPtr->numLevels += numLevels;
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * InjectHandler, InjectHandlerPostProc --
+ *
+ * Part of the implementation of [coroinject] and [coroprobe]. These are
+ * run inside the context of the coroutine being injected/probed into.
+ *
+ * InjectHandler runs a script (possibly adding arguments) in the context
+ * of the coroutine. The script is specified as a one-shot list (with
+ * reference count equal to 1) in data[1]. This function also arranges
+ * for InjectHandlerPostProc to be the part that runs after the script
+ * completes.
+ *
+ * InjectHandlerPostProc cleans up after InjectHandler (deleting the
+ * list) and, for the [coroprobe] command *only*, yields back to the
+ * caller context (i.e., where [coroprobe] was run).
+ *s
+ *----------------------------------------------------------------------
+ */
+
+static int
+InjectHandler(
+ ClientData data[],
+ Tcl_Interp *interp,
+ int result)
+{
+ CoroutineData *corPtr = data[0];
+ Tcl_Obj *listPtr = data[1];
+ int nargs = PTR2INT(data[2]);
+ ClientData isProbe = data[3];
+ int objc;
+ Tcl_Obj **objv;
+
+ if (!isProbe) {
+ /*
+ * If this is [coroinject], add the extra arguments now.
+ */
+
+ if (nargs == COROUTINE_ARGUMENTS_SINGLE_OPTIONAL) {
+ Tcl_ListObjAppendElement(NULL, listPtr,
+ Tcl_NewStringObj("yield", -1));
+ } else if (nargs == COROUTINE_ARGUMENTS_ARBITRARY) {
+ Tcl_ListObjAppendElement(NULL, listPtr,
+ Tcl_NewStringObj("yieldto", -1));
+ } else {
+ /*
+ * I don't think this is reachable...
+ */
+
+ Tcl_ListObjAppendElement(NULL, listPtr, Tcl_NewIntObj(nargs));
+ }
+ Tcl_ListObjAppendElement(NULL, listPtr, Tcl_GetObjResult(interp));
+ }
+
+ /*
+ * Call the user's script; we're in the right place.
+ */
+
+ Tcl_IncrRefCount(listPtr);
+ TclMarkTailcall(interp);
+ TclNRAddCallback(interp, InjectHandlerPostCall, corPtr, listPtr,
+ INT2PTR(nargs), isProbe);
+ TclListObjGetElements(NULL, listPtr, &objc, &objv);
+ return TclNREvalObjv(interp, objc, objv, 0, NULL);
+}
+
+static int
+InjectHandlerPostCall(
+ ClientData data[],
+ Tcl_Interp *interp,
+ int result)
+{
+ CoroutineData *corPtr = data[0];
+ Tcl_Obj *listPtr = data[1];
+ int nargs = PTR2INT(data[2]);
+ ClientData isProbe = data[3];
+ int numLevels;
+
+ /*
+ * Delete the command words for what we just executed.
+ */
+
+ Tcl_DecrRefCount(listPtr);
+
+ /*
+ * If we were doing a probe, splice ourselves back out of the stack
+ * cleanly here. General injection should instead just look after itself.
+ *
+ * Code from guts of [yield] implementation.
+ */
+
+ if (isProbe) {
+ if (result == TCL_ERROR) {
+ Tcl_AddErrorInfo(interp,
+ "\n (injected coroutine probe command)");
+ }
+ corPtr->nargs = nargs;
+ corPtr->stackLevel = NULL;
+ numLevels = iPtr->numLevels;
+ iPtr->numLevels = corPtr->auxNumLevels;
+ corPtr->auxNumLevels = numLevels - corPtr->auxNumLevels;
+ iPtr->execEnvPtr = corPtr->callerEEPtr;
+ }
+ return result;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * NRInjectObjCmd --
+ *
+ * Implementation of [::tcl::unsupported::inject] command.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+NRInjectObjCmd(
+ ClientData clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *const objv[])
+{
+ CoroutineData *corPtr;
+ ExecEnv *savedEEPtr = iPtr->execEnvPtr;
+
+ /*
+ * Usage more or less like tailcall:
+ * inject coroName cmd ?arg1 arg2 ...?
+ */
+
+ if (objc < 3) {
+ Tcl_WrongNumArgs(interp, 1, objv, "coroName cmd ?arg1 arg2 ...?");
+ return TCL_ERROR;
+ }
+
+ corPtr = GetCoroutineFromObj(interp, objv[1],
+ "can only inject a command into a coroutine");
+ if (!corPtr) {
+ return TCL_ERROR;
+ }
if (!COR_IS_SUSPENDED(corPtr)) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"can only inject a command into a suspended coroutine", -1));
diff --git a/generic/tclCmdAH.c b/generic/tclCmdAH.c
index 0893ba4..68c0eb4 100644
--- a/generic/tclCmdAH.c
+++ b/generic/tclCmdAH.c
@@ -956,6 +956,7 @@ TclInitFileCmd(
{"stat", FileAttrStatCmd, TclCompileBasic2ArgCmd, NULL, NULL, 1},
{"system", PathFilesystemCmd, TclCompileBasic0Or1ArgCmd, NULL, NULL, 0},
{"tail", PathTailCmd, TclCompileBasic1ArgCmd, NULL, NULL, 1},
+ {"tempdir", TclFileTempDirCmd, TclCompileBasic0Or1ArgCmd, NULL, NULL, 1},
{"tempfile", TclFileTemporaryCmd, TclCompileBasic0To2ArgCmd, NULL, NULL, 1},
{"type", FileAttrTypeCmd, TclCompileBasic1ArgCmd, NULL, NULL, 1},
{"volumes", FilesystemVolumesCmd, TclCompileBasic0ArgCmd, NULL, NULL, 1},
diff --git a/generic/tclFCmd.c b/generic/tclFCmd.c
index 5c6883d..264711d 100644
--- a/generic/tclFCmd.c
+++ b/generic/tclFCmd.c
@@ -1345,7 +1345,7 @@ TclFileReadLinkCmd(
/*
*---------------------------------------------------------------------------
*
- * TclFileTemporaryCmd
+ * TclFileTemporaryCmd --
*
* This function implements the "tempfile" subcommand of the "file"
* command.
@@ -1505,6 +1505,151 @@ TclFileTemporaryCmd(
}
/*
+ *---------------------------------------------------------------------------
+ *
+ * TclFileTempDirCmd --
+ *
+ * This function implements the "tempdir" subcommand of the "file"
+ * command.
+ *
+ * Results:
+ * Returns a standard Tcl result.
+ *
+ * Side effects:
+ * Creates a temporary directory.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+int
+TclFileTempDirCmd(
+ ClientData clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *const objv[])
+{
+ Tcl_Obj *dirNameObj; /* Object that will contain the directory
+ * name. */
+ Tcl_Obj *baseDirObj = NULL, *nameBaseObj = NULL;
+ /* Pieces of template. Each piece is NULL if
+ * it is omitted. The platform temporary file
+ * engine might ignore some pieces. */
+
+ if (objc < 1 || objc > 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "?template?");
+ return TCL_ERROR;
+ }
+
+ if (objc > 1) {
+ int length;
+ Tcl_Obj *templateObj = objv[1];
+ const char *string = TclGetStringFromObj(templateObj, &length);
+ const int onWindows = (tclPlatform == TCL_PLATFORM_WINDOWS);
+
+ /*
+ * Treat an empty string as if it wasn't there.
+ */
+
+ if (length == 0) {
+ goto makeTemporary;
+ }
+
+ /*
+ * The template only gives a directory if there is a directory
+ * separator in it, and only gives a base name if there's at least one
+ * character after the last directory separator.
+ */
+
+ if (strchr(string, '/') == NULL
+ && (!onWindows || strchr(string, '\\') == NULL)) {
+ /*
+ * No directory separator, so just assume we have a file name.
+ * This is a bit wrong on Windows where we could have problems
+ * with disk name prefixes... but those are much less common in
+ * naked form so we just pass through and let the OS figure it out
+ * instead.
+ */
+
+ nameBaseObj = templateObj;
+ Tcl_IncrRefCount(nameBaseObj);
+ } else if (string[length-1] != '/'
+ && (!onWindows || string[length-1] != '\\')) {
+ /*
+ * If the template has a non-terminal directory separator, split
+ * into dirname and tail.
+ */
+
+ baseDirObj = TclPathPart(interp, templateObj, TCL_PATH_DIRNAME);
+ nameBaseObj = TclPathPart(interp, templateObj, TCL_PATH_TAIL);
+ } else {
+ /*
+ * Otherwise, there must be a terminal directory separator, so
+ * just the directory is given.
+ */
+
+ baseDirObj = templateObj;
+ Tcl_IncrRefCount(baseDirObj);
+ }
+
+ /*
+ * Only allow creation of temporary directories in the native
+ * filesystem since they are frequently used for integration with
+ * external tools or system libraries.
+ */
+
+ if (baseDirObj != NULL && Tcl_FSGetFileSystemForPath(baseDirObj)
+ != &tclNativeFilesystem) {
+ TclDecrRefCount(baseDirObj);
+ baseDirObj = NULL;
+ }
+ }
+
+ /*
+ * Convert empty parts of the template into unspecified parts.
+ */
+
+ if (baseDirObj && !TclGetString(baseDirObj)[0]) {
+ TclDecrRefCount(baseDirObj);
+ baseDirObj = NULL;
+ }
+ if (nameBaseObj && !TclGetString(nameBaseObj)[0]) {
+ TclDecrRefCount(nameBaseObj);
+ nameBaseObj = NULL;
+ }
+
+ /*
+ * Create and open the temporary file.
+ */
+
+ makeTemporary:
+ dirNameObj = TclpCreateTemporaryDirectory(baseDirObj, nameBaseObj);
+
+ /*
+ * If we created pieces of template, get rid of them now.
+ */
+
+ if (baseDirObj) {
+ TclDecrRefCount(baseDirObj);
+ }
+ if (nameBaseObj) {
+ TclDecrRefCount(nameBaseObj);
+ }
+
+ /*
+ * Deal with results.
+ */
+
+ if (dirNameObj == NULL) {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "can't create temporary directory: %s",
+ Tcl_PosixError(interp)));
+ return TCL_ERROR;
+ }
+ Tcl_SetObjResult(interp, dirNameObj);
+ return TCL_OK;
+}
+
+/*
* Local Variables:
* mode: c
* c-basic-offset: 4
diff --git a/generic/tclInt.decls b/generic/tclInt.decls
index c0d7696..6dc748b 100644
--- a/generic/tclInt.decls
+++ b/generic/tclInt.decls
@@ -1057,6 +1057,12 @@ declare 257 {
void TclStaticPackage(Tcl_Interp *interp, const char *pkgName,
Tcl_PackageInitProc *initProc, Tcl_PackageInitProc *safeInitProc)
}
+
+# TIP 431: temporary directory creation function
+declare 258 {
+ Tcl_Obj *TclpCreateTemporaryDirectory(Tcl_Obj *dirObj,
+ Tcl_Obj *basenameObj)
+}
##############################################################################
diff --git a/generic/tclInt.h b/generic/tclInt.h
index 0c884b3..389b5da 100644
--- a/generic/tclInt.h
+++ b/generic/tclInt.h
@@ -2896,6 +2896,7 @@ MODULE_SCOPE Tcl_ObjCmdProc TclFileLinkCmd;
MODULE_SCOPE Tcl_ObjCmdProc TclFileMakeDirsCmd;
MODULE_SCOPE Tcl_ObjCmdProc TclFileReadLinkCmd;
MODULE_SCOPE Tcl_ObjCmdProc TclFileRenameCmd;
+MODULE_SCOPE Tcl_ObjCmdProc TclFileTempDirCmd;
MODULE_SCOPE Tcl_ObjCmdProc TclFileTemporaryCmd;
MODULE_SCOPE void TclCreateLateExitHandler(Tcl_ExitProc *proc,
void *clientData);
diff --git a/generic/tclIntDecls.h b/generic/tclIntDecls.h
index 61249c0..ec86071 100644
--- a/generic/tclIntDecls.h
+++ b/generic/tclIntDecls.h
@@ -583,6 +583,9 @@ EXTERN void TclStaticPackage(Tcl_Interp *interp,
const char *pkgName,
Tcl_PackageInitProc *initProc,
Tcl_PackageInitProc *safeInitProc);
+/* 258 */
+EXTERN Tcl_Obj * TclpCreateTemporaryDirectory(Tcl_Obj *dirObj,
+ Tcl_Obj *basenameObj);
typedef struct TclIntStubs {
int magic;
@@ -846,6 +849,7 @@ typedef struct TclIntStubs {
int (*tclPtrObjMakeUpvar) (Tcl_Interp *interp, Tcl_Var otherPtr, Tcl_Obj *myNamePtr, int myFlags); /* 255 */
int (*tclPtrUnsetVar) (Tcl_Interp *interp, Tcl_Var varPtr, Tcl_Var arrayPtr, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, const int flags); /* 256 */
void (*tclStaticPackage) (Tcl_Interp *interp, const char *pkgName, Tcl_PackageInitProc *initProc, Tcl_PackageInitProc *safeInitProc); /* 257 */
+ Tcl_Obj * (*tclpCreateTemporaryDirectory) (Tcl_Obj *dirObj, Tcl_Obj *basenameObj); /* 258 */
} TclIntStubs;
extern const TclIntStubs *tclIntStubsPtr;
@@ -1263,6 +1267,8 @@ extern const TclIntStubs *tclIntStubsPtr;
(tclIntStubsPtr->tclPtrUnsetVar) /* 256 */
#define TclStaticPackage \
(tclIntStubsPtr->tclStaticPackage) /* 257 */
+#define TclpCreateTemporaryDirectory \
+ (tclIntStubsPtr->tclpCreateTemporaryDirectory) /* 258 */
#endif /* defined(USE_TCL_STUBS) */
diff --git a/generic/tclStubInit.c b/generic/tclStubInit.c
index 1b3214d..a0973fc 100644
--- a/generic/tclStubInit.c
+++ b/generic/tclStubInit.c
@@ -458,6 +458,7 @@ static const TclIntStubs tclIntStubs = {
TclPtrObjMakeUpvar, /* 255 */
TclPtrUnsetVar, /* 256 */
TclStaticPackage, /* 257 */
+ TclpCreateTemporaryDirectory, /* 258 */
};
static const TclIntPlatStubs tclIntPlatStubs = {
diff --git a/tests/cmdAH.test b/tests/cmdAH.test
index 69394ac..54c4413 100644
--- a/tests/cmdAH.test
+++ b/tests/cmdAH.test
@@ -233,7 +233,7 @@ test cmdAH-5.1 {Tcl_FileObjCmd} -returnCodes error -body {
} -result {wrong # args: should be "file subcommand ?arg ...?"}
test cmdAH-5.2 {Tcl_FileObjCmd} -returnCodes error -body {
file x
-} -result {unknown or ambiguous subcommand "x": must be atime, attributes, channels, copy, delete, dirname, executable, exists, extension, isdirectory, isfile, join, link, lstat, mkdir, mtime, nativename, normalize, owned, pathtype, readable, readlink, rename, rootname, separator, size, split, stat, system, tail, tempfile, type, volumes, or writable}
+} -result {unknown or ambiguous subcommand "x": must be atime, attributes, channels, copy, delete, dirname, executable, exists, extension, isdirectory, isfile, join, link, lstat, mkdir, mtime, nativename, normalize, owned, pathtype, readable, readlink, rename, rootname, separator, size, split, stat, system, tail, tempdir, tempfile, type, volumes, or writable}
test cmdAH-5.3 {Tcl_FileObjCmd} -returnCodes error -body {
file exists
} -result {wrong # args: should be "file exists name"}
@@ -1524,7 +1524,7 @@ test cmdAH-29.6.1 {
# Error conditions
test cmdAH-30.1 {Tcl_FileObjCmd: error conditions} -returnCodes error -body {
file gorp x
-} -result {unknown or ambiguous subcommand "gorp": must be atime, attributes, channels, copy, delete, dirname, executable, exists, extension, isdirectory, isfile, join, link, lstat, mkdir, mtime, nativename, normalize, owned, pathtype, readable, readlink, rename, rootname, separator, size, split, stat, system, tail, tempfile, type, volumes, or writable}
+} -result {unknown or ambiguous subcommand "gorp": must be atime, attributes, channels, copy, delete, dirname, executable, exists, extension, isdirectory, isfile, join, link, lstat, mkdir, mtime, nativename, normalize, owned, pathtype, readable, readlink, rename, rootname, separator, size, split, stat, system, tail, tempdir, tempfile, type, volumes, or writable}
test cmdAH-30.2 {Tcl_FileObjCmd: error conditions} -returnCodes error -body {
file ex x
} -match glob -result {unknown or ambiguous subcommand "ex": must be *}
@@ -1656,6 +1656,62 @@ test cmdAH-32.6 {file tempfile - templates} -body {
} -constraints {unix nonPortable} -cleanup {
catch {file delete $name}
} -result ok
+
+test cmdAH-33.1 {file tempdir} -body {
+ file tempdir a b
+} -returnCodes error -result {wrong # args: should be "file tempdir ?template?"}
+test cmdAH-33.2 {file tempdir} -body {
+ set d [file tempdir]
+ list [file tail $d] [file exists $d] [file type $d] \
+ [glob -nocomplain -directory $d *]
+} -match glob -result {tcl_* 1 directory {}} -cleanup {
+ catch {file delete $d}
+}
+test cmdAH-33.3 {file tempdir} -body {
+ set d [file tempdir gorp]
+ list [file tail $d] [file exists $d] [file type $d] \
+ [glob -nocomplain -directory $d *]
+} -match glob -result {gorp_* 1 directory {}} -cleanup {
+ catch {file delete $d}
+}
+test cmdAH-33.4 {file tempdir} -setup {
+ set base [file join [temporaryDirectory] gorp]
+ file mkdir $base
+} -body {
+ set pre [glob -nocomplain -directory $base *]
+ set d [file normalize [file tempdir $base/]]
+ list [string map [list $base GORP:] $d] [file exists $d] [file type $d] \
+ $pre [glob -nocomplain -directory $d *]
+} -match glob -result {GORP:/tcl_* 1 directory {} {}} -cleanup {
+ catch {file delete -force $base}
+}
+test cmdAH-33.5 {file tempdir} -setup {
+ set base [file join [temporaryDirectory] gorp]
+ file mkdir $base
+} -body {
+ set pre [glob -nocomplain -directory $base *]
+ set d [file normalize [file tempdir $base/gorp]]
+ list [string map [list $base GORP:] $d] [file exists $d] [file type $d] \
+ $pre [glob -nocomplain -directory $d *]
+} -match glob -result {GORP:/gorp_* 1 directory {} {}} -cleanup {
+ catch {file delete -force $base}
+}
+test cmdAH-33.6 {file tempdir: missing parent dir} -setup {
+ set base [file join [temporaryDirectory] gorp]
+ file mkdir $base
+} -returnCodes error -body {
+ file tempdir $base/quux/
+} -cleanup {
+ catch {file delete -force $base}
+} -result {can't create temporary directory: no such file or directory}
+test cmdAH-33.7 {file tempdir: missing parent dir} -setup {
+ set base [file join [temporaryDirectory] gorp]
+ file mkdir $base
+} -returnCodes error -body {
+ file tempdir $base/quux/foobar
+} -cleanup {
+ catch {file delete -force $base}
+} -result {can't create temporary directory: no such file or directory}
# This shouldn't work, but just in case a test above failed...
catch {close $newFileId}
diff --git a/tests/coroutine.test b/tests/coroutine.test
index ffb9eb9..86a5481 100644
--- a/tests/coroutine.test
+++ b/tests/coroutine.test
@@ -793,7 +793,152 @@ test coroutine-8.1.2 {coro inject with result, ticket 42202ba1e5ff566e} -body {
set result
} -result {inject-executed}
-test coroutine-9.1 {coro type} {
+test coroutine-9.1 {coroprobe with yield} -body {
+ coroutine demo apply {{} { foreach i {1 2} yield }}
+ list [coroprobe demo set i] [demo] [coroprobe demo set i] [demo]
+} -cleanup {
+ catch {rename demo {}}
+} -result {1 {} 2 {}}
+test coroutine-9.2 {coroprobe with yieldto} -body {
+ coroutine demo apply {{} { lmap i {1 2} {yieldto string cat} }}
+ list [coroprobe demo set i] [demo a b] [coroprobe demo set i] [demo c d]
+} -cleanup {
+ catch {rename demo {}}
+} -result {1 {} 2 {{a b} {c d}}}
+test coroutine-9.3 {coroprobe errors} -setup {
+ catch {rename demo {}}
+} -body {
+ coroprobe demo set i
+} -returnCodes error -result {can only inject a probe command into a coroutine}
+test coroutine-9.4 {coroprobe errors} -body {
+ proc demo {} { foreach i {1 2} yield }
+ coroprobe demo set i
+} -returnCodes error -cleanup {
+ catch {rename demo {}}
+} -result {can only inject a probe command into a coroutine}
+test coroutine-9.5 {coroprobe errors} -body {
+ coroutine demo apply {{} { foreach i {1 2} yield }}
+ coroprobe
+} -returnCodes error -cleanup {
+ catch {rename demo {}}
+} -result {wrong # args: should be "coroprobe coroName cmd ?arg1 arg2 ...?"}
+test coroutine-9.6 {coroprobe errors} -body {
+ coroutine demo apply {{} { foreach i {1 2} yield }}
+ coroprobe demo
+} -returnCodes error -cleanup {
+ catch {rename demo {}}
+} -result {wrong # args: should be "coroprobe coroName cmd ?arg1 arg2 ...?"}
+test coroutine-9.7 {coroprobe errors in probe command} -body {
+ coroutine demo apply {{} { foreach i {1 2} yield }}
+ coroprobe demo set
+} -returnCodes error -cleanup {
+ catch {rename demo {}}
+} -result {wrong # args: should be "set varName ?newValue?"}
+test coroutine-9.8 {coroprobe errors in probe command} -body {
+ coroutine demo apply {{} { foreach i {1 2} yield }}
+ list [catch {coroprobe demo set}] [demo] [coroprobe demo set i]
+} -cleanup {
+ catch {rename demo {}}
+} -result {1 {} 2}
+test coroutine-9.9 {coroprobe: advanced features} -setup {
+ set i [interp create]
+} -body {
+ $i eval {
+ coroutine demo apply {{} {
+ set f [info level],[info frame]
+ foreach i {1 2} yield
+ }}
+ coroprobe demo apply {{} {
+ upvar 1 f f
+ list [info coroutine] [info level] [info frame] $f
+ }}
+ }
+} -cleanup {
+ interp delete $i
+} -result {::demo 2 3 1,2}
+
+test coroutine-10.1 {coroinject with yield} -setup {
+ set result {}
+} -body {
+ coroutine demo apply {{} { lmap i {1 2} yield }}
+ coroinject demo apply {{op val} {lappend ::result $op $val}}
+ list $result [demo x] [demo y] $result
+} -cleanup {
+ catch {rename demo {}}
+} -result {{} {} {{yield x} y} {yield x}}
+test coroutine-10.2 {coroinject stacking} -setup {
+ set result {}
+} -body {
+ coroutine demo apply {{} { lmap i {1 2} yield }}
+ coroinject demo apply {{op val} {lappend ::result $op $val A;return $val}}
+ coroinject demo apply {{op val} {lappend ::result $op $val B;return $val}}
+ list $result [demo x] [demo y] $result
+} -cleanup {
+ catch {rename demo {}}
+} -result {{} {} {x y} {yield x B yield x A}}
+test coroutine-10.3 {coroinject with yieldto} -setup {
+ set result {}
+} -body {
+ coroutine demo apply {{} { lmap i {1 2} {yieldto string cat} }}
+ coroinject demo apply {{op val} {lappend ::result $op $val;return $val}}
+ list $result [demo x mp] [demo y le] $result
+} -cleanup {
+ catch {rename demo {}}
+} -result {{} {} {{x mp} {y le}} {yieldto {x mp}}}
+test coroutine-10.4 {coroinject errors} -setup {
+ catch {rename demo {}}
+} -body {
+ coroinject demo set i
+} -returnCodes error -result {can only inject a command into a coroutine}
+test coroutine-10.5 {coroinject errors} -body {
+ proc demo {} { foreach i {1 2} yield }
+ coroinject demo set i
+} -returnCodes error -cleanup {
+ catch {rename demo {}}
+} -result {can only inject a command into a coroutine}
+test coroutine-10.6 {coroinject errors} -body {
+ coroutine demo apply {{} { foreach i {1 2} yield }}
+ coroinject
+} -returnCodes error -cleanup {
+ catch {rename demo {}}
+} -result {wrong # args: should be "coroinject coroName cmd ?arg1 arg2 ...?"}
+test coroutine-10.7 {coroinject errors} -body {
+ coroutine demo apply {{} { foreach i {1 2} yield }}
+ coroinject demo
+} -returnCodes error -cleanup {
+ catch {rename demo {}}
+} -result {wrong # args: should be "coroinject coroName cmd ?arg1 arg2 ...?"}
+test coroutine-10.8 {coroinject errors in injected command} -body {
+ coroutine demo apply {{} { foreach i {1 2} yield }}
+ coroinject demo apply {args {error "ERR: $args"}}
+ list [catch demo msg] $msg [catch demo msg] $msg
+} -cleanup {
+ catch {rename demo {}}
+} -result {1 {ERR: yield {}} 1 {invalid command name "demo"}}
+test coroutine-10.9 {coroinject: advanced features} -setup {
+ set i [interp create]
+} -body {
+ $i eval {
+ coroutine demo apply {{} {
+ set l [info level]
+ set f [info frame]
+ lmap i {1 2} yield
+ }}
+ coroinject demo apply {{arg op val} {
+ global result
+ upvar 1 f f l l
+ lappend result [info coroutine] $arg $op $val
+ lappend result [info level] $l [info frame] $f
+ lappend result [yield $arg]
+ return [string toupper $val]
+ }} grill
+ list [demo ABC] [demo pqr] [demo def] $result
+ }
+} -cleanup {
+ interp delete $i
+} -result {grill {} {ABC def} {::demo grill yield ABC 2 1 3 2 pqr}}
+
+test coroutine-11.1 {coro type} {
coroutine demo eval {
yield
yield "PHASE 1"
@@ -803,19 +948,19 @@ test coroutine-9.1 {coro type} {
list [demo] [::tcl::unsupported::corotype demo] \
[demo] [::tcl::unsupported::corotype demo] [demo]
} {{PHASE 1} yield {PHASE 2} yieldto active}
-test coroutine-9.2 {coro type} -setup {
+test coroutine-11.2 {coro type} -setup {
catch {rename nosuchcommand ""}
} -returnCodes error -body {
::tcl::unsupported::corotype nosuchcommand
} -result {can only get coroutine type of a coroutine}
-test coroutine-9.3 {coro type} -returnCodes error -body {
+test coroutine-11.3 {coro type} -returnCodes error -body {
proc notacoroutine {} {}
::tcl::unsupported::corotype notacoroutine
} -returnCodes error -cleanup {
rename notacoroutine {}
} -result {can only get coroutine type of a coroutine}
-test coroutine-10.1 {coroutine general introspection} -setup {
+test coroutine-12.1 {coroutine general introspection} -setup {
set i [interp create]
} -body {
$i eval {
diff --git a/tests/interp.test b/tests/interp.test
index 29e3b2d..76ac01f 100644
--- a/tests/interp.test
+++ b/tests/interp.test
@@ -20,7 +20,7 @@ catch [list package require -exact Tcltest [info patchlevel]]
testConstraint testinterpdelete [llength [info commands testinterpdelete]]
-set hidden_cmds {cd encoding exec exit fconfigure file glob load open pwd socket source tcl:encoding:dirs tcl:encoding:system tcl:file:atime tcl:file:attributes tcl:file:copy tcl:file:delete tcl:file:dirname tcl:file:executable tcl:file:exists tcl:file:extension tcl:file:isdirectory tcl:file:isfile tcl:file:link tcl:file:lstat tcl:file:mkdir tcl:file:mtime tcl:file:nativename tcl:file:normalize tcl:file:owned tcl:file:readable tcl:file:readlink tcl:file:rename tcl:file:rootname tcl:file:size tcl:file:stat tcl:file:tail tcl:file:tempfile tcl:file:type tcl:file:volumes tcl:file:writable tcl:info:cmdtype tcl:info:nameofexecutable tcl:process:autopurge tcl:process:list tcl:process:purge tcl:process:status tcl:zipfs:lmkimg tcl:zipfs:lmkzip tcl:zipfs:mkimg tcl:zipfs:mkkey tcl:zipfs:mkzip tcl:zipfs:mount tcl:zipfs:mount_data tcl:zipfs:unmount unload}
+set hidden_cmds {cd encoding exec exit fconfigure file glob load open pwd socket source tcl:encoding:dirs tcl:encoding:system tcl:file:atime tcl:file:attributes tcl:file:copy tcl:file:delete tcl:file:dirname tcl:file:executable tcl:file:exists tcl:file:extension tcl:file:isdirectory tcl:file:isfile tcl:file:link tcl:file:lstat tcl:file:mkdir tcl:file:mtime tcl:file:nativename tcl:file:normalize tcl:file:owned tcl:file:readable tcl:file:readlink tcl:file:rename tcl:file:rootname tcl:file:size tcl:file:stat tcl:file:tail tcl:file:tempdir tcl:file:tempfile tcl:file:type tcl:file:volumes tcl:file:writable tcl:info:cmdtype tcl:info:nameofexecutable tcl:process:autopurge tcl:process:list tcl:process:purge tcl:process:status tcl:zipfs:lmkimg tcl:zipfs:lmkzip tcl:zipfs:mkimg tcl:zipfs:mkkey tcl:zipfs:mkzip tcl:zipfs:mount tcl:zipfs:mount_data tcl:zipfs:unmount unload}
foreach i [interp slaves] {
interp delete $i
diff --git a/unix/tclUnixFCmd.c b/unix/tclUnixFCmd.c
index 6dfc772..f36aecd 100644
--- a/unix/tclUnixFCmd.c
+++ b/unix/tclUnixFCmd.c
@@ -2274,6 +2274,85 @@ DefaultTempDir(void)
return TCL_TEMPORARY_FILE_DIRECTORY;
}
+/*
+ *----------------------------------------------------------------------
+ *
+ * TclpCreateTemporaryDirectory --
+ *
+ * Creates a temporary directory, possibly based on the supplied bits and
+ * pieces of template supplied in the arguments.
+ *
+ * Results:
+ * An object (refcount 0) containing the name of the newly-created
+ * directory, or NULL on failure.
+ *
+ * Side effects:
+ * Accesses the native filesystem. Makes a directory.
+ *
+ *----------------------------------------------------------------------
+ */
+
+Tcl_Obj *
+TclpCreateTemporaryDirectory(
+ Tcl_Obj *dirObj,
+ Tcl_Obj *basenameObj)
+{
+ Tcl_DString template, tmp;
+ const char *string;
+
+#define DEFAULT_TEMP_DIR_PREFIX "tcl"
+
+ /*
+ * Build the template in writable memory from the user-supplied pieces and
+ * some defaults.
+ */
+
+ if (dirObj) {
+ string = TclGetString(dirObj);
+ Tcl_UtfToExternalDString(NULL, string, dirObj->length, &template);
+ } else {
+ Tcl_DStringInit(&template);
+ Tcl_DStringAppend(&template, DefaultTempDir(), -1); /* INTL: native */
+ }
+
+ if (Tcl_DStringValue(&template)[Tcl_DStringLength(&template) - 1] != '/') {
+ TclDStringAppendLiteral(&template, "/");
+ }
+
+ if (basenameObj) {
+ string = TclGetString(basenameObj);
+ if (basenameObj->length) {
+ Tcl_UtfToExternalDString(NULL, string, basenameObj->length, &tmp);
+ TclDStringAppendDString(&template, &tmp);
+ Tcl_DStringFree(&tmp);
+ } else {
+ TclDStringAppendLiteral(&template, DEFAULT_TEMP_DIR_PREFIX);
+ }
+ } else {
+ TclDStringAppendLiteral(&template, DEFAULT_TEMP_DIR_PREFIX);
+ }
+
+ TclDStringAppendLiteral(&template, "_XXXXXX");
+
+ /*
+ * Make the temporary directory.
+ */
+
+ if (mkdtemp(Tcl_DStringValue(&template)) == NULL) {
+ Tcl_DStringFree(&template);
+ return NULL;
+ }
+
+ /*
+ * The template has been updated. Tell the caller what it was.
+ */
+
+ Tcl_ExternalToUtfDString(NULL, Tcl_DStringValue(&template),
+ Tcl_DStringLength(&template), &tmp);
+ Tcl_DStringFree(&template);
+ return TclDStringToObj(&tmp);
+}
+
#if defined(__CYGWIN__)
static void
diff --git a/win/tclWinFCmd.c b/win/tclWinFCmd.c
index ab783d9..a69ec3b 100644
--- a/win/tclWinFCmd.c
+++ b/win/tclWinFCmd.c
@@ -1957,6 +1957,121 @@ TclpObjListVolumes(void)
}
/*
+ *----------------------------------------------------------------------
+ *
+ * TclpCreateTemporaryDirectory --
+ *
+ * Creates a temporary directory, possibly based on the supplied bits and
+ * pieces of template supplied in the arguments.
+ *
+ * Results:
+ * An object (refcount 0) containing the name of the newly-created
+ * directory, or NULL on failure.
+ *
+ * Side effects:
+ * Accesses the native filesystem. Makes a directory.
+ *
+ *----------------------------------------------------------------------
+ */
+
+Tcl_Obj *
+TclpCreateTemporaryDirectory(
+ Tcl_Obj *dirObj,
+ Tcl_Obj *basenameObj)
+{
+ Tcl_DString base, name; /* Contains WCHARs */
+ int baseLen;
+ DWORD error;
+ WCHAR tempBuf[MAX_PATH + 1];
+ DWORD len = GetTempPathW(MAX_PATH, tempBuf);
+
+ /*
+ * Build the path in writable memory from the user-supplied pieces and
+ * some defaults. First, the parent temporary directory.
+ */
+
+ if (dirObj) {
+ Tcl_GetString(dirObj);
+ if (dirObj->length < 1) {
+ goto useSystemTemp;
+ }
+ Tcl_WinUtfToTChar(Tcl_GetString(dirObj), -1, &base);
+ if (dirObj->bytes[dirObj->length - 1] != '\\') {
+ TclUtfToWCharDString("\\", -1, &base);
+ }
+ } else {
+ useSystemTemp:
+ Tcl_DStringInit(&base);
+ Tcl_DStringAppend(&base, (char *) tempBuf, len * sizeof(WCHAR));
+ }
+
+ /*
+ * Next, the base of the directory name.
+ */
+
+#define DEFAULT_TEMP_DIR_PREFIX "tcl"
+#define SUFFIX_LENGTH 8
+
+ if (basenameObj) {
+ Tcl_WinUtfToTChar(Tcl_GetString(basenameObj), -1, &name);
+ TclDStringAppendDString(&base, &name);
+ Tcl_DStringFree(&name);
+ } else {
+ TclUtfToWCharDString(DEFAULT_TEMP_DIR_PREFIX, -1, &base);
+ }
+ TclUtfToWCharDString("_", -1, &base);
+
+ /*
+ * Now we keep on trying random suffixes until we get one that works
+ * (i.e., that doesn't trigger the ERROR_ALREADY_EXISTS error). Note that
+ * SUFFIX_LENGTH is longer than on Unix because we expect to be not on a
+ * case-sensitive filesystem.
+ */
+
+ baseLen = Tcl_DStringLength(&base);
+ do {
+ char tempbuf[SUFFIX_LENGTH + 1];
+ int i;
+ static const char randChars[] =
+ "QWERTYUIOPASDFGHJKLZXCVBNM1234567890";
+ static const int numRandChars = sizeof(randChars) - 1;
+
+ /*
+ * Put a random suffix on the end.
+ */
+
+ error = ERROR_SUCCESS;
+ tempbuf[SUFFIX_LENGTH] = '\0';
+ for (i = 0 ; i < SUFFIX_LENGTH; i++) {
+ tempbuf[i] = randChars[(int) (rand() % numRandChars)];
+ }
+ Tcl_DStringSetLength(&base, baseLen);
+ TclUtfToWCharDString(tempbuf, -1, &base);
+ } while (!CreateDirectoryW((LPCWSTR) Tcl_DStringValue(&base), NULL)
+ && (error = GetLastError()) == ERROR_ALREADY_EXISTS);
+
+ /*
+ * Check for other errors. The big ones are ERROR_PATH_NOT_FOUND and
+ * ERROR_ACCESS_DENIED.
+ */
+
+ if (error != ERROR_SUCCESS) {
+ TclWinConvertError(error);
+ Tcl_DStringFree(&base);
+ return NULL;
+ }
+
+ /*
+ * We actually made the directory, so we're done! Report what we made back
+ * as a (clean) Tcl_Obj.
+ */
+
+ Tcl_WinTCharToUtf((LPCWSTR) Tcl_DStringValue(&base), -1, &name);
+ Tcl_DStringFree(&base);
+ return TclDStringToObj(&name);
+}
+
+/*
* Local Variables:
* mode: c
* c-basic-offset: 4