menusys.cpp

Go to the documentation of this file.
00001 
00006 #ifdef _WIN32
00007 # include <windows.h>
00008 # include <direct.h>
00009 # include <process.h>
00010 # ifndef MSYS_NOTHREADS
00011 #  define THREAD_HANDLE          HANDLE
00012 #  define THREAD_HANDLE_INIT(x)  x = NULL
00013 # endif
00014 #else
00015 # include <unistd.h>
00016 # include <sys/stat.h>
00017 # ifndef MSYS_NOTHREADS
00018 #  include <pthread.h>
00019 #  define THREAD_HANDLE          pthread_t
00020 #  define THREAD_HANDLE_INIT(x)
00021 # endif
00022 # include <limits.h>
00023 # define MAX_PATH               PATH_MAX
00024 #endif
00025 
00026 #include <stdarg.h>
00027 #include <stdio.h>
00028 #include <stdlib.h>
00029 #include <assert.h>
00030 #include <errno.h>
00031 #include <sys/timeb.h>
00032 #include <sys/types.h>
00033 #include <time.h>
00034 
00035 #include <queue>
00036 #include <set>
00037 #include <map>
00038 #include <algorithm>
00039 
00040 #include "menusys.h"
00041 #include "menusysu.h"
00042 #ifdef _UNICODE
00043 # include "ConvertUTF.h"
00044 #endif
00045 
00046 // ----------------------------------------------------------------------------
00047 // STRING CONSTANTS
00048 // ----------------------------------------------------------------------------
00049 
00055 #if defined(USING_ICU) && !defined(_WIN32)
00056 #define StringConstant(name, str) \
00057     static const struct _str_ ## name { \
00058         static const size_t len = sizeof(str)-1; \
00059         static const UChar data[sizeof str]; \
00060         _str_ ## name() { \
00061             u_charsToUChars(str, (UChar*) data, sizeof str); \
00062         } \
00063         operator const UChar *() const { return data; } \
00064     } name
00065 #else
00066 # ifdef _UNICODE
00067 #  define StringConstant(name, str) static const TCHAR name[] = L ## str;
00068 # else
00069 #  define StringConstant(name, str) static const TCHAR name[] = str;
00070 # endif
00071 #endif
00072 
00073 // naming convention is to prefix the actual string with double underscore
00074 // when it is a short word. Otherwise use a short name with double underscore.
00075 static const TCHAR  OPTION_PREFIX[]   = _T("_setopt_");
00076 static const size_t OPTION_PREFIX_LEN = (sizeof(OPTION_PREFIX) / sizeof(TCHAR)) - 1;
00077 
00078 // the text of this isn't used, only the address which will be unique
00079 extern const TCHAR * MSYS_HEADER = _T("");
00080 
00081 
00082 // ----------------------------------------------------------------------------
00083 // TYPE DEFINITIONS
00084 // ----------------------------------------------------------------------------
00085 
00089 typedef std::map<std::tstring,long> ProcMap;
00090 
00093 typedef std::vector<long> ProcStack;
00094 
00095 struct InputFile
00096 {
00097     FILE *          mFile;
00098     std::tstring    mPath;
00099     bool            mIsUtf8;
00100     ProcMap         mProc;
00101     ProcStack       mProcStack;
00102 #ifdef _WIN32
00103     HANDLE          mFileLock;
00104 #endif
00105 
00106     InputFile(FILE * aFile = NULL, const TCHAR * aPath = _T(""), bool aIsUtf8 = false);
00107     InputFile(const InputFile & rhs);
00108     InputFile & operator=(const InputFile & rhs);
00109 };
00110 
00111 template <typename T, size_t INIT_SIZE = 1024>
00112 struct BufferImpl
00113 {
00114     T *     mBuf;
00115     size_t  mBufSiz;
00116 
00117     BufferImpl();
00118     ~BufferImpl();
00119     void Grow();
00120     void Format(const TCHAR * aFormat, ...);
00121     void FormatV(const TCHAR * aFormat, va_list ap);
00122 };
00123 
00124 typedef BufferImpl<char>    BufferA;
00125 typedef BufferImpl<TCHAR>   Buffer;
00126 
00127 typedef std::vector<long> LapArray;
00128 
00129 class Timer {
00130     struct timeb    mStart;
00131     long            mElapsed;
00132     bool            mRunning;
00133     LapArray        mLaps;
00134 
00135 public:
00136     Timer();
00137     void Start(bool aReset = false);
00138     void Stop();
00139     void Reset();
00140     long GetCurrentElapsed() const;
00141     bool IsRunning() const;
00142     void Lap();
00143     const LapArray & Laps() const { return mLaps; }
00144 
00145 private:
00146     long CalculateElapsed(struct timeb * aFinish = NULL) const;
00147 };
00148 
00149 typedef std::map<std::tstring,Timer> TimerMap;
00150 
00151 enum DISPLAY {
00152     DISPLAY_NONE, DISPLAY_PROMPT, DISPLAY_OUTPUT, DISPLAY_ALL
00153 };
00154 
00155 struct MSYS_ThreadImpl : public MSYS_Thread
00156 {
00157     bool                    bStopThread;
00158     bool                    bIsRunning;
00159     std::tstring            mThreadName;
00160 #ifndef MSYS_NOTHREADS
00161     THREAD_HANDLE           mThreadHandle;
00162 #endif
00163     std::vector<InputFile>  mInputStack;
00164     InputFile               mInputCurr;
00165     FILE *                  mTeeFile;
00166     bool                    mUseNeedHelp;
00167     DISPLAY                 mOptionConsole;
00168     TimerMap                mTimerMap;
00169     Buffer                  mHandlerBuf;
00170     BufferA                 mCharBuf;
00171     bool                    mStringReplacements;
00172 
00173     MSYS_ThreadImpl(const TCHAR * aThreadName = _T(""), bool a_bRootThread = false);
00174     ~MSYS_ThreadImpl();
00175 
00176 public:
00177     void SetCurrCommand(const TCHAR * aCommand) { mCurrCommand = aCommand; }
00178     void LoopStartCounted(long aProcedure, int aCount);
00179     void LoopStartTimed(long aProcedure, time_t aExitTime);
00180     bool Loop();
00181 
00182 private:
00183     long    mLoopProcedure;
00184     time_t  mLoopLimit;
00185 
00186     void LoopStart();
00187     bool LoopTestConditions();
00188 
00189 private:
00190     MSYS_ThreadImpl(const MSYS_ThreadImpl & rhs); // disable
00191     MSYS_ThreadImpl & operator=(const MSYS_ThreadImpl & rhs); // disable
00192 };
00193 
00194 typedef std::map<std::tstring, MSYS_ThreadImpl*> ThreadMap;
00195 
00196 #ifdef MSYS_NOTHREADS
00197 class ThreadLock {
00198 public:
00199     ThreadLock()    { }
00200     ~ThreadLock()   { }
00201     void Lock()     { }
00202     void Unlock()   { }
00203 };
00204 #else
00205 # ifdef _WIN32
00206 class ThreadLock {
00207     CRITICAL_SECTION m_lock;
00208 public:
00209     ThreadLock()    { InitializeCriticalSection(&m_lock); }
00210     ~ThreadLock()   { DeleteCriticalSection(&m_lock); }
00211     void Lock()     { EnterCriticalSection(&m_lock); }
00212     void Unlock()   { LeaveCriticalSection(&m_lock); }
00213 };
00214 # else // !_WIN32
00215 class ThreadLock {
00216     pthread_mutex_t m_lock;
00217 public:
00218     ThreadLock()    { pthread_mutex_init(&m_lock, NULL); }
00219     ~ThreadLock()   { pthread_mutex_destroy(&m_lock); }
00220     void Lock()     { pthread_mutex_lock(&m_lock); }
00221     void Unlock()   { pthread_mutex_unlock(&m_lock); }
00222 };
00223 # endif // _WIN32
00224 #endif // MSYS_NOTHREADS
00225 
00226 struct AutoLock {
00227     ThreadLock & mLock;
00228     AutoLock(ThreadLock & aLock) : mLock(aLock) { mLock.Lock(); }
00229     ~AutoLock() { mLock.Unlock(); }
00230 
00231 private:
00232     AutoLock(const AutoLock &); // disable
00233     AutoLock & operator=(const AutoLock &); // disable
00234 };
00235 
00236 enum PARSE { PARSE_DEFAULT, PARSE_ON, PARSE_OFF };
00237 
00238 
00239 // ----------------------------------------------------------------------------
00240 // TYPE IMPLEMENTATIONS
00241 // ----------------------------------------------------------------------------
00242 
00243 InputFile::InputFile(
00244     FILE *          aFile,
00245     const TCHAR *   aPath,
00246     bool            aIsUtf8
00247     )
00248     : mFile(aFile)
00249     , mPath(aPath)
00250     , mIsUtf8(aIsUtf8)
00251 { }
00252 
00253 InputFile::InputFile(const InputFile & rhs)
00254 {
00255     operator=(rhs);
00256 }
00257 
00258 InputFile &
00259 InputFile::operator=(
00260     const InputFile & rhs
00261     )
00262 {
00263     mFile       = rhs.mFile;
00264     mPath       = rhs.mPath;
00265     mIsUtf8     = rhs.mIsUtf8;
00266     mProc       = rhs.mProc;
00267     mProcStack  = rhs.mProcStack;
00268 #ifdef _WIN32
00269     mFileLock   = rhs.mFileLock;
00270 #endif
00271 
00272     return *this;
00273 }
00274 
00275 template <typename T, size_t INIT_SIZE>
00276 BufferImpl<T,INIT_SIZE>::BufferImpl()
00277     : mBuf(NULL)
00278     , mBufSiz(INIT_SIZE)
00279 {
00280     Grow();
00281 }
00282 
00283 template <typename T, size_t INIT_SIZE>
00284 BufferImpl<T,INIT_SIZE>::~BufferImpl()
00285 {
00286     if (mBuf) free(mBuf);
00287 }
00288 
00289 template <typename T, size_t INIT_SIZE>
00290 void
00291 BufferImpl<T,INIT_SIZE>::Grow()
00292 {
00293     mBufSiz *= 2;
00294     mBuf = (T*) realloc(mBuf, mBufSiz * sizeof(T));
00295     if (!mBuf) {
00296         _ftprintf(stderr, _T("Out of memory.\n"));
00297         exit(1);
00298     }
00299 }
00300 
00301 template <typename T, size_t INIT_SIZE>
00302 void
00303 BufferImpl<T,INIT_SIZE>::Format(
00304     const TCHAR * aFormat,
00305     ...
00306     )
00307 {
00308     va_list ap;
00309     va_start(ap, aFormat);
00310     FormatV(aFormat, ap);
00311     va_end(ap);
00312 }
00313 
00314 template <typename T, size_t INIT_SIZE>
00315 void
00316 BufferImpl<T,INIT_SIZE>::FormatV(
00317     const TCHAR *   aFormat,
00318     va_list         ap
00319     )
00320 {
00321     int nLength = 0;
00322     do {
00323         if (nLength == -1) Grow();
00324         nLength = _vsntprintf(mBuf, mBufSiz, aFormat, ap);
00325     } while (nLength == -1);
00326 }
00327 
00328 Timer::Timer()
00329 {
00330     mElapsed = 0;
00331     mStart.time = 0;
00332     mStart.millitm = 0;
00333     mRunning = false;
00334 }
00335 
00336 void
00337 Timer::Start(
00338     bool    aReset
00339     )
00340 {
00341     if (aReset) {
00342         Reset();
00343     }
00344     ftime(&mStart);
00345     mRunning = true;
00346 }
00347 
00348 void
00349 Timer::Stop()
00350 {
00351     if (mRunning) {
00352         mRunning = false;
00353         mElapsed += CalculateElapsed();
00354     }
00355 }
00356 
00357 void
00358 Timer::Reset()
00359 {
00360     mElapsed = 0;
00361     mRunning = false;
00362     mLaps.clear();
00363 }
00364 
00365 long
00366 Timer::GetCurrentElapsed() const
00367 {
00368     if (mRunning) {
00369         return mElapsed + CalculateElapsed();
00370     }
00371     return mElapsed;
00372 }
00373 
00374 bool
00375 Timer::IsRunning() const
00376 {
00377     return mRunning;
00378 }
00379 
00380 void
00381 Timer::Lap()
00382 {
00383     long nLapTime = mElapsed;
00384     mElapsed = 0;
00385     if (mRunning) {
00386         struct timeb timeEnd;
00387         ftime(&timeEnd);
00388         nLapTime += CalculateElapsed(&timeEnd);
00389         mStart = timeEnd;
00390     }
00391     mLaps.push_back(nLapTime);
00392 }
00393 
00394 long
00395 Timer::CalculateElapsed(
00396     struct timeb * aFinish
00397     ) const
00398 {
00399     struct timeb timeEnd;
00400     if (aFinish) {
00401         timeEnd = *aFinish;
00402     }
00403     else {
00404         ftime(&timeEnd);
00405     }
00406     time_t nPeriodSecs = timeEnd.time - mStart.time;
00407     int nPeriodMillis = (int) timeEnd.millitm - (int) mStart.millitm;
00408     return (long) (nPeriodSecs * 1000) + nPeriodMillis;
00409 }
00410 
00411 MSYS_ThreadImpl::MSYS_ThreadImpl(
00412     const TCHAR *   aThreadName,
00413     bool            a_bRootThread
00414     )
00415     : bStopThread(false)
00416     , bIsRunning(true)
00417     , mThreadName(aThreadName)
00418     , mTeeFile(NULL)
00419     , mUseNeedHelp(true)
00420     , mOptionConsole(DISPLAY_ALL)
00421     , mStringReplacements(true)
00422     , mLoopProcedure(0)
00423     , mLoopLimit(0)
00424 {
00425 #ifndef MSYS_NOTHREADS
00426     THREAD_HANDLE_INIT(mThreadHandle);
00427 #endif
00428     static int nNextThreadId = ROOT_THREAD;
00429     if (a_bRootThread) {
00430         nNextThreadId = ROOT_THREAD;
00431     }
00432     mThreadId = nNextThreadId++;
00433     mInputCurr = InputFile(stdin, _T("stdin"));
00434 }
00435 
00436 MSYS_ThreadImpl::~MSYS_ThreadImpl()
00437 {
00438     if (mTeeFile) {
00439         fclose(mTeeFile);
00440         mTeeFile = NULL;
00441     }
00442     while (!mInputStack.empty()) {
00443         if (mInputStack.back().mFile != stdin) {
00444             fclose(mInputStack.back().mFile);
00445 #ifdef _WIN32
00446             CloseHandle(mInputStack.back().mFileLock);
00447 #endif
00448         }
00449         mInputStack.pop_back();
00450     }
00451     MSYS_ThreadUserDestroy(this);
00452 }
00453 
00454 void
00455 MSYS_ThreadImpl::LoopStartCounted(
00456     long    aProcedure,
00457     int     aCount
00458     )
00459 {
00460     assert(mInputCurr.mFile != stdin);
00461 
00462     if (aCount < 0) aCount = 0;
00463     mLoopProcedure = aProcedure;
00464     mLoopLimit = -aCount;
00465     if (!LoopTestConditions()) return;
00466     LoopStart();
00467 }
00468 
00469 void
00470 MSYS_ThreadImpl::LoopStartTimed(
00471     long    aProcedure,
00472     time_t  aExitTime
00473     )
00474 {
00475     assert(mInputCurr.mFile != stdin);
00476 
00477     mLoopProcedure = aProcedure;
00478     mLoopLimit = aExitTime;
00479     if (!LoopTestConditions()) return;
00480     LoopStart();
00481 }
00482 
00483 bool
00484 MSYS_ThreadImpl::Loop()
00485 {
00486     assert(mInputCurr.mFile != stdin);
00487     if (!LoopTestConditions()) return false;
00488 
00489     // no exit yet, go back to the beginning of the procedure
00490     if (fseek(mInputCurr.mFile, mLoopProcedure, SEEK_SET) != 0) {
00491         _ftprintf(stderr, _T("Failed loop procedure call. Exiting loop.\n"));
00492         mLoopLimit = 0;
00493         return false;
00494     }
00495 
00496     return true;
00497 }
00498 
00499 void
00500 MSYS_ThreadImpl::LoopStart()
00501 {
00502     assert(mInputCurr.mFile != stdin);
00503 
00504     // call the procedure
00505     long lCurr = ftell(mInputCurr.mFile);
00506     if (fseek(mInputCurr.mFile, mLoopProcedure, SEEK_SET) != 0) {
00507         _ftprintf(stderr, _T("Failed loop procedure call. Exiting loop.\n"));
00508         mLoopLimit = 0;
00509         return;
00510     }
00511 
00512     // push our return value
00513     mInputCurr.mProcStack.push_back(lCurr);
00514 }
00515 
00516 bool
00517 MSYS_ThreadImpl::LoopTestConditions()
00518 {
00519     assert(mInputCurr.mFile != stdin);
00520     if (mLoopLimit == 0) return false;
00521 
00522     // check our loop exit status
00523     if (mLoopLimit < 0) {
00524         // negative values are counter limits
00525         if (mLoopLimit++ == 0) return false;
00526     }
00527     else {
00528         // positive values are time limits
00529         time_t tNow;
00530         time(&tNow);
00531         if (tNow >= mLoopLimit) {
00532             mLoopLimit = 0;
00533             return false;
00534         }
00535     }
00536 
00537     return true;
00538 }
00539 
00540 bool MSYS_DispatchEntry::operator==(const MSYS_DispatchEntry & rhs) const {
00541     return _tcsicmp(cmd, rhs.cmd) == 0;
00542 }
00543 
00544 
00545 // ----------------------------------------------------------------------------
00546 // LOCAL FUNCTION DEFINITIONS
00547 // ----------------------------------------------------------------------------
00548 
00549 // general functions
00550 static void InitInternal();
00551 static bool FileExists(const TCHAR * aFilePath);
00552 static bool StringReplacements(MSYS_ThreadImpl * aThread, Buffer & aBuf);
00553 #ifdef _WIN32
00554 static void InputFromConsole(Buffer & aBuf);
00555 #endif // _WIN32
00556 static void OutputToConsole(const TCHAR * aString);
00557 static void OutputToTeeFile(MSYS_ThreadImpl * aThread, const Buffer & aBuf);
00558 
00559 // dispatch loop
00560 static bool Input(MSYS_ThreadImpl * aThread, Buffer & aBuf);
00561 static void Parse(TCHAR * aBuf, TCHAR *& aCommand, TCHAR *& aArgs);
00562 static void ParseArgs(TCHAR * aBuf, bool aUnquote, MSYS_Args & aArgs);
00563 static bool Dispatch(MSYS_ThreadImpl * aThread, TCHAR * aCommand, TCHAR * aArgs, PARSE aParse);
00564 
00565 // internal command handlers
00566 static void DoChdir(MSYS_Thread * thrd, MSYS_Args & aArgs);
00567 static void DoEcho(MSYS_Thread * thrd, MSYS_Args & aArgs);
00568 static void DoExit(MSYS_Thread * thrd, MSYS_Args & aArgs);
00569 static void DoHelp(MSYS_Thread * thrd, MSYS_Args & aArgs);
00570 static void DoLoop(MSYS_Thread * thrd, MSYS_Args & aArgs);
00571 static void DoProc(MSYS_Thread * thrd, MSYS_Args & aArgs);
00572 static void DoPwd(MSYS_Thread * thrd, MSYS_Args & aArgs);
00573 static void DoRead(MSYS_Thread * thrd, MSYS_Args & aArgs);
00574 static void DoSetOpt(MSYS_Thread * thrd, MSYS_Args & aArgs);
00575 static void DoShell(MSYS_Thread * thrd, MSYS_Args & aArgs);
00576 static void DoSleep(MSYS_Thread * thrd, MSYS_Args & aArgs);
00577 static void DoTee(MSYS_Thread * thrd, MSYS_Args & aArgs);
00578 static void DoTimer(MSYS_Thread * thrd, MSYS_Args & aArgs);
00579 #ifndef MSYS_NOTHREADS
00580 static void DoThreads(MSYS_Thread * thrd, MSYS_Args & aArgs);
00581 #endif // MSYS_NOTHREADS
00582 
00583 // internal option handlers
00584 static void OptConsole(MSYS_Thread * thrd, MSYS_Args & aArgs);
00585 static void OptThreads(MSYS_Thread * thrd, MSYS_Args & aArgs);
00586 static void OptUtf8(MSYS_Thread * thrd, MSYS_Args & aArgs);
00587 
00588 
00589 // ----------------------------------------------------------------------------
00590 // LOCAL VARIABLE DEFINITIONS
00591 // ----------------------------------------------------------------------------
00592 
00593 extern const MSYS_DispatchEntry g_rgInternal[]; // forward declaration
00594 
00595 static MSYS_DispatchEntry * g_rgDispatch = NULL;
00596 static bool                 g_bEnableThreads = false;
00597 static ThreadMap            g_mapThreads;
00598 static ThreadLock           g_lockThreadsMap;
00599 static ThreadLock           g_lockOutputConsole;
00600 static ThreadLock           g_lockOutputTeeFile;
00601 static MSYS_ThreadImpl *    g_pRootThread = NULL;
00602 
00603 
00604 // ----------------------------------------------------------------------------
00605 // FUNCTION DEFINITIONS
00606 // ----------------------------------------------------------------------------
00607 
00613 #ifdef _WIN32
00614 static unsigned __stdcall InputLoop(void * thrd)
00615 #else
00616 static void * InputLoop(void * thrd)
00617 #endif
00618 {
00619     MSYS_ThreadImpl * aThread = reinterpret_cast<MSYS_ThreadImpl*>(thrd);
00620 
00621     Buffer inputBuffer;
00622     TCHAR *pCommand, *pArgs;
00623 
00624     // menu dispatch loop
00625     TCHAR szPrompt[200];
00626     bool bInCommentBlock = false;
00627     bool bStringReps = false;
00628     for (bool isOK = true; isOK && !aThread->bStopThread; bStringReps = false) {
00629         MSYS_GetPrompt(aThread, szPrompt, sizeof(szPrompt)/sizeof(szPrompt[0]));
00630         if (aThread->mInputCurr.mFile == stdin) {
00631             if (aThread->mTeeFile) fflush(aThread->mTeeFile);
00632             _ftprintf(stdout, _T("%s"), szPrompt);
00633             Input(aThread, inputBuffer);
00634             bStringReps = StringReplacements(aThread, inputBuffer);
00635         }
00636         else {
00637             // when the current input file is finished (or returns an error)
00638             // close it and return to the previous one
00639             if (!Input(aThread, inputBuffer)) {
00640                 if (!aThread->mInputCurr.mProcStack.empty()) {
00641                     MSYS_Display(aThread,
00642                         _T("WARNING: procedure stack not empty at end of file.\n"));
00643                 }
00644                 fclose(aThread->mInputCurr.mFile);
00645 #ifdef _WIN32
00646                 CloseHandle(aThread->mInputCurr.mFileLock);
00647 #endif
00648                 isOK = !aThread->mInputStack.empty();
00649                 if (isOK) {
00650                     aThread->mInputCurr = aThread->mInputStack.back();
00651                     aThread->mInputStack.pop_back();
00652                 }
00653                 bInCommentBlock = false;
00654                 continue;
00655             }
00656             bStringReps = StringReplacements(aThread, inputBuffer);
00657         }
00658 
00659         Parse(inputBuffer.mBuf, pCommand, pArgs);
00660         if (_tcsicmp(pCommand, _T("*/")) == 0) {
00661             bInCommentBlock = false;
00662             continue;
00663         }
00664         if (bInCommentBlock || *pCommand == ';' || *pCommand == '#') { // comments
00665             continue;
00666         }
00667         if (_tcsicmp(pCommand, _T("/*")) == 0) {
00668             bInCommentBlock = true;
00669             continue;
00670         }
00671 
00672         // remove special prefixes. shell escape (!) handled separately.
00673         // @ no echo, $ time command, = no help, - no parse, + force parse
00674         const TCHAR szPrefix[] = _T("@$=-+");
00675         enum { NO_ECHO, TIMING, NO_HELP, NO_PARSE, USE_PARSE, COUNT };
00676         assert(_tcslen(szPrefix) == COUNT);
00677         bool rgFlags[COUNT] = { false };
00678         const TCHAR * pItem = _tcschr(szPrefix, *pCommand);
00679         while (pItem && *pCommand) {
00680             ++pCommand;
00681             rgFlags[pItem - szPrefix] = true;
00682             pItem = _tcschr(szPrefix, *pCommand);
00683         }
00684         if (!*pCommand) { // if we had a space before the command
00685             Parse(pArgs, pCommand, pArgs);
00686         }
00687 
00688         // process the command
00689         Timer lineTimer;
00690         aThread->mUseNeedHelp = !rgFlags[NO_HELP];
00691         if (rgFlags[TIMING]) {
00692             lineTimer.Start(true);
00693         }
00694         if (aThread->mOptionConsole == DISPLAY_ALL || aThread->mOptionConsole == DISPLAY_PROMPT) {
00695             if (!rgFlags[NO_ECHO]) {
00696                 if (bStringReps || aThread->mInputCurr.mFile != stdin) {
00697                     MSYS_Display(aThread, _T("%s%s %s\n"), szPrompt, pCommand, pArgs);
00698                 }
00699                 else if (aThread->mTeeFile) {
00700                     Buffer buf;
00701                     buf.Format(_T("%s%s %s\n"), szPrompt, pCommand, pArgs);
00702                     OutputToTeeFile(aThread, buf);
00703                 }
00704             }
00705         }
00706         if (*pCommand) {
00707             PARSE p = PARSE_DEFAULT;
00708             if (rgFlags[NO_PARSE])
00709                 p = PARSE_OFF;
00710             else if (rgFlags[USE_PARSE])
00711                 p = PARSE_ON;
00712             isOK = Dispatch(aThread, pCommand, pArgs, p);
00713         }
00714         if (rgFlags[TIMING]) {
00715             lineTimer.Stop();
00716             long nElapsed = lineTimer.GetCurrentElapsed();
00717             if (aThread->mOptionConsole == DISPLAY_PROMPT) {
00718                 // force console output for this
00719                 TCHAR szResults[100];
00720                 _sntprintf(szResults, 100, _T("Elapsed time %ld.%03ld seconds.\n"),
00721                     nElapsed / 1000, nElapsed % 1000);
00722                 OutputToConsole(szResults);
00723             }
00724             else {
00725                 MSYS_Display(aThread, _T("Elapsed time %ld.%03ld seconds.\n"),
00726                     nElapsed / 1000, nElapsed % 1000);
00727             }
00728         }
00729         aThread->mUseNeedHelp = true;
00730     }
00731 
00732     aThread->bIsRunning = false;
00733     return 0;
00734 }
00735 
00736 extern bool
00737 MSYS_Process(
00738     const TCHAR *   aInputFile,
00739     bool            aEnableThreads
00740     )
00741 {
00742     // make sure no-one starts us multiple times in different threads
00743     static bool bIsRunning = false;
00744     assert(!bIsRunning);
00745     if (bIsRunning) return false;
00746     bIsRunning = true;
00747 
00748     // create our main thread
00749     InitInternal();
00750     g_pRootThread = new MSYS_ThreadImpl(_T("<main>"), true);
00751     if (!g_pRootThread) {
00752         _ftprintf(stderr, _T("Out of memory.\n"));
00753         exit(1);
00754     }
00755     MSYS_ThreadUserCreate(g_pRootThread, NULL);
00756     assert(g_pRootThread->GetThreadId() == MSYS_Thread::ROOT_THREAD);
00757 
00758 #ifdef MSYS_NOTHREADS
00759     if (aEnableThreads) {
00760         _ftprintf(stderr, _T("Threads cannot be enabled. Thread support is compiled out.\n"));
00761     }
00762     g_bEnableThreads = false;
00763 #else
00764     g_bEnableThreads = aEnableThreads;
00765 #endif // MSYS_NOTHREADS
00766 
00767     // prepare the main thread input file if we have one
00768     bool bRunInputLoop = true;
00769     MSYS_Args args;
00770     if (aInputFile) {
00771         args.resize(1);
00772         // DoRead never modifies the filename, so removing the const is safe...
00773         args[0] = const_cast<TCHAR*>(aInputFile);
00774         DoRead(g_pRootThread, args);
00775         if (g_pRootThread->mInputCurr.mFile == stdin) { // failed
00776             bRunInputLoop = false;
00777         }
00778         else {
00779             // don't return to stdin
00780             g_pRootThread->mInputStack.pop_back();
00781         }
00782     }
00783     if (bRunInputLoop) {
00784         // load the default properties file. Success or failure doesn't
00785         // matter for this file.
00786         if (FileExists(MSYS_DefaultOptionsFile)) {
00787             args.resize(1);
00788             // DoRead never modifies the filename, so removing the const is safe...
00789             args[0] = const_cast<TCHAR*>(MSYS_DefaultOptionsFile);
00790             DoRead(g_pRootThread, args);
00791         }
00792 
00793         // run the menu
00794         InputLoop(g_pRootThread);
00795     }
00796 
00797     // wait for and clear all threads before returning
00798 #ifndef MSYS_NOTHREADS
00799     if (g_bEnableThreads) {
00800         args.clear();
00801         args.push_back(_T("joinall"));
00802         DoThreads(g_pRootThread, args);
00803     }
00804     assert(g_mapThreads.empty());
00805 #endif // MSYS_NOTHREADS
00806 
00807     // shutdown
00808     free(g_rgDispatch);
00809     g_rgDispatch = NULL;
00810     delete g_pRootThread;
00811     g_pRootThread = NULL;
00812 
00813     bIsRunning = false;
00814     return true;
00815 }
00816 
00817 static bool
00818 Input(
00819     MSYS_ThreadImpl *   aThread,
00820     Buffer &            aBuf
00821     )
00822 {
00823 #ifdef _WIN32
00824     if (aThread->mInputCurr.mFile == stdin) {
00825         InputFromConsole(aBuf);
00826         return true;
00827     }
00828 #endif
00829 
00830 #ifdef _UNICODE
00831     char * pBuf    = aThread->mCharBuf.mBuf;
00832     size_t nBufSiz = aThread->mCharBuf.mBufSiz;
00833 #else
00834     char * pBuf    = aBuf.mBuf;
00835     size_t nBufSiz = aBuf.mBufSiz;
00836 #endif
00837 
00838     // always read a line in single byte chars from the file. We do this
00839     // knowing that UTF-8 and most MBCS encodings will not confuse an end of
00840     // line with any other character.
00841     if (!fgets(pBuf, (int)nBufSiz, aThread->mInputCurr.mFile)) {
00842         if (ferror(aThread->mInputCurr.mFile)) {
00843             _ftprintf(stderr, _T("Error while reading input file: %s\n"),
00844                 aThread->mInputCurr.mPath.c_str());
00845         }
00846         return false;
00847     }
00848 
00849     // ensure that we have a complete line
00850     size_t nBufLen = strlen(pBuf);
00851     if (nBufLen >= nBufSiz - 3) { // CR LF NULL
00852         _ftprintf(stderr, _T("Line too long while reading input file: %s\n"),
00853             aThread->mInputCurr.mPath.c_str());
00854         return false;
00855     }
00856 
00857 #ifdef _UNICODE
00858     // convert to Unicode
00859     if (aThread->mInputCurr.mIsUtf8) {
00860         const UTF8 *pIn = (UTF8*)pBuf, *pInEnd = (UTF8*)(pBuf + nBufLen + 1);
00861         UTF16 *pOut = (UTF16*)aBuf.mBuf, *pOutEnd = (UTF16*)(aBuf.mBuf + aBuf.mBufSiz);
00862         ConversionResult rc = ConvertUTF8toUTF16(
00863             &pIn, pInEnd, &pOut, pOutEnd, lenientConversion);
00864         if (rc != conversionOK) {
00865             _ftprintf(stderr, _T("UTF-8 encoding error reading input file: %s\n"),
00866                 aThread->mInputCurr.mPath.c_str());
00867             return false;
00868         }
00869     }
00870     else {
00871         size_t nCount = mbstowcs(aBuf.mBuf, pBuf, aBuf.mBufSiz);
00872         if (nCount == (size_t) -1) {
00873             _ftprintf(stderr, _T("Native encoding error reading input file: %s\n"),
00874                 aThread->mInputCurr.mPath.c_str());
00875             return false;
00876         }
00877         if (nCount == aBuf.mBufSiz) {
00878             _ftprintf(stderr, _T("Line too long while reading input file: %s\n"),
00879                 aThread->mInputCurr.mPath.c_str());
00880             return false;
00881         }
00882     }
00883 #endif
00884 
00885     return true;
00886 }
00887 
00888 static void
00889 Parse(
00890     TCHAR *  aBuf,
00891     TCHAR *& aCommand,
00892     TCHAR *& aArgs
00893     )
00894 {
00895     // skip leading whitespace
00896     while (_istspace(*aBuf)) ++aBuf;
00897 
00898     // found start of command
00899     aCommand = aBuf;
00900 
00901     // find end of command
00902     while (*aBuf && !_istspace(*aBuf)) ++aBuf;
00903 
00904     // no arguments
00905     if (!*aBuf) {
00906         aArgs = aBuf;
00907         return;
00908     }
00909 
00910     // terminate command
00911     *aBuf++ = 0;
00912     aArgs = aBuf;
00913 
00914     // trim trailing spaces from the args
00915     aBuf += _tcslen(aBuf);
00916     for (--aBuf; aBuf >= aArgs && _istspace(*aBuf); --aBuf) {
00917         *aBuf = 0;
00918     }
00919 }
00920 
00921 static void
00922 ParseArgs(
00923     TCHAR *     aBuf,
00924     bool        aFullParse,
00925     MSYS_Args & aArgs
00926     )
00927 {
00928     aArgs.clear();
00929 
00930     bool isDone;
00931     do {
00932         // skip leading whitespace
00933         while (_istspace(*aBuf)) ++aBuf;
00934         if (!*aBuf) break; // done
00935 
00936         // start of arg
00937         TCHAR * pArg = aBuf;
00938 
00939         // There is a special interpretation of backslash characters when they are
00940         // followed by a quotation mark character ("), as follows:
00941         //
00942         // * 2n backslashes followed by a quotation mark produce n backslashes followed
00943         //      by a quotation mark.
00944         // * (2n) + 1 backslashes followed by a quotation mark again produce n
00945         //      backslashes followed by a quotation mark.
00946         // * n backslashes not followed by a quotation mark simply produce n backslashes.
00947         //
00948         // This is required to allow quotation marks to be quoted in a string. Yes,
00949         // it is also a pain in the arse.
00950         //
00951         TCHAR * pOut = aBuf;
00952         bool inQuotes = false;
00953         for (;;) {
00954             if (!*aBuf) {
00955                 break;
00956             }
00957             if (!inQuotes && (*aBuf == ' ' || *aBuf == '\t')) {
00958                 break;
00959             }
00960             if (*aBuf == '"') {
00961                 inQuotes = !inQuotes;
00962                 aBuf++;
00963                 continue;
00964             }
00965             if (*aBuf != '\\') {
00966                 *pOut++ = *aBuf++;
00967                 continue;
00968             }
00969 
00970             // need to know if we are using quoted (2n=n) or unquoted (n=n)
00971             if (*aBuf == '\\') {
00972                 TCHAR * pLast = aBuf + 1;
00973                 while (*pLast == '\\') ++pLast;
00974                 if (*pLast != '"') {
00975                     // non-quoting backslash, n=n, then continue normally
00976                     while (*aBuf == '\\') {
00977                         *pOut++ = *aBuf++;
00978                     }
00979                     continue;
00980                 }
00981 
00982                 // quoting backslash, 2n=n, then continue normally
00983                 while (*aBuf == '\\') {
00984                     if (!*++aBuf) break;
00985                     *pOut++ = *aBuf++;
00986                 }
00987                 continue;
00988             }
00989         }
00990 
00991         isDone = (*aBuf == 0);
00992         *pOut = 0;
00993         ++aBuf;
00994 
00995         // add arg to arg array
00996         aArgs.push_back(pArg);
00997 
00998         // parse this arg to remove quoted chars
00999         if (aFullParse) {
01000             TCHAR * out = pArg;
01001             while (*pArg) {
01002                 // full parse means that we turn known escape sequences into the
01003                 // special characters.
01004                 if (*pArg == '\\') {
01005                     ++pArg;
01006                     switch (*pArg) {
01007                     case 'n':  *out++ = '\n'; pArg++; break;
01008                     case 'r':  *out++ = '\r'; pArg++; break;
01009                     case 't':  *out++ = '\t'; pArg++; break;
01010                     default:
01011                         *out++ = '\\';
01012                         *out++ = *pArg++;
01013                     }
01014                 }
01015                 else {
01016                     *out++ = *pArg++;
01017                 }
01018             }
01019             *out = 0;
01020         }
01021     }
01022     while (!isDone);
01023 }
01024 
01025 static void DoExit(MSYS_Thread * thrd, MSYS_Args & aArgs)
01026 {
01027     MSYS_ThreadImpl * aThread = reinterpret_cast<MSYS_ThreadImpl*>(thrd);
01028 
01029     if (MSYS_NeedHelp(aThread, DoExit, aArgs)) return;
01030 
01031     TCHAR * pAction = aArgs.size() >= 1 ? aArgs[0] : NULL;
01032     if (!pAction) return;
01033 
01034     if (_tcsicmp(_T("now"), pAction) == 0) {
01035 #ifndef MSYS_NOTHREADS
01036         MSYS_Args args;
01037         args.push_back(_T("stopall"));
01038         DoThreads(aThread, args);
01039 #endif // MSYS_NOTHREADS
01040 
01041         g_pRootThread->bStopThread = true;
01042         return;
01043     }
01044 
01045     MSYS_Display(aThread, _T("Invalid arguments. See help.\n"));
01046 }
01047 
01048 static bool ReplaceString(Buffer & aBuf, const TCHAR * aSearch, const TCHAR * aReplace)
01049 {
01050     TCHAR * pLoc = _tcsstr(aBuf.mBuf, aSearch);
01051     if (pLoc) {
01052         int nBuf     = (int) _tcslen(aBuf.mBuf);
01053         int nSearch  = (int) _tcslen(aSearch);
01054         int nReplace = (int) _tcslen(aReplace);
01055         int nDiff    = nReplace - nSearch;
01056         while (pLoc) {
01057             while (nBuf + nReplace - nSearch >= (int) aBuf.mBufSiz) {
01058                 aBuf.Grow();
01059             }
01060             if (nDiff != 0) {
01061                 int nRemaining = (int) (nBuf - (pLoc - aBuf.mBuf) - nSearch);
01062                 memmove(pLoc + nSearch + nDiff, pLoc + nSearch,
01063                     nRemaining * sizeof(TCHAR));
01064                 pLoc[nSearch + nDiff + nRemaining] = 0;
01065             }
01066             memcpy(pLoc, aReplace, nReplace * sizeof(TCHAR));
01067             pLoc = _tcsstr(pLoc + nReplace, aSearch);
01068         }
01069         return true;
01070     }
01071     return false;
01072 }
01073 
01074 static bool StringReplacements(MSYS_ThreadImpl * aThread, Buffer & aBuf)
01075 {
01076     if (!aThread->mStringReplacements) return false;
01077     if (!_tcsstr(aBuf.mBuf, _T("{{"))) return false;
01078 
01079     TCHAR szBuf[200];
01080     const size_t nBufSiz = sizeof(szBuf)/sizeof(szBuf[0]);
01081     bool bMadeReplacement = false;
01082     if (g_bEnableThreads) {
01083         _sntprintf(szBuf, nBufSiz, _T("%d"), aThread->GetThreadId());
01084         if (ReplaceString(aBuf, _T("{{TID}}"), szBuf)) {
01085             bMadeReplacement = true;
01086         }
01087         if (ReplaceString(aBuf, _T("{{TNAME}}"), aThread->mThreadName.c_str())) {
01088             bMadeReplacement = true;
01089         }
01090     }
01091 
01092     if (_tcsstr(aBuf.mBuf, _T("{{NOW}}"))) {
01093         time_t tNow;
01094         time(&tNow);
01095         struct tm tmNow = *localtime(&tNow);
01096         _sntprintf(szBuf, nBufSiz, _T("%02d/%02d/%04d %02d:%02d:%02d"),
01097             tmNow.tm_mday, tmNow.tm_mon+1, tmNow.tm_year+1900,
01098             tmNow.tm_hour, tmNow.tm_min, tmNow.tm_sec);
01099         ReplaceString(aBuf, _T("{{NOW}}"), szBuf);
01100         bMadeReplacement = true;
01101     }
01102 
01103     return bMadeReplacement;
01104 }
01105 
01106 static void OutputToTeeFile(MSYS_ThreadImpl * aThread, const Buffer & aBuf)
01107 {
01108     if (!aThread->mTeeFile) return;
01109 
01110 #ifdef _UNICODE
01111     // output is always in UTF-8
01112     while (aThread->mCharBuf.mBufSiz < aBuf.mBufSiz) {
01113         aThread->mCharBuf.Grow();
01114     }
01115     UTF8 *pOut    = (UTF8 *) aThread->mCharBuf.mBuf;
01116     UTF8 *pOutEnd = (UTF8 *) aThread->mCharBuf.mBuf + aThread->mCharBuf.mBufSiz;
01117     size_t nLen = _tcslen(aBuf.mBuf) + 1;
01118     const UTF16 *pIn    = (UTF16*)  aBuf.mBuf;
01119     const UTF16 *pInEnd = (UTF16*) (aBuf.mBuf + nLen);
01120     ConversionResult rc = ConvertUTF16toUTF8(
01121         &pIn, pInEnd, &pOut, pOutEnd, lenientConversion);
01122     if (rc != conversionOK) {
01123         _ftprintf(stderr, _T("UTF-8 encoding error writing output file\n"));
01124         return;
01125     }
01126     size_t nBytes = (pOut - (UTF8*)aThread->mCharBuf.mBuf) - 1;
01127     AutoLock lock(g_lockOutputTeeFile);
01128     if (nBytes != fwrite(aThread->mCharBuf.mBuf, 1, nBytes, aThread->mTeeFile)) {
01129         _ftprintf(stderr, _T("Error writing output file\n"));
01130         return;
01131     }
01132 #else // _UNICODE
01133     AutoLock lock(g_lockOutputTeeFile);
01134     _ftprintf(aThread->mTeeFile, _T("%s"), aBuf.mBuf);
01135 #endif
01136 }
01137 
01138 extern void MSYS_Display(MSYS_Thread * thrd, const TCHAR * aFormat, ...)
01139 {
01140     MSYS_ThreadImpl * aThread = reinterpret_cast<MSYS_ThreadImpl*>(thrd);
01141 
01142     // generate the output string
01143     va_list ap;
01144     va_start(ap, aFormat);
01145     aThread->mHandlerBuf.FormatV(aFormat, ap);
01146     va_end(ap);
01147 
01148     StringReplacements(aThread, aThread->mHandlerBuf);
01149 
01150     if (aThread->mOptionConsole >= DISPLAY_OUTPUT) {
01151         // DISPLAY_OUTPUT, DISPLAY_ALL
01152         // DISPLAY_PROMPT will be handled where we handle the prompt
01153         OutputToConsole(aThread->mHandlerBuf.mBuf);
01154     }
01155     if (aThread->mTeeFile) {
01156         OutputToTeeFile(aThread, aThread->mHandlerBuf);
01157     }
01158 }
01159 
01160 extern void MSYS_DisplayWrapped(MSYS_Thread * thrd, const TCHAR * aText)
01161 {
01162     MSYS_ThreadImpl * aThread = reinterpret_cast<MSYS_ThreadImpl*>(thrd);
01163 
01164     assert(aText);
01165 
01166     std::tstring sText(aText);
01167     size_t n = sText.find(_T('\t'));
01168     while (n != std::tstring::npos) {
01169         sText.replace(n, 1, _T("    "));
01170         n = sText.find(_T('\t'), n + 3);
01171     }
01172     aText = sText.c_str();
01173 
01174     const int MAX_LINE_LEN = 79;
01175     int nLineLen, nStartIdx, nCurrIdx = 0, nLastBreakIdx = -1;
01176     while (nCurrIdx < (int) sText.size()) {
01177         nLineLen = 0;
01178         nStartIdx = nCurrIdx = nLastBreakIdx + 1;
01179 
01180         // find the end of this line
01181         while (nCurrIdx < (int) sText.size() && nLineLen < MAX_LINE_LEN) {
01182             if (_istspace(sText[nCurrIdx])) {
01183                 nLastBreakIdx = nCurrIdx;
01184                 if (sText[nCurrIdx] == _T('\n')) {
01185                     break; // line break
01186                 }
01187             }
01188 
01189             ++nLineLen;
01190             ++nCurrIdx;
01191         }
01192         if (!sText[nCurrIdx]) {
01193             nLastBreakIdx = nCurrIdx;
01194         }
01195 
01196         // display the line
01197         std::tstring sLine(sText, nStartIdx, nLastBreakIdx - nStartIdx);
01198         MSYS_Display(aThread, _T("%s\n"), sLine.c_str());
01199     }
01200 }
01201 
01202 extern bool MSYS_NeedHelp(MSYS_Thread * thrd, MSYS_Handler fn, MSYS_Args & aArgs, bool aShowHelp)
01203 {
01204     MSYS_ThreadImpl * aThread = reinterpret_cast<MSYS_ThreadImpl*>(thrd);
01205 
01206     if (aThread->mUseNeedHelp) {
01207         for (size_t n = 0; n < aArgs.size(); ++n) {
01208             if (!_tcsicmp(aArgs[n], _T("-?")) || !_tcsicmp(aArgs[n], _T("/?"))) {
01209                 if (aShowHelp) {
01210                     MSYS_ShowHelp(aThread, fn);
01211                 }
01212                 return true;
01213             }
01214         }
01215     }
01216     return false;
01217 }
01218 
01219 // dummy functions for MSYS_ShowHelp
01220 static void HelpExternal(MSYS_Thread *, MSYS_Args &) { }
01221 static void HelpInternal(MSYS_Thread *, MSYS_Args &) { }
01222 static void HelpAll(MSYS_Thread *, MSYS_Args &) { }
01223 
01224 static void DoHelp(MSYS_Thread * thrd, MSYS_Args & aArgs)
01225 {
01226     MSYS_ThreadImpl * aThread = reinterpret_cast<MSYS_ThreadImpl*>(thrd);
01227 
01228     if (MSYS_NeedHelp(aThread, DoHelp, aArgs)) return;
01229 
01230     if (aArgs.empty()) {
01231         MSYS_ShowHelp(aThread, HelpExternal);
01232         return;
01233     }
01234 
01235     // check for help options
01236     if (aArgs[0][0] == '/') {
01237         TCHAR ch = (TCHAR) _totlower(aArgs[0][1]);
01238         if (ch == 'i') {
01239             MSYS_ShowHelp(aThread, HelpInternal);
01240         }
01241         else if (ch == 'a') {
01242             MSYS_ShowHelp(aThread, HelpAll);
01243         }
01244         else {
01245             MSYS_Display(aThread, _T("Unknown command.\n"));
01246         }
01247         return;
01248     }
01249 
01250     // should be a command
01251     const TCHAR * cmd = aArgs[0];
01252     for (int n = 0; g_rgDispatch[n].cmd; ++n) {
01253         if (_tcsicmp(cmd, g_rgDispatch[n].cmd) == 0) {
01254             // call the function to display the extended help
01255             aArgs.clear();
01256             if (g_rgDispatch[n].fn == DoShell) {
01257                 aArgs.push_back(_T(""));
01258             }
01259             aArgs.push_back(_T("-?"));
01260             g_rgDispatch[n].fn(aThread, aArgs);
01261             return;
01262         }
01263     }
01264 
01265     // unknown command
01266     MSYS_Display(aThread, _T("Unknown command.\n"));
01267 }
01268 
01269 static void DoSleep(MSYS_Thread * thrd, MSYS_Args & aArgs)
01270 {
01271     MSYS_ThreadImpl * aThread = reinterpret_cast<MSYS_ThreadImpl*>(thrd);
01272 
01273     if (MSYS_NeedHelp(aThread, DoSleep, aArgs)) return;
01274 
01275     if (aArgs.size() != 1) {
01276         MSYS_Display(aThread, _T("Invalid arguments. See help.\n"));
01277         return;
01278     }
01279 
01280     int nMillis = _ttoi(aArgs[0]);
01281     if (nMillis < 0) {
01282         MSYS_Display(aThread, _T("Invalid arguments. See help.\n"));
01283         return;
01284     }
01285 
01286 #ifdef _WIN32
01287     Sleep((DWORD)nMillis);
01288 #else
01289     usleep(nMillis * 1000);
01290 #endif
01291 }
01292 
01293 static void DoEcho(MSYS_Thread * thrd, MSYS_Args & aArgs)
01294 {
01295     MSYS_ThreadImpl * aThread = reinterpret_cast<MSYS_ThreadImpl*>(thrd);
01296 
01297     if (MSYS_NeedHelp(aThread, DoEcho, aArgs)) return;
01298 
01299     for (size_t n = 0; n < aArgs.size(); ++n) {
01300         MSYS_Display(aThread, _T("%s"), aArgs[n]);
01301     }
01302     MSYS_Display(aThread, _T("\n"));
01303 }
01304 
01305 static void DoPwd(MSYS_Thread * thrd, MSYS_Args & aArgs)
01306 {
01307     MSYS_ThreadImpl * aThread = reinterpret_cast<MSYS_ThreadImpl*>(thrd);
01308 
01309     if (MSYS_NeedHelp(aThread, DoPwd, aArgs)) return;
01310 
01311     TCHAR buf[MAX_PATH];
01312     if (_tgetcwd(buf, sizeof(buf))) {
01313         MSYS_Display(aThread, _T("%s\n"), buf);
01314     }
01315 }
01316 
01317 static void DoChdir(MSYS_Thread * thrd, MSYS_Args & aArgs)
01318 {
01319     MSYS_ThreadImpl * aThread = reinterpret_cast<MSYS_ThreadImpl*>(thrd);
01320 
01321     if (MSYS_NeedHelp(aThread, DoChdir, aArgs)) return;
01322 
01323     if (aArgs.size() != 1) {
01324         MSYS_Display(aThread, _T("Invalid arguments. See help.\n"));
01325         return;
01326     }
01327 
01328     const TCHAR * pPath = aArgs[0];
01329     if (_tchdir(pPath) != 0) {
01330         if (errno == ENOENT) {
01331             MSYS_Display(aThread, _T("Invalid directory.\n"));
01332         }
01333         else {
01334             MSYS_Display(aThread, _T("chdir() failed.\n"));
01335         }
01336         return;
01337     }
01338 }
01339 
01340 static void DoShell(MSYS_Thread * thrd, MSYS_Args & aArgs)
01341 {
01342     MSYS_ThreadImpl * aThread = reinterpret_cast<MSYS_ThreadImpl*>(thrd);
01343 
01344     assert(aArgs.size() == 2);
01345 
01346     const TCHAR * pCommand = aArgs[0];
01347     const TCHAR * pArgs = aArgs[1];
01348 
01349     // equivalent to MSYS_NeedHelp, but we only recognize "! -?" and "! /?"
01350     if (!*pCommand && (!_tcsicmp(pArgs, _T("-?")) || !_tcsicmp(pArgs, _T("/?")))) {
01351         MSYS_ShowHelp(aThread, DoShell);
01352         return;
01353     }
01354 
01355     fflush(stdout);
01356     fflush(stderr);
01357 
01358     std::tstring sCommandLine;
01359     if (!*pCommand && !*pArgs) {
01360 #ifdef _WIN32
01361         pCommand = _tgetenv(_T("COMSPEC"));
01362         if (!pCommand) {
01363             pCommand = _T("cmd.exe");
01364         }
01365 #else
01366         pCommand = _tgetenv(_T("SH"));
01367         if (!pCommand) {
01368             pCommand = _T("/bin/sh");
01369         }
01370 #endif
01371     }
01372 
01373     sCommandLine = pCommand;
01374     sCommandLine += _T(' ');
01375     sCommandLine += pArgs;
01376     _tsystem(sCommandLine.c_str());
01377 }
01378 
01379 static void DoRead(MSYS_Thread * thrd, MSYS_Args & aArgs)
01380 {
01381     MSYS_ThreadImpl * aThread = reinterpret_cast<MSYS_ThreadImpl*>(thrd);
01382 
01383     if (MSYS_NeedHelp(aThread, DoRead, aArgs)) return;
01384 
01385     if (aArgs.size() != 1) {
01386         MSYS_Display(aThread, _T("Invalid arguments. See help.\n"));
01387         return;
01388     }
01389 
01390     const TCHAR * pPath = aArgs[0];
01391 
01392     FILE * fp = _tfopen(pPath, _T("rb"));
01393 #ifdef _WIN32
01394     // create a shared-read lock on this file to prevent writes to it while
01395     // we are reading it, as we use the file offsets for procedures
01396     HANDLE hLock = fp ? CreateFile(pPath, GENERIC_READ, FILE_SHARE_READ,
01397         NULL, OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, NULL) : INVALID_HANDLE_VALUE;
01398     if (fp && hLock == INVALID_HANDLE_VALUE) {
01399         fclose(fp);
01400         fp = NULL;
01401     }
01402 #endif
01403     if (!fp) {
01404         _ftprintf(stderr, _T("Error opening file '%s'\n"), pPath);
01405         return;
01406     }
01407 
01408     // see if we have a UTF-8 file signature
01409     bool bIsUtf8 = false;
01410     const char * szUtf8Signature = "\xEF\xBB\xBF";
01411     char szSig[3];
01412     if (3 == fread(szSig, 1, 3, fp) && 0 == memcmp(szSig, szUtf8Signature, 3)) {
01413         bIsUtf8 = true;
01414     }
01415     else {
01416         fseek(fp, 0, SEEK_SET);
01417     }
01418 
01419     aThread->mInputStack.push_back(aThread->mInputCurr);
01420     aThread->mInputCurr = InputFile(fp, pPath, bIsUtf8);
01421 #ifdef _WIN32
01422     // ensure that we have a read lock on this file
01423     aThread->mInputCurr.mFileLock = hLock;
01424 #endif
01425 }
01426 
01427 #ifndef MSYS_NOTHREADS
01428 static void ThreadCreate(MSYS_ThreadImpl * aThread, TCHAR * aThreadName, TCHAR * aPath)
01429 {
01430     if (!aThreadName || !aPath) {
01431         MSYS_Display(aThread, _T("Invalid arguments. See help.\n"));
01432         return;
01433     }
01434 
01435     if (*aThreadName) {
01436         ThreadMap::const_iterator i = g_mapThreads.find(aThreadName);
01437         if (i != g_mapThreads.end()) {
01438             MSYS_Display(aThread, _T("Thread '%s' is already registered.\n"), aThreadName);
01439             return;
01440         }
01441     }
01442 
01443     // create our thread
01444     MSYS_ThreadImpl * pNewThread = new MSYS_ThreadImpl(aThreadName);
01445     if (!pNewThread) {
01446         _ftprintf(stderr, _T("Out of memory.\n"));
01447         exit(1);
01448     }
01449     MSYS_ThreadUserCreate(pNewThread, aThread);
01450 
01451     // generate a thread name if necessary
01452     if (!*aThreadName) {
01453         TCHAR szThreadId[100];
01454         _sntprintf(szThreadId, sizeof(szThreadId)/sizeof(szThreadId[0]),
01455             _T("%d"), pNewThread->GetThreadId());
01456         ThreadMap::const_iterator i = g_mapThreads.find(szThreadId);
01457         for (int nCount = 0; i != g_mapThreads.end(); ++nCount) {
01458             _sntprintf(szThreadId, sizeof(szThreadId)/sizeof(szThreadId[0]),
01459                 _T("%d-%d"), pNewThread->GetThreadId(), nCount);
01460             i = g_mapThreads.find(szThreadId);
01461         }
01462         pNewThread->mThreadName = szThreadId;
01463         aThreadName = NULL;
01464     }
01465 
01466     // default thread output to none
01467     pNewThread->mOptionConsole = DISPLAY_NONE;
01468 
01469     // start this thread reading input on the file
01470     MSYS_Args args;
01471     args.push_back(aPath);
01472     DoRead(pNewThread, args);
01473     if (pNewThread->mInputCurr.mFile == stdin) { // failed
01474         delete pNewThread;
01475         return;
01476     }
01477     pNewThread->mInputStack.pop_back(); // don't return to stdin
01478 
01479     // start a real thread executing
01480 #ifdef _WIN32
01481     unsigned threadID;
01482     pNewThread->mThreadHandle = (HANDLE) _beginthreadex(
01483         NULL, 0, InputLoop, pNewThread, 0, &threadID);
01484     if (!pNewThread->mThreadHandle) {
01485         delete pNewThread;
01486         _ftprintf(stderr, _T("Failed to start thread.\n"));
01487         return;
01488     }
01489 #else
01490     int rc = pthread_create(&pNewThread->mThreadHandle,
01491         NULL, InputLoop, pNewThread);
01492     if (rc != 0) {
01493         delete pNewThread;
01494         _ftprintf(stderr, _T("Failed to start thread.\n"));
01495         return;
01496     }
01497 #endif
01498 
01499     // add this thread handle to the thread table
01500     g_mapThreads.insert(ThreadMap::value_type(pNewThread->mThreadName, pNewThread));
01501 }
01502 #endif // MSYS_NOTHREADS
01503 
01504 #ifndef MSYS_NOTHREADS
01505 static void ThreadJoin(MSYS_ThreadImpl * aThread, TCHAR * aThreadName)
01506 {
01507     if (aThreadName) {
01508         ThreadMap::const_iterator i = g_mapThreads.find(aThreadName);
01509         if (i == g_mapThreads.end()) {
01510             MSYS_Display(aThread, _T("Thread '%s' is not registered.\n"), aThreadName);
01511             return;
01512         }
01513         if (i->second == aThread) {
01514             MSYS_Display(aThread, _T("Cannot wait on self.\n"));
01515             return;
01516         }
01517     }
01518     if (g_mapThreads.empty()) {
01519         return;
01520     }
01521 
01522     ThreadMap::iterator i = g_mapThreads.begin();
01523     for (; i != g_mapThreads.end(); ++i) {
01524         MSYS_ThreadImpl * pThread = i->second;
01525         if (pThread == aThread) continue;
01526         if (aThreadName && pThread->mThreadName != aThreadName) continue;
01527 #ifdef _WIN32
01528         DWORD dwResult = WaitForSingleObject(pThread->mThreadHandle, INFINITE);
01529         if (dwResult != WAIT_OBJECT_0) {
01530 #else
01531         int rc = pthread_join(pThread->mThreadHandle, NULL);
01532         if (rc != 0) {
01533 #endif
01534             MSYS_Display(aThread, _T("Wait for thread '%s' failed.\n"),
01535                 pThread->mThreadName.c_str());
01536         }
01537         delete pThread;
01538         if (aThreadName) {
01539             g_mapThreads.erase(i);
01540             break;
01541         }
01542     }
01543     if (!aThreadName) {
01544         g_mapThreads.clear();
01545     }
01546 }
01547 #endif // MSYS_NOTHREADS
01548 
01549 #ifndef MSYS_NOTHREADS
01550 static void ThreadStop(MSYS_ThreadImpl * aThread, TCHAR * aThreadName)
01551 {
01552     ThreadMap::const_iterator i = g_mapThreads.end();
01553     if (aThreadName) {
01554         i = g_mapThreads.find(aThreadName);
01555         if (i == g_mapThreads.end()) {
01556             MSYS_Display(aThread, _T("Thread '%s' is not registered.\n"), aThreadName);
01557             return;
01558         }
01559     }
01560     if (g_mapThreads.empty()) {
01561         return;
01562     }
01563 
01564     if (aThreadName) {
01565         i->second->bStopThread = true;
01566     }
01567     else {
01568         i = g_mapThreads.begin();
01569         for (int n = 0; i != g_mapThreads.end(); ++i, ++n) {
01570             i->second->bStopThread = true;
01571         }
01572     }
01573 }
01574 #endif // MSYS_NOTHREADS
01575 
01576 #ifndef MSYS_NOTHREADS
01577 static void DoThreads(MSYS_Thread * thrd, MSYS_Args & aArgs)
01578 {
01579     MSYS_ThreadImpl * aThread = reinterpret_cast<MSYS_ThreadImpl*>(thrd);
01580 
01581     if (MSYS_NeedHelp(aThread, DoThreads, aArgs)) return;
01582 
01583     if (!g_bEnableThreads) {
01584         MSYS_Display(aThread, _T("Threads are disabled.\n"));
01585         return;
01586     }
01587     AutoLock lock(g_lockThreadsMap);
01588 
01589     TCHAR * pAction = aArgs.size() >= 1 ? aArgs[0] : _T("list");
01590     TCHAR * pName   = aArgs.size() >= 2 ? aArgs[1] : NULL;
01591     TCHAR * pPath   = aArgs.size() >= 3 ? aArgs[2] : NULL;
01592 
01593     // list
01594     if (_tcsicmp(_T("list"), pAction) == 0) {
01595         if (g_mapThreads.empty()) {
01596             MSYS_Display(aThread, _T("No current threads.\n"));
01597             return;
01598         }
01599 
01600         ThreadMap::const_iterator i = g_mapThreads.begin();
01601         for (; i != g_mapThreads.end(); ++i) {
01602             const MSYS_ThreadImpl * pThread = i->second;
01603             MSYS_Display(aThread, _T("  %-2d %-12s %s\n"),
01604                 pThread->GetThreadId(), i->first.c_str(),
01605                 pThread->bIsRunning ? _T("running") : _T("done"));
01606         }
01607         return;
01608     }
01609 
01610     // start
01611     if (_tcsicmp(_T("start"), pAction) == 0) {
01612         ThreadCreate(aThread, pName, pPath);
01613         return;
01614     }
01615 
01616     // stop
01617     if (_tcsicmp(_T("stop"), pAction) == 0) {
01618         if (!pName) {
01619             MSYS_Display(aThread, _T("Invalid arguments. See help.\n"));
01620             return;
01621         }
01622         ThreadStop(aThread, pName);
01623         return;
01624     }
01625 
01626     // stopall
01627     if (_tcsicmp(_T("stopall"), pAction) == 0) {
01628         ThreadStop(aThread, NULL);
01629         return;
01630     }
01631 
01632     // join
01633     if (_tcsicmp(_T("join"), pAction) == 0) {
01634         if (!pName) {
01635             MSYS_Display(aThread, _T("Invalid arguments. See help.\n"));
01636             return;
01637         }
01638         ThreadJoin(aThread, pName);
01639         return;
01640     }
01641 
01642     // joinall
01643     if (_tcsicmp(_T("joinall"), pAction) == 0) {
01644         ThreadJoin(aThread, NULL);
01645         return;
01646     }
01647 
01648     MSYS_Display(aThread, _T("Invalid arguments. See help.\n"));
01649 }
01650 #endif // MSYS_NOTHREADS
01651 
01652 static void DoTee(MSYS_Thread * thrd, MSYS_Args & aArgs)
01653 {
01654     MSYS_ThreadImpl * aThread = reinterpret_cast<MSYS_ThreadImpl*>(thrd);
01655 
01656     if (MSYS_NeedHelp(aThread, DoTee, aArgs)) return;
01657 
01658     const TCHAR * pPath = aArgs.empty() ? NULL : aArgs[0];
01659     const TCHAR * pMode = _T("wb");
01660     if (aArgs.size() == 2 && !_tcsicmp(aArgs[0], _T("-append"))) {
01661         pMode = _T("ab");
01662         pPath = aArgs[1];
01663     }
01664 
01665     FILE * fpNew = NULL;
01666     if (pPath) {
01667         fpNew = _tfopen(pPath, pMode);
01668         if (!fpNew) {
01669             _ftprintf(stderr, _T("Error opening file '%s'\n"), pPath);
01670             return;
01671         }
01672     }
01673 
01674     if (aThread->mTeeFile) {
01675         fclose(aThread->mTeeFile);
01676         aThread->mTeeFile = NULL;
01677         _ftprintf(stdout, _T("Closed tee output file\n"));
01678     }
01679 
01680     if (fpNew) {
01681         aThread->mTeeFile = fpNew;
01682         _ftprintf(stdout, _T("Started to tee output to '%s'\n"), pPath);
01683     }
01684 }
01685 
01686 extern void
01687 MSYS_DisplayOption(
01688     MSYS_Thread *   aThread,
01689     const TCHAR *   aOption,
01690     const TCHAR *   aValue
01691     )
01692 {
01693     if (!aOption || !aValue) return;
01694 
01695     if (!*aValue || _tcschr(aValue, ' ')) {
01696         MSYS_Display(aThread, _T("setopt %-12s \"%s\"\n"), aOption, aValue);
01697     }
01698     else {
01699         MSYS_Display(aThread, _T("setopt %-12s %s\n"), aOption, aValue);
01700     }
01701 }
01702 
01703 static const TCHAR *
01704 GetConsoleType(
01705     DISPLAY aType
01706     )
01707 {
01708     switch (aType) {
01709     case DISPLAY_ALL:    return _T("all");
01710     case DISPLAY_PROMPT: return _T("prompt");
01711     case DISPLAY_OUTPUT: return _T("output");
01712     case DISPLAY_NONE:   return _T("none");
01713     default:             return NULL;
01714     }
01715 }
01716 
01717 static void DoSetOpt(MSYS_Thread * thrd, MSYS_Args & aArgs)
01718 {
01719     MSYS_ThreadImpl * aThread = reinterpret_cast<MSYS_ThreadImpl*>(thrd);
01720 
01721     if (MSYS_NeedHelp(aThread, DoSetOpt, aArgs, false)) {
01722         // determine if they have requested help for a specific option
01723         if (aArgs.size() == 2) {
01724             std::tstring sCommand = OPTION_PREFIX;
01725             sCommand += aArgs[0];
01726             for (int n = 0; g_rgDispatch[n].cmd; ++n) {
01727                 if (0 != _tcsicmp(g_rgDispatch[n].cmd, sCommand.c_str())) continue;
01728                 MSYS_ShowHelp(aThread, g_rgDispatch[n].fn);
01729                 return;
01730             }
01731         }
01732 
01733         MSYS_ShowHelp(aThread, DoSetOpt);
01734         MSYS_Display(aThread, _T("\nAvailable options are:\n"));
01735         for (int n = 0; g_rgDispatch[n].cmd; ++n) {
01736             if (_tcsnicmp(g_rgDispatch[n].cmd, OPTION_PREFIX, OPTION_PREFIX_LEN)) continue;
01737             MSYS_Display(aThread, _T("  %-15s%s\n"),
01738                 g_rgDispatch[n].cmd + OPTION_PREFIX_LEN, g_rgDispatch[n].oneHelp);
01739         }
01740         return;
01741     }
01742 
01743     std::tstring opt;
01744     std::tstring cmd;
01745     if (!aArgs.empty()) {
01746         opt = aArgs[0];
01747         cmd = OPTION_PREFIX;
01748         cmd += opt;
01749     }
01750 
01751     // load or save
01752     if (!aArgs.empty() && aArgs[0][0] == '-') {
01753         const TCHAR * pAction = aArgs[0];
01754         const TCHAR * pFile   = aArgs.size() > 1 ? aArgs[1] : MSYS_DefaultOptionsFile;
01755 
01756         if (!_tcsicmp(pAction, _T("-load"))) {
01757             MSYS_Args args;
01758             // DoRead never modifies the filename, so removing the const is safe...
01759             args.push_back(const_cast<TCHAR*>(pFile));
01760             DoRead(aThread, args);
01761             return;
01762         }
01763 
01764         if (!_tcsicmp(pAction, _T("-save"))) {
01765             // set tee to output file
01766             FILE * fpSave = aThread->mTeeFile;
01767             aThread->mTeeFile = _tfopen(pFile, _T("wb"));
01768             if (!aThread->mTeeFile) {
01769                 aThread->mTeeFile = fpSave;
01770                 MSYS_Display(aThread, _T("Failed to open options file.\n"));
01771                 return;
01772             }
01773 
01774             DISPLAY nSave = aThread->mOptionConsole;
01775             aThread->mOptionConsole = DISPLAY_NONE;
01776 
01777             // disable the console and set UTF-8 when we reload this
01778             aThread->mHandlerBuf.Format(_T("%s"),
01779                 _T("@setopt console none\n")
01780                 _T("@setopt utf8 true\n")
01781                 _T("\n")
01782                 );
01783             OutputToTeeFile(aThread, aThread->mHandlerBuf);
01784 
01785             // output all USER options
01786             MSYS_Args args;
01787             args.resize(2);
01788             TCHAR szCommand[MAX_PATH+1];
01789             for (int n = 0; MSYS_Commands[n].cmd; ++n) {
01790                 if (_tcsnicmp(MSYS_Commands[n].cmd, OPTION_PREFIX, OPTION_PREFIX_LEN) == 0) {
01791                     _tcsncpy(szCommand, MSYS_Commands[n].cmd + OPTION_PREFIX_LEN, MAX_PATH);
01792                     szCommand[MAX_PATH] = 0;
01793                     args[0] = _T("getopt");
01794                     args[1] = szCommand;
01795                     MSYS_Commands[n].fn(aThread, args);
01796                 }
01797             }
01798 
01799             // output the internal options. We can ignore utf8 because it will be reset
01800             // when the file read finishes. The threads option is read-only.
01801             MSYS_DisplayOption(aThread, _T("console"), GetConsoleType(nSave));
01802 
01803             aThread->mOptionConsole = nSave;
01804             fclose(aThread->mTeeFile);
01805             aThread->mTeeFile = fpSave;
01806             return;
01807         }
01808     }
01809 
01810     if (aArgs.size() < 2) {
01811         aArgs.insert(aArgs.begin(), _T("getopt"));
01812         if (aArgs.size() < 2) {
01813             aArgs.push_back(_T(""));
01814         }
01815 
01816         int nFound = 0;
01817         TCHAR szCommand[MAX_PATH+1];
01818         for (int n = 0; g_rgDispatch[n].cmd; ++n) {
01819             if (_tcsnicmp(g_rgDispatch[n].cmd, OPTION_PREFIX, OPTION_PREFIX_LEN) == 0) {
01820                 if (!cmd.empty() && _tcsicmp(g_rgDispatch[n].cmd, cmd.c_str()) != 0) continue;
01821                 _tcsncpy(szCommand, g_rgDispatch[n].cmd + OPTION_PREFIX_LEN, MAX_PATH);
01822                 szCommand[MAX_PATH] = 0;
01823                 aArgs[1] = szCommand;
01824                 g_rgDispatch[n].fn(aThread, aArgs);
01825                 ++nFound;
01826             }
01827         }
01828 
01829         if (aArgs.size() == 2 && !nFound) {
01830             MSYS_Display(aThread, _T("Invalid arguments. See help.\n"));
01831             return;
01832         }
01833     }
01834     else {
01835         aArgs.insert(aArgs.begin(), _T("setopt"));
01836 
01837         for (int n = 0; g_rgDispatch[n].cmd; ++n) {
01838             if (_tcsicmp(cmd.c_str(), g_rgDispatch[n].cmd) == 0) {
01839                 g_rgDispatch[n].fn(aThread, aArgs);
01840                 return;
01841             }
01842         }
01843         MSYS_Display(aThread, _T("Unrecognized option: %s\n"), opt.c_str());
01844     }
01845 }
01846 
01847 static void OptConsole(MSYS_Thread * thrd, MSYS_Args & aArgs)
01848 {
01849     MSYS_ThreadImpl * aThread = reinterpret_cast<MSYS_ThreadImpl*>(thrd);
01850 
01851     if (MSYS_NeedHelp(aThread, OptConsole, aArgs)) return;
01852 
01853     assert(!aArgs.empty());
01854 
01855     const TCHAR * pAction = aArgs[0];
01856     const TCHAR * pValue  = aArgs.size() > 2 ? aArgs[2] : NULL;
01857 
01858     // setopt
01859     if (*pAction == _T('s')) {
01860         TCHAR cValue = *pValue;
01861         if (cValue >= 'A' && cValue <= 'Z') {
01862             cValue -= 'A' + 'a'; // lowercase
01863         }
01864         switch (cValue) {
01865         case 'a': aThread->mOptionConsole = DISPLAY_ALL;    break;
01866         case 'p': aThread->mOptionConsole = DISPLAY_PROMPT; break;
01867         case 'o': aThread->mOptionConsole = DISPLAY_OUTPUT; break;
01868         case 'n': aThread->mOptionConsole = DISPLAY_NONE;   break;
01869         default:
01870             MSYS_Display(aThread, _T("Invalid arguments. See help.\n"));
01871             return;
01872         }
01873     }
01874 
01875     // getopt
01876     if (*pAction == _T('g')) {
01877         MSYS_DisplayOption(aThread, _T("console"),
01878             GetConsoleType(aThread->mOptionConsole));
01879     }
01880 }
01881 
01882 static void OptThreads(MSYS_Thread * thrd, MSYS_Args & aArgs)
01883 {
01884     MSYS_ThreadImpl * aThread = reinterpret_cast<MSYS_ThreadImpl*>(thrd);
01885 
01886     if (MSYS_NeedHelp(aThread, OptThreads, aArgs)) return;
01887 
01888     assert(!aArgs.empty());
01889 
01890     const TCHAR * pAction = aArgs[0];
01891 
01892     // setopt
01893     if (*pAction == _T('s')) {
01894         MSYS_Display(aThread, _T("This option is read-only and cannot be changed.\n"));
01895         return;
01896     }
01897 
01898     // getopt
01899     if (*pAction == _T('g')) {
01900         const TCHAR * pValue = g_bEnableThreads ? _T("true") : _T("false");
01901         MSYS_DisplayOption(aThread, _T("threads"), pValue);
01902     }
01903 }
01904 
01905 static void OptUtf8(MSYS_Thread * thrd, MSYS_Args & aArgs)
01906 {
01907     MSYS_ThreadImpl * aThread = reinterpret_cast<MSYS_ThreadImpl*>(thrd);
01908 
01909     if (MSYS_NeedHelp(aThread, OptUtf8, aArgs)) return;
01910 
01911     assert(!aArgs.empty());
01912 
01913     const TCHAR * pAction = aArgs[0];
01914     const TCHAR * pValue  = aArgs.size() > 2 ? aArgs[2] : NULL;
01915 
01916     // setopt
01917     if (*pAction == _T('s')) {
01918         aThread->mInputCurr.mIsUtf8 =
01919             pValue[0] == _T('t') || pValue[0] == _T('T') || pValue[0] == _T('1')
01920             || _tcsicmp(pValue, _T("on")) == 0;
01921     }
01922 
01923     // getopt
01924     if (*pAction == _T('g')) {
01925         const TCHAR * pValue = aThread->mInputCurr.mIsUtf8 ? _T("true") : _T("false");
01926         MSYS_DisplayOption(aThread, _T("utf8"), pValue);
01927     }
01928 }
01929 
01930 static void DoProc(MSYS_Thread * thrd, MSYS_Args & aArgs)
01931 {
01932     MSYS_ThreadImpl * aThread = reinterpret_cast<MSYS_ThreadImpl*>(thrd);
01933 
01934     if (MSYS_NeedHelp(aThread, DoProc, aArgs)) return;
01935 
01936     if (aArgs.empty()) {
01937         MSYS_Display(aThread, _T("Invalid arguments. See help.\n"));
01938         return;
01939     }
01940 
01941     if (aThread->mInputCurr.mFile == stdin) {
01942         MSYS_Display(aThread, _T("Invalid during console input.\n"));
01943         return;
01944     }
01945 
01946     const TCHAR * pAction = aArgs[0];
01947     const TCHAR * pName   = aArgs.size() > 0 ? aArgs[1] : NULL;
01948 
01949     // begin (defines a procedure)
01950     if (_tcsicmp(pAction, _T("begin")) == 0) {
01951         long lProc = ftell(aThread->mInputCurr.mFile);
01952         bool bInserted = aThread->mInputCurr.mProc.insert(
01953             ProcMap::value_type(pName, lProc)).second;
01954         if (!bInserted) {
01955             MSYS_Display(aThread, _T("Ignoring duplicate procedure definition: \"%s\".\n"), pName);
01956         }
01957 
01958         // find the end of the procedure by searching for "proc end" in the file
01959         Buffer buf;
01960         TCHAR *pCommand, *pArgs;
01961         for (;;) {
01962             if (!Input(aThread, buf)) {
01963                 MSYS_Display(aThread, _T("End of procedure definition not found: \"%s\".\n"), pName);
01964                 break;
01965             }
01966             Parse(buf.mBuf, pCommand, pArgs);
01967             if (_tcsicmp(pCommand, _T("proc")) == 0 && _tcsicmp(pArgs, _T("end")) == 0) {
01968                 break;
01969             }
01970         }
01971         return;
01972     }
01973 
01974     // end (finishes the current procedure and pops back to the original)
01975     if (_tcsicmp(pAction, _T("end")) == 0) {
01976         if (aThread->mInputCurr.mProcStack.empty()) {
01977             MSYS_Display(aThread, _T("Invalid return from procedure call.\n"));
01978             return;
01979         }
01980 
01981         // if we looped on this procedure then we're done
01982         if (aThread->Loop()) return;
01983 
01984         // otherwise pop the stack and return
01985         long lReturn = aThread->mInputCurr.mProcStack.back();
01986         aThread->mInputCurr.mProcStack.pop_back();
01987         if (fseek(aThread->mInputCurr.mFile, lReturn, SEEK_SET) != 0) {
01988             MSYS_Display(aThread, _T("Failed return from procedure call.\n"));
01989             return;
01990         }
01991         return;
01992     }
01993 
01994     // call a procedure
01995     if (_tcsicmp(pAction, _T("call")) == 0) {
01996         ProcMap::const_iterator i = aThread->mInputCurr.mProc.find(pName);
01997         if (i == aThread->mInputCurr.mProc.end()) {
01998             MSYS_Display(aThread, _T("Unknown procedure call: \"%s\".\n"), pName);
01999             return;
02000         }
02001 
02002         // push our return address
02003         long lCurr = ftell(aThread->mInputCurr.mFile);
02004         aThread->mInputCurr.mProcStack.push_back(lCurr);
02005 
02006         // call the procedure
02007         if (fseek(aThread->mInputCurr.mFile, i->second, SEEK_SET) != 0) {
02008             MSYS_Display(aThread, _T("Failed procedure call: \"%s\".\n"), pName);
02009         }
02010         return;
02011     }
02012 
02013     MSYS_Display(aThread, _T("Invalid command.\n"));
02014 }
02015 
02016 static int ParseTime(const TCHAR * aTimeString, struct tm & aTime)
02017 {
02018     if (3 == _stscanf(aTimeString, _T("%u:%u:%u"),
02019         &aTime.tm_hour, &aTime.tm_min, &aTime.tm_sec))
02020     {
02021         return 3;
02022     }
02023 
02024     aTime.tm_hour = 0;
02025     if (2 == _stscanf(aTimeString, _T("%u:%u"),
02026         &aTime.tm_min, &aTime.tm_sec))
02027     {
02028         return 2;
02029     }
02030 
02031     aTime.tm_min = 0;
02032     if (1 == _stscanf(aTimeString, _T("%u"),
02033         &aTime.tm_sec))
02034     {
02035         return 1;
02036     }
02037 
02038     aTime.tm_sec = 0;
02039     return 0;
02040 }
02041 
02042 static void DoLoop(MSYS_Thread * thrd, MSYS_Args & aArgs)
02043 {
02044     MSYS_ThreadImpl * aThread = reinterpret_cast<MSYS_ThreadImpl*>(thrd);
02045 
02046     if (MSYS_NeedHelp(aThread, DoLoop, aArgs)) return;
02047 
02048     if (aArgs.size() != 3) {
02049         MSYS_Display(aThread, _T("Invalid arguments. See help.\n"));
02050         return;
02051     }
02052 
02053     if (aThread->mInputCurr.mFile == stdin) {
02054         MSYS_Display(aThread, _T("Invalid during console input.\n"));
02055         return;
02056     }
02057 
02058     const TCHAR * pProc  = aArgs[0];
02059     const TCHAR * pType  = aArgs[1];
02060     const TCHAR * pLimit = aArgs[2];
02061 
02062     ProcMap::const_iterator i = aThread->mInputCurr.mProc.find(pProc);
02063     if (i == aThread->mInputCurr.mProc.end()) {
02064         MSYS_Display(aThread, _T("Unknown procedure call: \"%s\".\n"), pProc);
02065         return;
02066     }
02067 
02068     // get our current location
02069     long lProcedure = i->second;
02070 
02071     // count
02072     if (_tcsicmp(_T("count"), pType) == 0) {
02073         int nCount = _ttoi(pLimit);
02074         aThread->LoopStartCounted(lProcedure, nCount);
02075         return;
02076     }
02077 
02078     // for (time period), until (time)
02079     if (_tcsicmp(_T("for"), pType) == 0 || _tcsicmp(_T("until"), pType) == 0) {
02080         time_t tNow, tLimit;
02081         time(&tNow);
02082 
02083         struct tm tmNow = *localtime(&tNow);
02084         int nTimeLen = ParseTime(pLimit, tmNow);
02085 
02086         if (_tcsicmp(_T("for"), pType) == 0) {
02087             // for (period)
02088             tLimit = tNow + tmNow.tm_sec + (tmNow.tm_min * 60) + (tmNow.tm_hour * 60 * 60);
02089         }
02090         else {
02091             // until (time)
02092             if (nTimeLen < 2) {
02093                 MSYS_Display(aThread, _T("Invalid arguments. See help.\n"));
02094                 return;
02095             }
02096             if (nTimeLen == 2) {
02097                 tmNow.tm_hour = tmNow.tm_min;
02098                 tmNow.tm_min  = tmNow.tm_sec;
02099                 tmNow.tm_sec  = 0;
02100             }
02101             tLimit = mktime(&tmNow);
02102 
02103             // if it appears that the time is past, add a day to it
02104             if (tLimit < tNow) {
02105                 tLimit += (24 /*hours*/ * 60 /*mins*/ * 60 /*secs*/);
02106             }
02107         }
02108 
02109         const time_t MAX_PERIOD = (7 * 24 * 60 * 60); // 7 days
02110         if ((tLimit - tNow) > MAX_PERIOD) {
02111             MSYS_Display(aThread, _T("Period too long. See help.\n"));
02112             return;
02113         }
02114 
02115 /*
02116         MSYS_Display(aThread, _T("Looping until %s%d secs (%d h, %d m, %d s)\n"),
02117             asctime(localtime(&tLimit)),
02118             (tLimit - tNow),
02119             (tLimit - tNow) / (60 * 60),
02120             ((tLimit - tNow) / (60)) % 60,
02121             (tLimit - tNow) % (60)
02122             );
02123 */
02124 
02125         // set the time loop parameters
02126         aThread->LoopStartTimed(lProcedure, tLimit);
02127         return;
02128     }
02129 
02130     MSYS_Display(aThread, _T("Invalid loop type.\n"));
02131 }
02132 
02133 static void TimerTell(MSYS_ThreadImpl * aThread, const TCHAR * aTimer, bool aShowRaw)
02134 {
02135     if (aThread->mTimerMap.empty()) {
02136         MSYS_Display(aThread, _T("No timers.\n"));
02137     }
02138     TimerMap::const_iterator iTimer = aThread->mTimerMap.begin();
02139     for (; iTimer != aThread->mTimerMap.end(); ++iTimer) {
02140         if (aTimer && iTimer->first != aTimer) continue;
02141 
02142         const Timer & timer = iTimer->second;
02143         long nElapsed = timer.GetCurrentElapsed();
02144         if (aShowRaw) {
02145             MSYS_Display(aThread, _T("\"timer\",\"%s\"\n"),
02146                 iTimer->first.c_str());
02147             MSYS_Display(aThread, _T("\"current\",%ld.%03ld seconds\n"),
02148                 nElapsed / 1000, nElapsed % 1000);
02149         }
02150         else {
02151             MSYS_Display(aThread, _T("%c%-10s %5ld.%03ld seconds\n"),
02152                 timer.IsRunning() ? '*' : ' ',
02153                 iTimer->first.c_str(), nElapsed / 1000, nElapsed % 1000);
02154         }
02155         if (!timer.Laps().empty()) {
02156             // display individual laps
02157             long nCount = (long) timer.Laps().size();
02158             long nTotal = 0;
02159             LapArray::const_iterator iLap = timer.Laps().begin();
02160             for (int n = 1; iLap != timer.Laps().end(); ++iLap, ++n) {
02161                 nTotal += *iLap;
02162                 if (aShowRaw) {
02163                     MSYS_Display(aThread, _T("%d,%ld.%03ld\n"),
02164                         n, *iLap / 1000, *iLap % 1000);
02165                 }
02166                 else {
02167                     MSYS_Display(aThread, _T("   %3d: %3ld.%03ld"),
02168                         n, *iLap / 1000, *iLap % 1000);
02169                     if (n % 5 == 0) MSYS_Display(aThread, _T("\n"));
02170                 }
02171             }
02172             if (!aShowRaw && (nCount % 5) != 0) {
02173                 MSYS_Display(aThread, _T("\n"));
02174             }
02175 
02176             if (aShowRaw) {
02177                 MSYS_Display(aThread, _T("\n"));
02178             }
02179             else {
02180                 // display actual total and average
02181                 long nAverage = nTotal / nCount;
02182                 MSYS_Display(aThread, _T("   Total: %ld.%03ld   Laps: %ld   Average: %ld.%03ld\n"),
02183                     nTotal / 1000, nTotal % 1000, nCount,
02184                     nAverage / 1000, nAverage % 1000);
02185 
02186                 // generate an array removing 10% outriders from each side
02187                 LapArray elim(timer.Laps());
02188                 std::sort(elim.begin(), elim.end());
02189                 long nOutrider = nCount / 10;
02190                 elim.erase(elim.begin(), elim.begin() + nOutrider);
02191                 elim.erase(elim.end() - nOutrider, elim.end());
02192                 nTotal = 0;
02193                 for (size_t n = 0; n < elim.size(); ++n) {
02194                     nTotal += elim[n];
02195                 }
02196                 nCount -= 2 * nOutrider;
02197                 nAverage = nTotal / nCount;
02198                 MSYS_Display(aThread, _T("   Total: %ld.%03ld   Laps: %ld   Average: %ld.%03ld (outrider adjusted)\n"),
02199                     nTotal / 1000, nTotal % 1000, nCount,
02200                     nAverage / 1000, nAverage % 1000);
02201             }
02202         }
02203     }
02204 }
02205 
02206 static void DoTimer(MSYS_Thread * thrd, MSYS_Args & aArgs)
02207 {
02208     MSYS_ThreadImpl * aThread = reinterpret_cast<MSYS_ThreadImpl*>(thrd);
02209 
02210     if (MSYS_NeedHelp(aThread, DoTimer, aArgs)) return;
02211 
02212     if (aArgs.size() > 2) {
02213         MSYS_Display(aThread, _T("Invalid arguments. See help.\n"));
02214         return;
02215     }
02216 
02217     const TCHAR * pAction = (aArgs.size() > 0) ? aArgs[0] : _T("tell");
02218     const TCHAR * pId     = (aArgs.size() > 1) ? aArgs[1] : NULL;
02219 
02220     // tell
02221     if (_tcsicmp(_T("tell"), pAction) == 0 || _tcsicmp(_T("tellraw"), pAction) == 0) {
02222         bool bIsRaw = (pAction[4] != 0);
02223         TimerTell(aThread, pId, bIsRaw);
02224         return;
02225     }
02226 
02227     if (!pId) {
02228         MSYS_Display(aThread, _T("Invalid arguments. See help.\n"));
02229         return;
02230     }
02231 
02232     // lookup this timer
02233     TimerMap::iterator iTimer = aThread->mTimerMap.find(pId);
02234 
02235     // start
02236     if (_tcsicmp(_T("start"), pAction) == 0) {
02237         // we only create a timer on "start"
02238         if (iTimer == aThread->mTimerMap.end()) {
02239             iTimer = aThread->mTimerMap.insert(
02240                 TimerMap::value_type(pId, Timer())).first;
02241         }
02242         iTimer->second.Start();
02243         return;
02244     }
02245 
02246     // delete
02247     if (_tcsicmp(_T("delete"), pAction) == 0) {
02248         if (iTimer != aThread->mTimerMap.end()) {
02249             aThread->mTimerMap.erase(iTimer);
02250         }
02251         return;
02252     }
02253 
02254     if (iTimer == aThread->mTimerMap.end()) {
02255         MSYS_Display(aThread, _T("Invalid timer.\n"));
02256         return;
02257     }
02258 
02259     // stop
02260     if (_tcsicmp(_T("stop"), pAction) == 0) {
02261         iTimer->second.Stop();
02262         return;
02263     }
02264 
02265     // lap
02266     if (_tcsicmp(_T("lap"), pAction) == 0) {
02267         iTimer->second.Lap();
02268         return;
02269     }
02270 
02271     // reset
02272     if (_tcsicmp(_T("reset"), pAction) == 0) {
02273         iTimer->second.Reset();
02274         return;
02275     }
02276 
02277     MSYS_Display(aThread, _T("Invalid command.\n"));
02278 }
02279 
02280 static bool
02281 Dispatch(
02282     MSYS_ThreadImpl *   aThread,
02283     TCHAR *             aCommand,
02284     TCHAR *             aArgs,
02285     PARSE               aParse
02286     )
02287 {
02288     MSYS_Args args;
02289 
02290     // special handling for shell
02291     if (*aCommand == '!' || !_tcsicmp(aCommand, _T("shell"))) {
02292         if (*aCommand == '!') {
02293             ++aCommand; // may be a prefixed entry
02294         }
02295         else {
02296             aCommand = _T("");
02297         }
02298         args.push_back(aCommand);
02299         args.push_back(aArgs);
02300         DoShell(aThread, args);
02301         return true;
02302     }
02303 
02304     if (*aCommand != _T('_')) {
02305         for (int n = 0; g_rgDispatch[n].cmd; ++n) {
02306             if (_tcsicmp(aCommand, g_rgDispatch[n].cmd) == 0) {
02307                 bool bFullParse = (aParse != PARSE_OFF) &&
02308                     (g_rgDispatch[n].unquote || (aParse == PARSE_ON));
02309                 ParseArgs(aArgs, bFullParse, args);
02310                 aThread->SetCurrCommand(g_rgDispatch[n].cmd);
02311                 g_rgDispatch[n].fn(aThread, args);
02312                 aThread->SetCurrCommand(NULL);
02313                 return (g_rgDispatch[n].fn != DoExit);
02314             }
02315         }
02316     }
02317     MSYS_Display(aThread, _T("Unrecognized command: %s\n"), aCommand);
02318     return true;
02319 }
02320 
02321 static void InitInternal()
02322 {
02323     // count the total number of commands
02324     int nInternal = 0, nExternal = 0;
02325     for (int n = 0; g_rgInternal[n].cmd; ++n) ++nInternal;
02326     for (int n = 0; MSYS_Commands[n].cmd; ++n) {
02327         const MSYS_DispatchEntry * pItem = std::find(
02328             &g_rgInternal[0], &g_rgInternal[nInternal], MSYS_Commands[n]);
02329         if (pItem->cmd != MSYS_HEADER && pItem != &g_rgInternal[nInternal]) {
02330             _ftprintf(stderr,
02331                 _T("Command '%s' is shadowed by internal command of the same name\n"),
02332                 pItem->cmd);
02333             assert(!"Command shadowed by internal command.");
02334         }
02335         ++nExternal;
02336     }
02337 
02338     // allocate our combined table
02339     g_rgDispatch = (MSYS_DispatchEntry*)
02340         calloc(sizeof(MSYS_DispatchEntry), nInternal+nExternal+1);
02341     if (!g_rgDispatch) {
02342         _ftprintf(stderr, _T("Out of memory.\n"));
02343         exit(1);
02344     }
02345 
02346     // create our combined table
02347     for (int n = 0; n < nInternal; ++n) {
02348         g_rgDispatch[n] = g_rgInternal[n];
02349     }
02350     for (int n = 0; n < nExternal; ++n) {
02351         g_rgDispatch[nInternal + n] = MSYS_Commands[n];
02352     }
02353 }
02354 
02355 static bool
02356 FileExists(
02357     const TCHAR * aFilePath
02358     )
02359 {
02360 #ifdef _WIN32
02361     DWORD dwAttribs = GetFileAttributes(aFilePath);
02362     if (dwAttribs == INVALID_FILE_ATTRIBUTES) {
02363         return false;
02364     }
02365     if (dwAttribs & FILE_ATTRIBUTE_DIRECTORY) {
02366         return false;
02367     }
02368     return true;
02369 #else
02370     struct stat st;
02371     if (-1 == stat(aFilePath, &st)) {
02372         return false;
02373     }
02374     if (st.st_mode & S_IFDIR) {
02375         return false;
02376     }
02377     return true;
02378 #endif
02379 }
02380 
02381 #ifdef _WIN32
02382 void
02383 InputFromConsole(
02384     Buffer & aBuf
02385     )
02386 {
02387     // we can't use simple pipes because using ReadFile on the console doesn't
02388     // work to convert the input characters to Unicode.
02389 
02390     DWORD dwChars = 0;
02391     ReadConsole(GetStdHandle(STD_INPUT_HANDLE),
02392         aBuf.mBuf, (DWORD) aBuf.mBufSiz, &dwChars, NULL);
02393     aBuf.mBuf[dwChars] = L'\0';
02394 }
02395 #endif
02396 
02397 void
02398 OutputToConsole(
02399     const TCHAR * aString
02400     )
02401 {
02402     AutoLock lock(g_lockOutputConsole);
02403 
02404 #ifdef _WIN32
02405     // we can't use simple pipes because using WriteFile on the console doesn't work
02406     // to convert the Unicode characters correctly.
02407     DWORD dwWritten;
02408     const DWORD CHUNK = 10000;
02409     DWORD nLength = (DWORD) _tcslen(aString);
02410     for (DWORD nOffset = 0; nOffset < nLength; nOffset += CHUNK) {
02411         DWORD nOutputLen = nLength - nOffset > CHUNK ? CHUNK : nLength - nOffset;
02412         static HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
02413         WriteConsole(hStdOut, aString + nOffset, nOutputLen, &dwWritten, NULL);
02414     }
02415 #else
02416     _ftprintf(stdout, "%s", aString);
02417 #endif
02418 }
02419 
02420 #ifdef _WIN32
02421 extern void
02422 MSYS_DisplayWindowsError(
02423     MSYS_Thread *   aThread,
02424     const TCHAR *   aLocation,
02425     HRESULT         aError
02426     )
02427 {
02428     DWORD dwFormatFlags =
02429         FORMAT_MESSAGE_IGNORE_INSERTS |
02430         FORMAT_MESSAGE_FROM_SYSTEM;
02431 
02432     TCHAR szMessage[1000];
02433     DWORD dwResult = FormatMessage(
02434         dwFormatFlags,
02435         NULL,
02436         aError,
02437         MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL),
02438         szMessage,
02439         sizeof(szMessage)/sizeof(szMessage[0]),
02440         NULL );
02441 
02442     MSYS_Display(aThread, _T("%s failed with hr = 0x%08x, %s"),
02443         aLocation, aError,
02444         dwResult ? szMessage : _T("<failed to get error string>") );
02445 }
02446 #endif
02447 
02448 const MSYS_DispatchEntry g_rgInternal[] = {
02449     // general
02450     { MSYS_HEADER, NULL, false, _T("GENERAL"), NULL },
02451     { _T("?"),      DoHelp,     false,  NULL, NULL },
02452     { _T("help"),   DoHelp,     false,  _T("Display command help. Use \"help /?\" for more details."),
02453         _T("Usage: help [<command> | /internal | /all]\n")
02454         _T("\n")
02455         _T("    <command>   Show detailed help for a specific command. By default only\n")
02456         _T("                summary help is shown for commands. Use \"help <command>\" or\n")
02457         _T("                \"<command> -?\" for full help for that command.\n")
02458         _T("    /internal   Show help for internal commands. By default only external\n")
02459         _T("                commands are shown in the command list.\n")
02460         _T("    /all        Show help for both internal and external commands.\n")
02461         _T("\n")
02462         _T("Note: the interpretation of an -? argument as a request for help can be disabled ")
02463         _T("so that it is passed through to the command by prefixing the command with the ")
02464         _T("'-' character.")
02465     },
02466 
02467     { _T("x"),      DoExit,     false,  NULL, NULL },
02468     { _T("exit"),   DoExit,     false,  _T("Exit the application."),
02469         _T("By default the exit command will only exit the currently executing thread. ")
02470         _T("If the current thread is the main thread, it will wait for all other threads ")
02471         _T("to exit before returning. To force an immediate exit from all threads, use ")
02472         _T("the command \"exit now\". This may be called from any executing thread.\n")
02473         _T("\n")
02474         _T("Usage: exit [now]")
02475     },
02476     { _T("setopt"), DoSetOpt,   false,  _T("Set or display options."),
02477         _T("Usage: setopt [<option>] [<value>]\n")
02478         _T("\n")
02479         _T("If no option or value to set is supplied the current values will be displayed. ")
02480         _T("Use \"setopt <option> -?\" for help on available options.\n")
02481         _T("\n")
02482         _T("Usage: setopt {-save|-load} [<file>]\n")
02483         _T("\n")
02484         _T("Save or load options with an options file. If no file name is supplied ")
02485         _T("then \"options.txt\" will be used by default.")
02486     },
02487 
02488     // file system
02489     { MSYS_HEADER, NULL, false, _T("FILE SYSTEM"), NULL },
02490     { _T("pwd"),    DoPwd,      false,  _T("Display the current working directory."),
02491         NULL
02492     },
02493     { _T("cd"),     DoChdir,    false,  _T("Change the current working directory."),
02494         _T("Usage: cd <path>")
02495     },
02496     { _T("!"),      DoShell,    false,  NULL, NULL },
02497     { _T("shell"),  DoShell,    false,  _T("Execute a shell command."),
02498         _T("If no command is supplied then a shell will be started. All characters are passed to the shell ")
02499         _T("unchanged (no quoted characters are unquoted).\n")
02500         _T("\n")
02501         _T("Usage: shell [<command> [<arguments>]]")
02502     },
02503 
02504     // input and output
02505     { MSYS_HEADER, NULL, false, _T("INPUT AND OUTPUT"), NULL },
02506     { _T("e"),      DoEcho,     true,   NULL, NULL },
02507     { _T("echo"),   DoEcho,     true,   _T("Echo all arguments."),
02508         _T("Each argument is echoed with all quoted characters (e.g. \\t \\n \\\" etc) converted. ")
02509         _T("Arguments are echoed immediately following each other, so to echo a full line of text, ")
02510         _T("start the line with a quotation mark. For example:\n")
02511         _T("\n")
02512         _T("echo \"This full line will be echoed.")
02513     },
02514     { _T("r"),      DoRead,     false,  NULL, NULL },
02515     { _T("read"),   DoRead,     false,  _T("Read and execute a script file."),
02516         _T("Usage: read <path>")
02517     },
02518     { _T("tee"),    DoTee,      false,  _T("Copy all output to a file."),
02519         _T("When a tee has been created, all output that is sent to the screen is also written ")
02520         _T("to the output file. The tee file is created by overwriting any existing file by ")
02521         _T("default. Specify \"-append\" to force data to be appended to the tee file. ")
02522         _T("Stop output from being written to the tee file by omitting the file path.")
02523         _T("Shell command output cannot be redirected with a tee.\n")
02524         _T("\n")
02525         _T("Usage: tee [-append] [<path>]")
02526     },
02527 
02528     // scripting
02529     { MSYS_HEADER, NULL, false, _T("SCRIPTING"), NULL },
02530     { _T("timer"),  DoTimer,    false,  _T("Start, stop and display timers."),
02531         _T("If no arguments are given, all timers are reported.\n")
02532         _T("\n")
02533         _T("Usage: timer [<command> <id>]\n")
02534         _T("\n")
02535         _T("  start    Create a new timer running or restart an existing one.\n")
02536         _T("  stop     Stop an existing timer from running. The timer is not deleted.\n")
02537         _T("  lap      Add the current time as a lap. The run state is not affected.\n")
02538         _T("  reset    Reset the current elapsed time and all lap details of the timer.\n")
02539         _T("  tell     Display the current timer details.\n")
02540         _T("  tellraw  Display the current timer details in raw format.\n")
02541         _T("  delete   Delete an existing timer.")
02542     },
02543     { _T("sleep"),  DoSleep,    false,  _T("Pause script execution for a period of time."),
02544         _T("Usage: sleep <milliseconds>")
02545     },
02546     { _T("proc"),   DoProc,     false,  _T("Start, end or call a procedure."),
02547         _T("This may only be used when reading from a file. The procedure name must not be ")
02548         _T("specified for \"proc end\".\n")
02549         _T("\n")
02550         _T("Usage: proc {begin|end|call} [<name>]")
02551     },
02552     { _T("loop"),   DoLoop,     false,  _T("Repetitively call a procedure."),
02553         _T("The number of times the procedure will be called depends on the type of ")
02554         _T("limit that is created. A procedure name always needs to be supplied. The ")
02555         _T("<limit> text depends on the <type> that is specified. When using \"until\", ")
02556         _T("the time is specified using the 24 hour clock. Times in the past are assumed ")
02557         _T("to be 24 hours in the future. When using \"for\", the maximum period that can ")
02558         _T("be specified is 7 days.\n")
02559         _T("\n")
02560         _T("Usage: loop <proc-name> <type> <limit>\n")
02561         _T("\n")
02562         _T("  count <iterations>    Loop a fixed number of times.\n")
02563         _T("  until HH:MM[:SS]      Loop until the specified time (24 hour time).\n")
02564         _T("  for [[HH:]MM:]SS      Loop for the specified period of time.")
02565     },
02566 #ifndef MSYS_NOTHREADS
02567     { _T("threads"), DoThreads, false,  _T("Manage execution threads."),
02568         _T("Threads must always execute on an input file. By default, a new thread will start ")
02569         _T("with console display set to \"off\". If threads are not supported by the application ")
02570         _T("then this command will return an error. If no name is supplied for a new thread then ")
02571         _T("a name will be generated from the thread ID (e.g. threads start \"\" file.txt).\n")
02572         _T("\n")
02573         _T("Usage: threads <command> [<name> [<path>]]\n")
02574         _T("\n")
02575         _T("  start <name> <path>  Create a new thread with <name> using file <path>.\n")
02576         _T("  stop <name>          Stop the thread with <name> immediately.\n")
02577         _T("  stopall              Stop all threads immediately.\n")
02578         _T("  join <name>          Join with the thread with <name>.\n")
02579         _T("  joinall              Join with all threads.\n")
02580         _T("  list                 List all threads.")
02581     },
02582 #endif // MSYS_NOTHREADS
02583 
02584     // options
02585     { _T("_setopt_console"), OptConsole, false, _T("Enable or disable console display."),
02586         _T("The display of command output and the echoing of the command prompt can be ")
02587         _T("selectively disabled. For example, if input is from a file and is being tee'd ")
02588         _T("to an output file, all console output can be disabled so that only the tee file ")
02589         _T("receives the output. Note that disabling command prompt echo only affects the ")
02590         _T("echoing of command prompt during file input. Standard console input will always ")
02591         _T("display the command prompt. Shell output cannot be disabled (or tee'd).\n")
02592         _T("\n")
02593         _T("Usage: setopt console {all|prompt|output|none}\n")
02594         _T("\n")
02595         _T("  all     Command prompt: echoed. Command output: echoed.\n")
02596         _T("  prompt  Command prompt: echoed. Command output: none.\n")
02597         _T("  output  Command prompt: none.   Command output: echoed.\n")
02598         _T("  none    Command prompt: none.   Command output: none.\n")
02599     },
02600     { _T("_setopt_threads"), OptThreads, false, _T("Are threads enabled (read-only)."),
02601         NULL
02602     },
02603     { _T("_setopt_utf8"), OptUtf8, false, _T("Force file input to use UTF-8 encoding."),
02604         _T("Usage: setopt utf8 {true|false}")
02605     },
02606 
02607     { NULL, NULL, false, NULL, NULL }
02608 };
02609 
02610 extern void MSYS_ShowHelp(MSYS_Thread * thrd, MSYS_Handler fn)
02611 {
02612     MSYS_ThreadImpl * aThread = reinterpret_cast<MSYS_ThreadImpl*>(thrd);
02613 
02614     bool bSaved = aThread->mStringReplacements;
02615     aThread->mStringReplacements = false;
02616 
02617     std::tstring sCommands;
02618 
02619     bool bDisplayInternal = false;
02620     const MSYS_DispatchEntry * rgDispatch = MSYS_Commands;
02621     if (fn == HelpAll) {
02622         rgDispatch = g_rgDispatch;
02623         fn = NULL;
02624         bDisplayInternal = true;
02625     }
02626     else if (fn == HelpInternal) {
02627         rgDispatch = g_rgInternal;
02628         fn = NULL;
02629         bDisplayInternal = true;
02630     }
02631     else if (fn == HelpExternal) {
02632         fn = NULL;
02633     }
02634 
02635     // general help
02636     if (!fn) {
02637         bool bIsFirst = true;
02638 
02639         // we always display the general group of the internal commands
02640         int nSwitchHeader = 0;
02641         if (!bDisplayInternal) {
02642             rgDispatch = g_rgInternal;
02643             nSwitchHeader = 2;
02644         }
02645 
02646         for (int n = 0; rgDispatch[n].cmd; ++n) {
02647             if (rgDispatch[n].cmd[0] == _T('_')) continue;
02648             if (rgDispatch[n].cmd == MSYS_HEADER) {
02649                 if (--nSwitchHeader == 0) {
02650                     assert(!bDisplayInternal && rgDispatch == g_rgInternal);
02651                     rgDispatch = MSYS_Commands;
02652                     n = 0;
02653                     continue;
02654                 }
02655 
02656                 MSYS_Display(aThread, _T("%s%s\n"),
02657                     bIsFirst ? _T("") : _T("\n"),
02658                     rgDispatch[n].oneHelp);
02659                 bIsFirst = false;
02660                 continue;
02661             }
02662 
02663             if (!sCommands.empty()) {
02664                 sCommands += _T(", ");
02665             }
02666             sCommands += rgDispatch[n].cmd;
02667             if (!rgDispatch[n].oneHelp) continue;
02668 
02669             MSYS_Display(aThread, _T("%-15s%s\n"),
02670                 sCommands.c_str(), rgDispatch[n].oneHelp);
02671             sCommands.erase();
02672             bIsFirst = false;
02673         }
02674 
02675         if (bDisplayInternal) {
02676             MSYS_Display(aThread, _T("%s"),
02677                 _T("\n")
02678                 _T("Special line prefixes to change default behaviour are:\n")
02679                 _T("  ;   Single line comment.\n")
02680                 _T("  #   Single line comment.\n")
02681                 _T("  @   Suppress the echoing of the prompt and command.\n")
02682                 _T("  $   Report the execution time of the command.\n")
02683                 _T("  -   Force off the parsing of special characters.\n")
02684                 _T("  +   Force on the parsing of special characters.\n")
02685                 _T("  =   Prevent \"-?\" or \"/?\" from being interpreted as a help request.\n")
02686                 _T("  !   Execute the command as a shell command.\n")
02687                 _T("  /*  Start multiple line comment (must be the only text on a line).\n")
02688                 _T("  */  End multiple line comment (must be the only text on a line).\n")
02689                 _T("\n")
02690                 _T("Special variables:\n")
02691                 _T("  {{NOW}}       Replaced with the current time and date\n")
02692                 );
02693             if (g_bEnableThreads) {
02694             MSYS_Display(aThread, _T("%s"),
02695                 _T("  {{TID}}       Replaced with the current thread ID\n")
02696                 _T("  {{TNAME}}     Replaced with the current thread name\n")
02697                 );
02698             }
02699         }
02700     }
02701     else {
02702         // specific command help
02703         for (int n = 0; g_rgDispatch[n].cmd; ++n) {
02704             if (g_rgDispatch[n].fn != fn) continue;
02705             if (!sCommands.empty()) {
02706                 sCommands += _T(", ");
02707             }
02708             if (0 == _tcsnicmp(g_rgDispatch[n].cmd, OPTION_PREFIX, OPTION_PREFIX_LEN)) {
02709                 sCommands += _T("setopt ");
02710                 sCommands += (g_rgDispatch[n].cmd + OPTION_PREFIX_LEN);
02711             }
02712             else {
02713                 sCommands += g_rgDispatch[n].cmd;
02714             }
02715             if (!g_rgDispatch[n].oneHelp) continue;
02716 
02717             MSYS_Display(aThread, _T("Commands:  %s\nSummary:   %s\n"),
02718                 sCommands.c_str(), g_rgDispatch[n].oneHelp);
02719             if (g_rgDispatch[n].longHelp) {
02720                 MSYS_Display(aThread, _T("\n"));
02721                 MSYS_DisplayWrapped(aThread, g_rgDispatch[n].longHelp);
02722             }
02723             break;
02724         }
02725     }
02726 
02727     aThread->mStringReplacements = bSaved;
02728 }
02729 

Generated on Wed Jun 20 09:28:35 2007 for menusys by  doxygen 1.5.2