[ros-diffs] [jmorlan] 40024: Replace a couple hacks supporting specific cases of enhanced variable substitution (%~var) with a more general implementation. All tests in rostests/win32/cmd/script_testsuite can now be passed using ReactOS cmd in Windows, though still not yet in ReactOS itself.

jmorlan at svn.reactos.org jmorlan at svn.reactos.org
Sun Mar 15 05:54:42 CET 2009


Author: jmorlan
Date: Sun Mar 15 07:54:41 2009
New Revision: 40024

URL: http://svn.reactos.org/svn/reactos?rev=40024&view=rev
Log:
Replace a couple hacks supporting specific cases of enhanced variable substitution (%~var) with a more general implementation. All tests in rostests/win32/cmd/script_testsuite can now be passed using ReactOS cmd in Windows, though still not yet in ReactOS itself.

Modified:
    trunk/reactos/base/shell/cmd/batch.c
    trunk/reactos/base/shell/cmd/batch.h
    trunk/reactos/base/shell/cmd/cmd.c

Modified: trunk/reactos/base/shell/cmd/batch.c
URL: http://svn.reactos.org/svn/reactos/trunk/reactos/base/shell/cmd/batch.c?rev=40024&r1=40023&r2=40024&view=diff
==============================================================================
--- trunk/reactos/base/shell/cmd/batch.c [iso-8859-1] (original)
+++ trunk/reactos/base/shell/cmd/batch.c [iso-8859-1] Sun Mar 15 07:54:41 2009
@@ -81,16 +81,18 @@
  *
  */
 
-LPTSTR FindArg (INT n)
+LPTSTR FindArg(TCHAR Char, BOOL *IsParam0)
 {
 	LPTSTR pp;
+	INT n = Char - _T('0');
 
 	TRACE ("FindArg: (%d)\n", n);
 
-	if (bc == NULL)
+	if (bc == NULL || n < 0 || n > 9)
 		return NULL;
 
 	n += bc->shiftlevel;
+	*IsParam0 = (n == 0);
 	pp = bc->params;
 
 	/* Step up the strings till we reach the end */

Modified: trunk/reactos/base/shell/cmd/batch.h
URL: http://svn.reactos.org/svn/reactos/trunk/reactos/base/shell/cmd/batch.h?rev=40024&r1=40023&r2=40024&view=diff
==============================================================================
--- trunk/reactos/base/shell/cmd/batch.h [iso-8859-1] (original)
+++ trunk/reactos/base/shell/cmd/batch.h [iso-8859-1] Sun Mar 15 07:54:41 2009
@@ -44,7 +44,7 @@
 extern TCHAR textline[BATCH_BUFFSIZE]; /* Buffer for reading Batch file lines */
 
 
-LPTSTR FindArg (INT);
+LPTSTR FindArg (TCHAR, BOOL *);
 LPTSTR BatchParams (LPTSTR, LPTSTR);
 VOID   ExitBatch (LPTSTR);
 BOOL   Batch (LPTSTR, LPTSTR, LPTSTR, PARSED_COMMAND *);

Modified: trunk/reactos/base/shell/cmd/cmd.c
URL: http://svn.reactos.org/svn/reactos/trunk/reactos/base/shell/cmd/cmd.c?rev=40024&r1=40023&r2=40024&view=diff
==============================================================================
--- trunk/reactos/base/shell/cmd/cmd.c [iso-8859-1] (original)
+++ trunk/reactos/base/shell/cmd/cmd.c [iso-8859-1] Sun Mar 15 07:54:41 2009
@@ -989,52 +989,242 @@
 	return NULL;
 }
 
-
+/* Handle the %~var syntax */
+static LPTSTR
+GetEnhancedVar(TCHAR **pFormat, LPTSTR (*GetVar)(TCHAR, BOOL *))
+{
+	static const TCHAR ModifierTable[] = _T("dpnxfsatz");
+	enum {
+		M_DRIVE = 1,   /* D: drive letter */
+		M_PATH  = 2,   /* P: path */
+		M_NAME  = 4,   /* N: filename */
+		M_EXT   = 8,   /* X: extension */
+		M_FULL  = 16,  /* F: full path (drive+path+name+ext) */
+		M_SHORT = 32,  /* S: full path (drive+path+name+ext), use short names */
+		M_ATTR  = 64,  /* A: attributes */
+		M_TIME  = 128, /* T: modification time */
+		M_SIZE  = 256, /* Z: file size */
+	} Modifiers = 0;
+
+	TCHAR *Format, *FormatEnd;
+	LPTSTR Variable;
+	TCHAR *VarEnd;
+	BOOL VariableIsParam0;
+	TCHAR FullPath[MAX_PATH];
+	TCHAR FixedPath[MAX_PATH], *Filename, *Extension;
+	HANDLE hFind;
+	WIN32_FIND_DATA w32fd;
+	TCHAR *In, *Out;
+
+	static TCHAR Result[CMDLINE_LENGTH];
+
+	/* There is ambiguity between modifier characters and FOR variables;
+	 * the rule that cmd uses is to pick the longest possible match.
+	 * For example, if there is a %n variable, then out of %~anxnd,
+	 * %~anxn will be substituted rather than just %~an. */
+
+	/* First, go through as many modifier characters as possible */
+	FormatEnd = Format = *pFormat;
+	while (*FormatEnd && _tcschr(ModifierTable, _totlower(*FormatEnd)))
+		FormatEnd++;
+
+	/* TODO: check for $PATH: syntax */
+
+	/* Now backtrack if necessary to get a variable name match */
+	while (!(Variable = GetVar(*FormatEnd, &VariableIsParam0)))
+	{
+		if (FormatEnd == Format)
+			return NULL;
+		FormatEnd--;
+	}
+
+	for (; Format < FormatEnd; Format++)
+		Modifiers |= 1 << (_tcschr(ModifierTable, _totlower(*Format)) - ModifierTable);
+
+	*pFormat = FormatEnd + 1;
+
+	/* Exclude the leading and trailing quotes */
+	VarEnd = &Variable[_tcslen(Variable)];
+	if (*Variable == _T('"'))
+	{
+		Variable++;
+		if (VarEnd > Variable && VarEnd[-1] == _T('"'))
+			VarEnd--;
+	}
+
+	if ((char *)VarEnd - (char *)Variable >= sizeof Result)
+		return _T("");
+	memcpy(Result, Variable, (char *)VarEnd - (char *)Variable);
+	Result[VarEnd - Variable] = _T('\0');
+
+	/* For plain %~var with no modifiers, just return the variable without quotes */
+	if (Modifiers == 0)
+		return Result;
+
+	if (VariableIsParam0)
+	{
+		/* Special case: If the variable is %0 and modifier characters are present,
+		 * use the batch file's path (which includes the .bat/.cmd extension)
+		 * rather than the actual %0 variable (which might not). */
+		_tcscpy(FullPath, bc->BatchFilePath);
+	}
+	else
+	{
+		/* Convert the variable, now without quotes, to a full path */
+		if (!GetFullPathName(Result, MAX_PATH, FullPath, NULL))
+			return _T("");
+	}
+
+	/* Next step is to change the path to fix letter case (e.g.
+	 * C:\ReAcToS -> C:\ReactOS) and, if requested with the S modifier,
+	 * replace long filenames with short. */
+
+	In = FullPath;
+	Out = FixedPath;
+
+	/* Copy drive letter */
+	*Out++ = *In++;
+	*Out++ = *In++;
+	*Out++ = *In++;
+	/* Loop over each \-separated component in the path */
+	do {
+		TCHAR *Next = _tcschr(In, _T('\\'));
+		if (Next)
+			*Next++ = _T('\0');
+		/* Use FindFirstFile to get the correct name */
+		if (Out + _tcslen(In) + 1 >= &FixedPath[MAX_PATH])
+			return _T("");
+		_tcscpy(Out, In);
+		hFind = FindFirstFile(FixedPath, &w32fd);
+		/* If it doesn't exist, just leave the name as it was given */
+		if (hFind != INVALID_HANDLE_VALUE)
+		{
+			LPTSTR FixedComponent = w32fd.cFileName;
+			if (*w32fd.cAlternateFileName &&
+			    ((Modifiers & M_SHORT) || !_tcsicmp(In, w32fd.cAlternateFileName)))
+			{
+				FixedComponent = w32fd.cAlternateFileName;
+			}
+			FindClose(hFind);
+
+			if (Out + _tcslen(FixedComponent) + 1 >= &FixedPath[MAX_PATH])
+				return _T("");
+			_tcscpy(Out, FixedComponent);
+		}
+		Filename = Out;
+		Out += _tcslen(Out);
+		*Out++ = _T('\\');
+
+		In = Next;
+	} while (In != NULL);
+	Out[-1] = _T('\0');
+
+	/* Build the result string. Start with attributes, modification time, and
+	 * file size. If the file didn't exist, these fields will all be empty. */
+	Out = Result;
+	if (hFind != INVALID_HANDLE_VALUE)
+	{
+		if (Modifiers & M_ATTR)
+		{
+			static const struct {
+				TCHAR Character;
+				WORD  Value;
+			} *Attrib, Table[] = {
+				{ _T('d'), FILE_ATTRIBUTE_DIRECTORY },
+				{ _T('r'), FILE_ATTRIBUTE_READONLY },
+				{ _T('a'), FILE_ATTRIBUTE_ARCHIVE },
+				{ _T('h'), FILE_ATTRIBUTE_HIDDEN },
+				{ _T('s'), FILE_ATTRIBUTE_SYSTEM },
+				{ _T('c'), FILE_ATTRIBUTE_COMPRESSED },
+				{ _T('o'), FILE_ATTRIBUTE_OFFLINE },
+				{ _T('t'), FILE_ATTRIBUTE_TEMPORARY },
+				{ _T('l'), FILE_ATTRIBUTE_REPARSE_POINT },
+			};
+			for (Attrib = Table; Attrib != &Table[9]; Attrib++)
+			{
+				*Out++ = w32fd.dwFileAttributes & Attrib->Value
+				         ? Attrib->Character
+				         : _T('-');
+			}
+			*Out++ = _T(' ');
+		}
+		if (Modifiers & M_TIME)
+		{
+			FILETIME ft;
+			SYSTEMTIME st;
+			FileTimeToLocalFileTime(&w32fd.ftLastWriteTime, &ft);
+			FileTimeToSystemTime(&ft, &st);
+
+			/* TODO: This probably should be locale-dependent */
+			Out += _stprintf(Out,
+				_T("%02d/%02d/%04d %02d:%02d %cM "),
+				st.wMonth, st.wDay, st.wYear,
+				(st.wHour + 11) % 12 + 1, st.wMinute,
+				(st.wHour >= 12) ? _T('P') : _T('A'));
+		}
+		if (Modifiers & M_SIZE)
+		{
+			ULARGE_INTEGER Size;
+			Size.LowPart = w32fd.nFileSizeLow;
+			Size.HighPart = w32fd.nFileSizeHigh;
+			Out += _stprintf(Out, _T("%I64u "), Size.QuadPart);
+		}
+	}
+
+	/* Now add the requested parts of the name.
+	 * With the F or S modifiers, add all parts to form the full path. */
+	Extension = _tcsrchr(Filename, _T('.'));
+	if (Modifiers & (M_DRIVE | M_FULL | M_SHORT))
+	{
+		*Out++ = FixedPath[0];
+		*Out++ = FixedPath[1];
+	}
+	if (Modifiers & (M_PATH | M_FULL | M_SHORT))
+	{
+		memcpy(Out, &FixedPath[2], (char *)Filename - (char *)&FixedPath[2]);
+		Out += Filename - &FixedPath[2];
+	}
+	if (Modifiers & (M_NAME | M_FULL | M_SHORT))
+	{
+		while (*Filename && Filename != Extension)
+			*Out++ = *Filename++;
+	}
+	if (Modifiers & (M_EXT | M_FULL | M_SHORT))
+	{
+		if (Extension)
+			Out = _stpcpy(Out, Extension);
+	}
+
+	/* Trim trailing space which otherwise would appear as a
+	 * result of using the A/T/Z modifiers but no others. */
+	while (Out != &Result[0] && Out[-1] == _T(' '))
+		Out--;
+	*Out = _T('\0');
+
+	return Result;
+}
 
 LPCTSTR
-GetBatchVar ( LPCTSTR varName, UINT* varNameLen )
-{
-	static LPTSTR ret = NULL;
-	static UINT retlen = 0;
-	DWORD len;
+GetBatchVar(TCHAR *varName, UINT *varNameLen)
+{
+	LPCTSTR ret;
+	TCHAR *varNameEnd;
+	BOOL dummy;
 
 	*varNameLen = 1;
 
 	switch ( *varName )
 	{
 	case _T('~'):
-		varName++;
-		if (_tcsncicmp(varName, _T("dp0"), 3) == 0)
-		{
-			*varNameLen = 4;
-			len = _tcsrchr(bc->BatchFilePath, _T('\\')) + 1 - bc->BatchFilePath;
-			if (!GrowIfNecessary(len + 1, &ret, &retlen))
-				return NULL;
-			memcpy(ret, bc->BatchFilePath, len * sizeof(TCHAR));
-			ret[len] = _T('\0');
-			return ret;
-		}
-
-		*varNameLen = 2;
-		if (*varName >= _T('0') && *varName <= _T('9')) {
-			LPTSTR arg = FindArg(*varName - _T('0'));
-
-			if (*arg != _T('"'))
-				return arg;
-
-			/* Exclude the leading and trailing quotes */
-			arg++;
-			len = _tcslen(arg);
-			if (arg[len - 1] == _T('"'))
-				len--;
-
-			if (!GrowIfNecessary(len + 1, &ret, &retlen))
-				return NULL;
-			memcpy(ret, arg, len * sizeof(TCHAR));
-			ret[len] = _T('\0');
-			return ret;
-		}
-		break;
+		varNameEnd = varName + 1;
+		ret = GetEnhancedVar(&varNameEnd, FindArg);
+		if (!ret)
+		{
+			error_syntax(varName);
+			return NULL;
+		}
+		*varNameLen = varNameEnd - varName;
+		return ret;
 	case _T('0'):
 	case _T('1'):
 	case _T('2'):
@@ -1045,7 +1235,7 @@
 	case _T('7'):
 	case _T('8'):
 	case _T('9'):
-		return FindArg(*varName - _T('0'));
+		return FindArg(*varName, &dummy);
 
     case _T('*'):
         //
@@ -1212,26 +1402,41 @@
 #undef APPEND1
 }
 
+/* Search the list of FOR contexts for a variable */
+static LPTSTR FindForVar(TCHAR Var, BOOL *IsParam0)
+{
+	FOR_CONTEXT *Ctx;
+	*IsParam0 = FALSE;
+	for (Ctx = fc; Ctx != NULL; Ctx = Ctx->prev)
+		if ((UINT)(Var - Ctx->firstvar) < Ctx->varcount)
+			return Ctx->values[Var - Ctx->firstvar];
+	return NULL;
+}
+
 BOOL
 SubstituteForVars(TCHAR *Src, TCHAR *Dest)
 {
 	TCHAR *DestEnd = &Dest[CMDLINE_LENGTH - 1];
 	while (*Src)
 	{
-		if (Src[0] == _T('%') && Src[1] != _T('\0'))
-		{
-			/* This might be a variable. Search the list of contexts for it */
-			FOR_CONTEXT *Ctx = fc;
-			while (Ctx && (UINT)(Src[1] - Ctx->firstvar) >= Ctx->varcount)
-				Ctx = Ctx->prev;
-			if (Ctx)
-			{
-				/* Found it */
-				LPTSTR Value = Ctx->values[Src[1] - Ctx->firstvar];
+		if (Src[0] == _T('%'))
+		{
+			BOOL Dummy;
+			LPTSTR End = &Src[2];
+			LPTSTR Value = NULL;
+
+			if (Src[1] == _T('~'))
+				Value = GetEnhancedVar(&End, FindForVar);
+
+			if (!Value)
+				Value = FindForVar(Src[1], &Dummy);
+
+			if (Value)
+			{
 				if (Dest + _tcslen(Value) > DestEnd)
 					return FALSE;
 				Dest = _stpcpy(Dest, Value);
-				Src += 2;
+				Src = End;
 				continue;
 			}
 		}



More information about the Ros-diffs mailing list