mirror of
https://github.com/reactos/reactos.git
synced 2025-02-23 00:45:24 +00:00
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.
svn path=/trunk/; revision=40024
This commit is contained in:
parent
89e44e0c4a
commit
a1eb1f6ba4
3 changed files with 254 additions and 47 deletions
|
@ -81,16 +81,18 @@ TCHAR textline[BATCH_BUFFSIZE];
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
LPTSTR FindArg (INT n)
|
LPTSTR FindArg(TCHAR Char, BOOL *IsParam0)
|
||||||
{
|
{
|
||||||
LPTSTR pp;
|
LPTSTR pp;
|
||||||
|
INT n = Char - _T('0');
|
||||||
|
|
||||||
TRACE ("FindArg: (%d)\n", n);
|
TRACE ("FindArg: (%d)\n", n);
|
||||||
|
|
||||||
if (bc == NULL)
|
if (bc == NULL || n < 0 || n > 9)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
n += bc->shiftlevel;
|
n += bc->shiftlevel;
|
||||||
|
*IsParam0 = (n == 0);
|
||||||
pp = bc->params;
|
pp = bc->params;
|
||||||
|
|
||||||
/* Step up the strings till we reach the end */
|
/* Step up the strings till we reach the end */
|
||||||
|
|
|
@ -44,7 +44,7 @@ extern BOOL bEcho; /* The echo flag */
|
||||||
extern TCHAR textline[BATCH_BUFFSIZE]; /* Buffer for reading Batch file lines */
|
extern TCHAR textline[BATCH_BUFFSIZE]; /* Buffer for reading Batch file lines */
|
||||||
|
|
||||||
|
|
||||||
LPTSTR FindArg (INT);
|
LPTSTR FindArg (TCHAR, BOOL *);
|
||||||
LPTSTR BatchParams (LPTSTR, LPTSTR);
|
LPTSTR BatchParams (LPTSTR, LPTSTR);
|
||||||
VOID ExitBatch (LPTSTR);
|
VOID ExitBatch (LPTSTR);
|
||||||
BOOL Batch (LPTSTR, LPTSTR, LPTSTR, PARSED_COMMAND *);
|
BOOL Batch (LPTSTR, LPTSTR, LPTSTR, PARSED_COMMAND *);
|
||||||
|
|
|
@ -989,52 +989,242 @@ GetEnvVarOrSpecial ( LPCTSTR varName )
|
||||||
return NULL;
|
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
|
LPCTSTR
|
||||||
GetBatchVar ( LPCTSTR varName, UINT* varNameLen )
|
GetBatchVar(TCHAR *varName, UINT *varNameLen)
|
||||||
{
|
{
|
||||||
static LPTSTR ret = NULL;
|
LPCTSTR ret;
|
||||||
static UINT retlen = 0;
|
TCHAR *varNameEnd;
|
||||||
DWORD len;
|
BOOL dummy;
|
||||||
|
|
||||||
*varNameLen = 1;
|
*varNameLen = 1;
|
||||||
|
|
||||||
switch ( *varName )
|
switch ( *varName )
|
||||||
{
|
{
|
||||||
case _T('~'):
|
case _T('~'):
|
||||||
varName++;
|
varNameEnd = varName + 1;
|
||||||
if (_tcsncicmp(varName, _T("dp0"), 3) == 0)
|
ret = GetEnhancedVar(&varNameEnd, FindArg);
|
||||||
|
if (!ret)
|
||||||
{
|
{
|
||||||
*varNameLen = 4;
|
error_syntax(varName);
|
||||||
len = _tcsrchr(bc->BatchFilePath, _T('\\')) + 1 - bc->BatchFilePath;
|
|
||||||
if (!GrowIfNecessary(len + 1, &ret, &retlen))
|
|
||||||
return NULL;
|
return NULL;
|
||||||
memcpy(ret, bc->BatchFilePath, len * sizeof(TCHAR));
|
|
||||||
ret[len] = _T('\0');
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
*varNameLen = varNameEnd - varName;
|
||||||
*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;
|
return ret;
|
||||||
}
|
|
||||||
break;
|
|
||||||
case _T('0'):
|
case _T('0'):
|
||||||
case _T('1'):
|
case _T('1'):
|
||||||
case _T('2'):
|
case _T('2'):
|
||||||
|
@ -1045,7 +1235,7 @@ GetBatchVar ( LPCTSTR varName, UINT* varNameLen )
|
||||||
case _T('7'):
|
case _T('7'):
|
||||||
case _T('8'):
|
case _T('8'):
|
||||||
case _T('9'):
|
case _T('9'):
|
||||||
return FindArg(*varName - _T('0'));
|
return FindArg(*varName, &dummy);
|
||||||
|
|
||||||
case _T('*'):
|
case _T('*'):
|
||||||
//
|
//
|
||||||
|
@ -1212,26 +1402,41 @@ too_long:
|
||||||
#undef APPEND1
|
#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
|
BOOL
|
||||||
SubstituteForVars(TCHAR *Src, TCHAR *Dest)
|
SubstituteForVars(TCHAR *Src, TCHAR *Dest)
|
||||||
{
|
{
|
||||||
TCHAR *DestEnd = &Dest[CMDLINE_LENGTH - 1];
|
TCHAR *DestEnd = &Dest[CMDLINE_LENGTH - 1];
|
||||||
while (*Src)
|
while (*Src)
|
||||||
{
|
{
|
||||||
if (Src[0] == _T('%') && Src[1] != _T('\0'))
|
if (Src[0] == _T('%'))
|
||||||
{
|
{
|
||||||
/* This might be a variable. Search the list of contexts for it */
|
BOOL Dummy;
|
||||||
FOR_CONTEXT *Ctx = fc;
|
LPTSTR End = &Src[2];
|
||||||
while (Ctx && (UINT)(Src[1] - Ctx->firstvar) >= Ctx->varcount)
|
LPTSTR Value = NULL;
|
||||||
Ctx = Ctx->prev;
|
|
||||||
if (Ctx)
|
if (Src[1] == _T('~'))
|
||||||
|
Value = GetEnhancedVar(&End, FindForVar);
|
||||||
|
|
||||||
|
if (!Value)
|
||||||
|
Value = FindForVar(Src[1], &Dummy);
|
||||||
|
|
||||||
|
if (Value)
|
||||||
{
|
{
|
||||||
/* Found it */
|
|
||||||
LPTSTR Value = Ctx->values[Src[1] - Ctx->firstvar];
|
|
||||||
if (Dest + _tcslen(Value) > DestEnd)
|
if (Dest + _tcslen(Value) > DestEnd)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
Dest = _stpcpy(Dest, Value);
|
Dest = _stpcpy(Dest, Value);
|
||||||
Src += 2;
|
Src = End;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue