You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

2129 lines
73 KiB

  1. unit uutlConsoleHelper;
  2. { Package: Utils
  3. Prefix: utl - UTiLs
  4. Beschreibung: diese Unit implementiert Helper Klassen für Consolen Ein- und Ausgaben,
  5. sowie Menüführung und Autovervollständigung }
  6. {$mode objfpc}{$H+}
  7. interface
  8. uses
  9. Classes, SysUtils, fgl, uutlMCF, uutlCommon, uutlGenerics, syncobjs;
  10. type
  11. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  12. TutlParameterStringFlag = (psfBrackets);
  13. TutlParameterStringFlags = set of TutlParameterStringFlag;
  14. TutlMenuItem = class;
  15. TutlMenuParameter = class
  16. private
  17. fParent: TutlMenuItem;
  18. fOptional: Boolean;
  19. fName, fDescription, fValue: String;
  20. public
  21. property Parent: TutlMenuItem read fParent;
  22. property Optional: Boolean read fOptional;
  23. property Value: String read fValue;
  24. property Name: String read fName;
  25. property Description: String read fDescription;
  26. procedure WriteConfig(const aMCF: TutlMCFSection); virtual;
  27. procedure ReadConfig(const aMCF: TutlMCFSection); virtual;
  28. procedure GetAutoCompleteStrings(const aStrings: TStrings); virtual; abstract;
  29. function SetValue(const aValue: String): Boolean; virtual;
  30. function GetString(const aOptions: TutlParameterStringFlags = [psfBrackets]): String; virtual;
  31. constructor Create(const aOptional: Boolean; const aName, aDescription: String);
  32. destructor Destroy; override;
  33. end;
  34. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  35. TutlMenuParameterListBase = specialize TFPGObjectList<TutlMenuParameter>;
  36. TutlMenuParameterList = class(TutlMenuParameterListBase)
  37. public
  38. function HasParameter(const aName: String): Boolean;
  39. function FindParameter(const aName: String): TutlMenuParameter;
  40. end;
  41. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  42. TutlMenuParameterStack = class(TutlMenuParameterList)
  43. public
  44. procedure Push(const aValue: TutlMenuParameter);
  45. function Seek: TutlMenuParameter;
  46. function Pop: TutlMenuParameter;
  47. end;
  48. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  49. TutlParameterType = (ptString = 0, ptInteger, ptBoolean, ptHex, ptPreset);
  50. TutlParameterTypeH = specialize TutlEnumHelper<TutlParameterType>;
  51. TutlMenuParameterSingle = class(TutlMenuParameter)
  52. private
  53. fType: TutlParameterType;
  54. public
  55. property ParamType: TutlParameterType read fType;
  56. procedure WriteConfig(const aMCF: TutlMCFSection); override;
  57. procedure ReadConfig(const aMCF: TutlMCFSection); override;
  58. procedure GetAutoCompleteStrings(const aStrings: TStrings); override;
  59. function SetValue(const aValue: String): Boolean; override;
  60. function GetString(const aOptions: TutlParameterStringFlags = [psfBrackets]): String; override;
  61. constructor Create(const aOptional: Boolean; const aName, aDescription: String; const aType: TutlParameterType);
  62. destructor Destroy; override;
  63. end;
  64. TutlMenuParameterSingleList = specialize TFPGObjectList<TutlMenuParameterSingle>;
  65. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  66. TutlMenuParameterGroup = class(TutlMenuParameter)
  67. private
  68. fParameters: TutlMenuParameterSingleList;
  69. function GetCount: Integer;
  70. function GetParameter(const aIndex: Integer): TutlMenuParameterSingle;
  71. public
  72. property Count: Integer read GetCount;
  73. property Parameter[const aIndex: Integer]: TutlMenuParameterSingle read GetParameter; default;
  74. procedure WriteConfig(const aMCF: TutlMCFSection); override;
  75. procedure ReadConfig(const aMCF: TutlMCFSection); override;
  76. procedure GetAutoCompleteStrings(const aStrings: TStrings); override;
  77. function SetValue(const aValue: String): Boolean; override;
  78. function GetString(const aOptions: TutlParameterStringFlags = [psfBrackets]): String; override;
  79. function AddParameter(const aName, aDescription: String;
  80. const aType: TutlParameterType): TutlMenuParameterSingle; overload;
  81. function AddParameter(const aParameter: TutlMenuParameterSingle): TutlMenuParameterSingle; overload;
  82. procedure DelParameter(const aIndex: Integer);
  83. constructor Create(const aOptional: Boolean; const aName, aDescription: String);
  84. destructor Destroy; override;
  85. end;
  86. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  87. TutlCallback = procedure(aSender: TObject) of object;
  88. TutlMenuItemList = specialize TFPGObjectList<TutlMenuItem>;
  89. TutlMenuItem = class(TObject)
  90. private
  91. fCommand: String;
  92. fDescription: String;
  93. fCallback: TutlCallback;
  94. fExecutable: Boolean;
  95. fParent: TutlMenuItem;
  96. fHelpItem: TutlMenuItem;
  97. function GetCount: Integer;
  98. function GetItems(const aIndex: Integer): TutlMenuItem;
  99. function GetMenuPath: String;
  100. function GetParamCount: Integer;
  101. function GetParameters(const aIndex: Integer): TutlMenuParameter;
  102. function GetParameterString: String;
  103. procedure SetCallback(aValue: TutlCallback);
  104. protected
  105. fItems: TutlMenuItemList;
  106. fParameters: TutlMenuParameterList;
  107. procedure WriteConfig(const aMCF: TutlMCFSection); virtual;
  108. procedure ReadConfig(const aMCF: TutlMCFSection); virtual;
  109. public
  110. property Command: String read fCommand write fCommand;
  111. property Description: String read fDescription write fDescription;
  112. property Callback: TutlCallback read fCallback write SetCallback;
  113. property Executable: Boolean read fExecutable;
  114. property MenuPath: String read GetMenuPath;
  115. property ParameterString: String read GetParameterString;
  116. property ParamCount: Integer read GetParamCount;
  117. property Count: Integer read GetCount;
  118. property Parent: TutlMenuItem read fParent;
  119. property Items[const aIndex: Integer]: TutlMenuItem read GetItems; default;
  120. property Parameters[const aIndex: Integer]: TutlMenuParameter read GetParameters;
  121. procedure GetAutoCompleteStrings(const aList: TStrings; const aParameter: TutlMenuParameterList); virtual;
  122. function GetString: String;
  123. function AddItem(const aCmd, aDesc: String; const aCallback: TutlCallback): TutlMenuItem; overload;
  124. function AddItem(const aItem: TutlMenuItem): TutlMenuItem; overload;
  125. procedure DelItem(const aIndex: Integer);
  126. function AddParameter(const aParameter: TutlMenuParameter): TutlMenuParameter;
  127. procedure DelParameter(const aIndex: Integer);
  128. procedure LoadFromStream(const aStream: TStream);
  129. procedure SaveToStream(const aStream: TStream);
  130. constructor Create(const aParent: TutlMenuItem; const aCmd, aDesc: String; const aCallback: TutlCallback);
  131. destructor Destroy; override;
  132. end;
  133. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  134. TutlParseResult = (prUnknownCommand, prInvalidParam, prInvalidParamCount, prSuccess, prIncompleteCmd);
  135. TutlHelpOption = (hoNone, hoAll, hoDetail);
  136. TutlCommandMenu = class(TutlMenuItem)
  137. private
  138. fInvalidParam: String;
  139. fUnknownCmd: String;
  140. fLastCmd: String;
  141. fCurrentParamCount: Integer;
  142. function GetCmdParameter: TutlMenuParameterList;
  143. protected
  144. fCmdParameter: TutlMenuParameterStack;
  145. fCmdStack: TutlStringStack;
  146. fCurrentMenu: TutlMenuItem;
  147. procedure SplitCmdString(const aText: String; const aChar: Char);
  148. function ParseCommand: TutlParseResult;
  149. public
  150. property CmdParameter: TutlMenuParameterList read GetCmdParameter;
  151. property LastCmd: String read fLastCmd;
  152. procedure ExecuteCommand(const aCmd: String); virtual;
  153. procedure DisplayHelp(const aRefMenu: TutlMenuItem = nil; const aOption: TutlHelpOption = hoNone);
  154. procedure DisplayIncompleteCommand;
  155. procedure DisplayUnknownCommand(const aCmd: String = '');
  156. procedure DisplayInvalidParamCount(const aParamCount: Integer = -1);
  157. procedure DisplayInvalidParam(const aParam: String = '');
  158. constructor Create(const aHelp: String);
  159. destructor Destroy; override;
  160. end;
  161. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  162. TutlInputEvent = procedure(aSender: TObject; const aInput: String) of Object;
  163. TutlAutoCompleteEvent = function(aSender: TObject; const aInput: String; const aDisplayPossibilities: Boolean): String of Object;
  164. TutlCommandPrompt = class(TObject)
  165. private
  166. fCurrent: String;
  167. fInput: String;
  168. fPrefix: String;
  169. fHistoryBackup: String;
  170. fCurID: Integer;
  171. fStartIndex: Integer;
  172. fHistoryID: Integer;
  173. fHistoryEnabled: Boolean;
  174. fRunning: Boolean;
  175. fHiddenChar: Char;
  176. fConsoleCS: TCriticalSection;
  177. fHistory: TStringList;
  178. fOnInput: TutlInputEvent;
  179. fOnAutoComplete: TutlAutoCompleteEvent;
  180. function GetCurrent: String;
  181. procedure SetCurrent(aValue: String);
  182. procedure SetHiddenChar(aValue: Char);
  183. procedure SetPrefix(aValue: String);
  184. procedure CursorToStart;
  185. procedure CursorToEnd;
  186. procedure DelInput(const aAll: Boolean = false);
  187. procedure RestoreInput;
  188. procedure CursorRight;
  189. procedure CursorLeft;
  190. procedure DelChar(const aBeforeCursor: Boolean = false);
  191. procedure WriteChar(const c: Char);
  192. function ReadLnEx: String;
  193. function AutoComplete(const aInput: String; const aDisplayPossibilities: Boolean): String;
  194. procedure AddHistory(const aInput: String);
  195. procedure DoInput;
  196. public
  197. property Prefix: String read fPrefix write SetPrefix;
  198. property Current: String read GetCurrent write SetCurrent;
  199. property HistoryEnabled: Boolean read fHistoryEnabled write fHistoryEnabled;
  200. property HiddenChar: Char read fHiddenChar write SetHiddenChar;
  201. property OnInput: TutlInputEvent read fOnInput write fOnInput;
  202. property OnAutoComplete: TutlAutoCompleteEvent read fOnAutoComplete write fOnAutoComplete;
  203. procedure Start;
  204. procedure Stop;
  205. procedure Reset;
  206. procedure Clear; //löscht nur die Ausgabe und hält die Eingabe intern
  207. procedure Restore; //stellt die Ausgabe wieder her
  208. constructor Create(const aConsoleCS: TCriticalSection);
  209. destructor Destroy; override;
  210. end;
  211. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  212. TutlConsoleMenu = class(TutlCommandMenu)
  213. private
  214. fExitMenu: TutlMenuItem;
  215. fCommandPrompt: TutlCommandPrompt;
  216. fIsAsking: Boolean;
  217. fInputBackup: String;
  218. fOnAnswer: TutlInputEvent;
  219. procedure CommandInput(aSender: TObject; const aCmd: String);
  220. function AutoComplete(aSender: TObject; const aCmd: String;
  221. const aDisplayPossibilities: Boolean): String;
  222. procedure DoAnswer(const aInput: String);
  223. public
  224. property CommandPrompt: TutlCommandPrompt read fCommandPrompt;
  225. property OnAnswer: TutlInputEvent read fOnAnswer;
  226. procedure ExecuteCommand(const aCmd: String); override;
  227. procedure StartMenu;
  228. procedure ExitMenu;
  229. procedure Ask(const aQuestion: String; const aHidden: Boolean = false; const aOnAnswer: TutlInputEvent = nil);
  230. constructor Create(const aHelp: String; const aConsoleCS: TCriticalSection);
  231. destructor Destroy; override;
  232. end;
  233. implementation
  234. uses
  235. strutils,
  236. uutlLogger,
  237. uutlKeyCodes,
  238. {$IFDEF WINDOWS}
  239. windows
  240. {$ELSE}
  241. crt
  242. {$ENDIF};
  243. const
  244. COMMAND_PROMPT_PREFIX = '> ';
  245. {$IFDEF WINDOWS}
  246. var
  247. ScanCode : char;
  248. SpecialKey : boolean;
  249. DoingNumChars: Boolean;
  250. DoingNumCode: Byte;
  251. Function RemapScanCode (ScanCode: byte; CtrlKeyState: byte; keycode:longint): byte;
  252. { Several remappings of scancodes are necessary to comply with what
  253. we get with MSDOS. Special Windows keys, as Alt-Tab, Ctrl-Esc etc.
  254. are excluded }
  255. var
  256. AltKey, CtrlKey, ShiftKey: boolean;
  257. const
  258. {
  259. Keypad key scancodes:
  260. Ctrl Norm
  261. $77 $47 - Home
  262. $8D $48 - Up arrow
  263. $84 $49 - PgUp
  264. $8E $4A - -
  265. $73 $4B - Left Arrow
  266. $8F $4C - 5
  267. $74 $4D - Right arrow
  268. $4E $4E - +
  269. $75 $4F - End
  270. $91 $50 - Down arrow
  271. $76 $51 - PgDn
  272. $92 $52 - Ins
  273. $93 $53 - Del
  274. }
  275. CtrlKeypadKeys: array[$47..$53] of byte =
  276. ($77, $8D, $84, $8E, $73, $8F, $74, $4E, $75, $91, $76, $92, $93);
  277. begin
  278. AltKey := ((CtrlKeyState AND
  279. (RIGHT_ALT_PRESSED OR LEFT_ALT_PRESSED)) > 0);
  280. CtrlKey := ((CtrlKeyState AND
  281. (RIGHT_CTRL_PRESSED OR LEFT_CTRL_PRESSED)) > 0);
  282. ShiftKey := ((CtrlKeyState AND SHIFT_PRESSED) > 0);
  283. if AltKey then
  284. begin
  285. case ScanCode of
  286. // Digits, -, =
  287. $02..$0D: inc(ScanCode, $76);
  288. // Function keys
  289. $3B..$44: inc(Scancode, $2D);
  290. $57..$58: inc(Scancode, $34);
  291. // Extended cursor block keys
  292. $47..$49, $4B, $4D, $4F..$53:
  293. inc(Scancode, $50);
  294. // Other keys
  295. $1C: Scancode := $A6; // Enter
  296. $35: Scancode := $A4; // / (keypad and normal!)
  297. end
  298. end
  299. else if CtrlKey then
  300. case Scancode of
  301. // Tab key
  302. $0F: Scancode := $94;
  303. // Function keys
  304. $3B..$44: inc(Scancode, $23);
  305. $57..$58: inc(Scancode, $32);
  306. // Keypad keys
  307. $35: Scancode := $95; // \
  308. $37: Scancode := $96; // *
  309. $47..$53: Scancode := CtrlKeypadKeys[Scancode];
  310. //Enter on Numpad
  311. $1C:
  312. begin
  313. Scancode := $0A;
  314. SpecialKey := False;
  315. end;
  316. end
  317. else if ShiftKey then
  318. case Scancode of
  319. // Function keys
  320. $3B..$44: inc(Scancode, $19);
  321. $57..$58: inc(Scancode, $30);
  322. //Enter on Numpad
  323. $1C:
  324. begin
  325. Scancode := $0D;
  326. SpecialKey := False;
  327. end;
  328. end
  329. else
  330. case Scancode of
  331. // Function keys
  332. $57..$58: inc(Scancode, $2E); // F11 and F12
  333. //Enter on NumPad
  334. $1C:
  335. begin
  336. Scancode := $0D;
  337. SpecialKey := False;
  338. end;
  339. end;
  340. RemapScanCode := ScanCode;
  341. end;
  342. function KeyPressed : boolean;
  343. var
  344. nevents,nread : dword;
  345. buf : TINPUTRECORD;
  346. AltKey: Boolean;
  347. c : longint;
  348. begin
  349. KeyPressed := FALSE;
  350. if ScanCode <> #0 then
  351. KeyPressed := TRUE
  352. else
  353. begin
  354. GetNumberOfConsoleInputEvents(TextRec(input).Handle,nevents{%H-});
  355. while nevents>0 do
  356. begin
  357. ReadConsoleInputA(TextRec(input).Handle,buf{%H-},1,nread{%H-});
  358. if buf.EventType = KEY_EVENT then
  359. if buf.Event.KeyEvent.bKeyDown then
  360. begin
  361. { Alt key is VK_MENU }
  362. { Capslock key is VK_CAPITAL }
  363. AltKey := ((Buf.Event.KeyEvent.dwControlKeyState AND
  364. (RIGHT_ALT_PRESSED OR LEFT_ALT_PRESSED)) > 0);
  365. if not(Buf.Event.KeyEvent.wVirtualKeyCode in [VK_SHIFT, VK_MENU, VK_CONTROL,
  366. VK_CAPITAL, VK_NUMLOCK,
  367. VK_SCROLL]) then
  368. begin
  369. keypressed:=true;
  370. if (ord(buf.Event.KeyEvent.AsciiChar) = 0) or
  371. (buf.Event.KeyEvent.dwControlKeyState and (LEFT_ALT_PRESSED or ENHANCED_KEY) > 0) then
  372. begin
  373. SpecialKey := TRUE;
  374. ScanCode := Chr(RemapScanCode(Buf.Event.KeyEvent.wVirtualScanCode, Buf.Event.KeyEvent.dwControlKeyState,
  375. Buf.Event.KeyEvent.wVirtualKeyCode));
  376. end
  377. else
  378. begin
  379. { Map shift-tab }
  380. if (buf.Event.KeyEvent.AsciiChar=#9) and
  381. (buf.Event.KeyEvent.dwControlKeyState and SHIFT_PRESSED > 0) then
  382. begin
  383. SpecialKey := TRUE;
  384. ScanCode := #15;
  385. end
  386. else
  387. begin
  388. SpecialKey := FALSE;
  389. ScanCode := Chr(Ord(buf.Event.KeyEvent.AsciiChar));
  390. end;
  391. end;
  392. if AltKey then
  393. begin
  394. case Buf.Event.KeyEvent.wVirtualScanCode of
  395. 71 : c:=7;
  396. 72 : c:=8;
  397. 73 : c:=9;
  398. 75 : c:=4;
  399. 76 : c:=5;
  400. 77 : c:=6;
  401. 79 : c:=1;
  402. 80 : c:=2;
  403. 81 : c:=3;
  404. 82 : c:=0;
  405. else
  406. break;
  407. end;
  408. DoingNumChars := true;
  409. DoingNumCode := Byte((DoingNumCode * 10) + c);
  410. Keypressed := false;
  411. Specialkey := false;
  412. ScanCode := #0;
  413. end
  414. else
  415. break;
  416. end;
  417. end
  418. else
  419. begin
  420. if (Buf.Event.KeyEvent.wVirtualKeyCode in [VK_MENU]) then
  421. if DoingNumChars then
  422. if DoingNumCode > 0 then
  423. begin
  424. ScanCode := Chr(DoingNumCode);
  425. Keypressed := true;
  426. DoingNumChars := false;
  427. DoingNumCode := 0;
  428. break
  429. end; { if }
  430. end;
  431. { if we got a key then we can exit }
  432. if keypressed then
  433. exit;
  434. GetNumberOfConsoleInputEvents(TextRec(input).Handle,nevents);
  435. end;
  436. end;
  437. end;
  438. function ReadKey: char;
  439. begin
  440. while (not KeyPressed) do
  441. Sleep(1);
  442. if SpecialKey then begin
  443. ReadKey := #0;
  444. SpecialKey := FALSE;
  445. end else begin
  446. ReadKey := ScanCode;
  447. ScanCode := #0;
  448. end;
  449. end;
  450. {$ENDIF}
  451. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  452. //TutlMenuParameter////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  453. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  454. procedure TutlMenuParameter.WriteConfig(const aMCF: TutlMCFSection);
  455. begin
  456. with aMCF do begin
  457. SetString('classname', self.ClassName);
  458. SetBool ('optional', fOptional);
  459. SetString('name', fName);
  460. SetString('description', fDescription);
  461. end;
  462. end;
  463. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  464. procedure TutlMenuParameter.ReadConfig(const aMCF: TutlMCFSection);
  465. begin
  466. with aMCF do begin
  467. fOptional := GetBool ('optional', true);
  468. fName := GetString('name', fName);
  469. fDescription := GetString('description', fDescription);
  470. end;
  471. end;
  472. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  473. function TutlMenuParameter.SetValue(const aValue: String): Boolean;
  474. begin
  475. fValue := aValue;
  476. result := true;
  477. end;
  478. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  479. //TutlMenuParameter////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  480. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  481. function TutlMenuParameter.GetString(const aOptions: TutlParameterStringFlags
  482. ): String;
  483. begin
  484. if fOptional then
  485. result := '(' + fName + ')'
  486. else
  487. result := '[' + fName + ']'
  488. end;
  489. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  490. constructor TutlMenuParameter.Create(const aOptional: Boolean; const aName, aDescription: String);
  491. begin
  492. inherited Create;
  493. fOptional := aOptional;
  494. fName := aName;
  495. fDescription := aDescription;
  496. end;
  497. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  498. destructor TutlMenuParameter.Destroy;
  499. begin
  500. inherited Destroy;
  501. end;
  502. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  503. //TutlMenuParameterList////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  504. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  505. function TutlMenuParameterList.HasParameter(const aName: String): Boolean;
  506. begin
  507. result := Assigned(FindParameter(aName));
  508. end;
  509. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  510. function TutlMenuParameterList.FindParameter(const aName: String): TutlMenuParameter;
  511. var
  512. i: Integer;
  513. begin
  514. for i := 0 to Count-1 do begin
  515. result := Items[i];
  516. if (result.Name = aName) then
  517. exit;
  518. end;
  519. result := nil
  520. end;
  521. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  522. //TutlMenuParameterStack///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  523. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  524. procedure TutlMenuParameterStack.Push(const aValue: TutlMenuParameter);
  525. begin
  526. Add(aValue);
  527. end;
  528. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  529. function TutlMenuParameterStack.Seek: TutlMenuParameter;
  530. begin
  531. if (Count > 0) then
  532. result := Items[Count-1]
  533. else
  534. result := nil;
  535. end;
  536. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  537. function TutlMenuParameterStack.Pop: TutlMenuParameter;
  538. begin
  539. if (Count > 0) then begin
  540. result := Items[Count-1];
  541. Delete(Count-1);
  542. end else
  543. result := nil;
  544. end;
  545. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  546. procedure TutlMenuParameterSingle.WriteConfig(const aMCF: TutlMCFSection);
  547. begin
  548. inherited WriteConfig(aMCF);
  549. aMCF.SetString('type', TutlParameterTypeH.ToString(fType));
  550. end;
  551. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  552. procedure TutlMenuParameterSingle.ReadConfig(const aMCF: TutlMCFSection);
  553. begin
  554. inherited ReadConfig(aMCF);
  555. fType := TutlParameterTypeH.ToEnum(aMCF.GetString('type', TutlParameterTypeH.ToString(ptPreset)));
  556. end;
  557. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  558. //TutlMenuParameterSingle//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  559. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  560. procedure TutlMenuParameterSingle.GetAutoCompleteStrings(const aStrings: TStrings);
  561. begin
  562. case fType of
  563. ptBoolean: begin
  564. aStrings.Add('true');
  565. aStrings.Add('false');
  566. end;
  567. ptInteger: begin
  568. aStrings.Add('[integer]');
  569. end;
  570. ptHex: begin
  571. aStrings.Add('[hex]');
  572. end;
  573. ptString: begin
  574. aStrings.Add('[string]');
  575. end;
  576. ptPreset: begin
  577. aStrings.Add(fName);
  578. end;
  579. end;
  580. end;
  581. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  582. function TutlMenuParameterSingle.SetValue(const aValue: String): Boolean;
  583. const
  584. BOOL_ARR: array[0..7] of String = ('y', 'n', 't', 'f', 'yes', 'no', 'true', 'false');
  585. function IsInArr: Boolean;
  586. var
  587. i: Integer;
  588. s: String;
  589. begin
  590. s := LowerCase(aValue);
  591. result := true;
  592. for i := 0 to high(BOOL_ARR) do
  593. if (BOOL_ARR[i] = s) then
  594. exit;
  595. result := false;
  596. end;
  597. var
  598. i: Integer;
  599. c: QWord;
  600. begin
  601. result := false;
  602. case fType of
  603. ptBoolean:
  604. if IsInArr then
  605. result := true;
  606. ptInteger:
  607. result := TryStrToInt(aValue, i);
  608. ptHex:
  609. result := AnsiStartsStr('0x', aValue) and TryStrToQWord('$' + AnsiRightStr(aValue, Length(aValue)-2), c);
  610. ptPreset:
  611. result := (aValue = fName);
  612. ptString:
  613. result := true;
  614. end;
  615. if result then
  616. result := inherited SetValue(aValue);
  617. end;
  618. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  619. //TutlMenuParameterSingle//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  620. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  621. function TutlMenuParameterSingle.GetString(const aOptions: TutlParameterStringFlags): String;
  622. begin
  623. result := fName;
  624. case fType of
  625. ptBoolean:
  626. result := result+':b';
  627. ptHex:
  628. result := result+':h';
  629. ptInteger:
  630. result := result+':i';
  631. ptString:
  632. result := result+':s';
  633. ptPreset:
  634. result := '''' + result + '''';
  635. end;
  636. if (psfBrackets in aOptions) then begin
  637. if fOptional then
  638. result := '(' + result + ')'
  639. else
  640. result := '[' + result + ']';
  641. end;
  642. end;
  643. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  644. constructor TutlMenuParameterSingle.Create(const aOptional: Boolean;
  645. const aName, aDescription: String; const aType: TutlParameterType);
  646. begin
  647. inherited Create(aOptional, aName, aDescription);
  648. fType := aType;
  649. end;
  650. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  651. destructor TutlMenuParameterSingle.Destroy;
  652. begin
  653. inherited Destroy;
  654. end;
  655. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  656. //TutlMenuParameterGroup///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  657. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  658. function TutlMenuParameterGroup.GetCount: Integer;
  659. begin
  660. result := fParameters.Count;
  661. end;
  662. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  663. function TutlMenuParameterGroup.GetParameter(const aIndex: Integer): TutlMenuParameterSingle;
  664. begin
  665. if (aIndex >= 0) and (aIndex < fParameters.Count) then
  666. result := fParameters[aIndex]
  667. else
  668. raise Exception.Create(format('TMenuParameterGroup.GetParameter - index out of bounds (%d)', [aIndex]));
  669. end;
  670. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  671. procedure TutlMenuParameterGroup.WriteConfig(const aMCF: TutlMCFSection);
  672. var
  673. i: Integer;
  674. begin
  675. inherited WriteConfig(aMCF);
  676. with aMCF.Sections['parameters'] do begin
  677. SetInt('count', fParameters.Count);
  678. for i := 0 to fParameters.Count-1 do
  679. fParameters[i].WriteConfig(Sections[IntToStr(i)]);
  680. end;
  681. end;
  682. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  683. procedure TutlMenuParameterGroup.ReadConfig(const aMCF: TutlMCFSection);
  684. var
  685. c, i: Integer;
  686. begin
  687. inherited ReadConfig(aMCF);
  688. with aMCF.Sections['parameters'] do begin
  689. c := GetInt('count', 0);
  690. for i := 0 to c-1 do
  691. AddParameter('', '', ptPreset).ReadConfig(Sections[IntToStr(i)]);
  692. end;
  693. end;
  694. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  695. procedure TutlMenuParameterGroup.GetAutoCompleteStrings(const aStrings: TStrings);
  696. var
  697. i: Integer;
  698. begin
  699. for i := 0 to fParameters.Count-1 do
  700. fParameters[i].GetAutoCompleteStrings(aStrings);
  701. end;
  702. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  703. function TutlMenuParameterGroup.SetValue(const aValue: String): Boolean;
  704. var
  705. i: Integer;
  706. begin
  707. for i := 0 to fParameters.Count-1 do
  708. if fParameters[i].SetValue(aValue) then begin
  709. result := inherited SetValue(aValue);
  710. break;
  711. end;
  712. end;
  713. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  714. function TutlMenuParameterGroup.GetString(const aOptions: TutlParameterStringFlags): String;
  715. var
  716. i: Integer;
  717. s: String;
  718. begin
  719. result := '';
  720. for i := 0 to fParameters.Count-1 do begin
  721. if result <> '' then
  722. result := result + '|';
  723. s := fParameters[i].GetString;
  724. s := copy(s, 2, Length(s)-2);
  725. result := result + s;
  726. end;
  727. result := fName + ':' + result;
  728. if (psfBrackets in aOptions) then begin
  729. if (fOptional) then
  730. result := '(' + result + ')'
  731. else
  732. result := '[' + result + ']';
  733. end;
  734. end;
  735. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  736. function TutlMenuParameterGroup.AddParameter(const aName, aDescription: String; const aType: TutlParameterType): TutlMenuParameterSingle;
  737. begin
  738. result := TutlMenuParameterSingle.Create(false, aName, aDescription, aType);
  739. fParameters.Add(result);
  740. end;
  741. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  742. function TutlMenuParameterGroup.AddParameter(const aParameter: TutlMenuParameterSingle): TutlMenuParameterSingle;
  743. begin
  744. result := aParameter;
  745. fParameters.Add(result);
  746. end;
  747. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  748. procedure TutlMenuParameterGroup.DelParameter(const aIndex: Integer);
  749. begin
  750. if (aIndex >= 0) and (aIndex < fParameters.Count) then
  751. fParameters.Delete(aIndex)
  752. else
  753. raise Exception.Create(format('TMenuParameterGroup.DelParameter - index out of bounds (%d)', [aIndex]));
  754. end;
  755. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  756. constructor TutlMenuParameterGroup.Create(const aOptional: Boolean; const aName, aDescription: String);
  757. begin
  758. inherited Create(aOptional, aName, aDescription);
  759. fParameters := TutlMenuParameterSingleList.Create(true);
  760. end;
  761. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  762. destructor TutlMenuParameterGroup.Destroy;
  763. begin
  764. FreeAndNil(fParameters);
  765. inherited Destroy;
  766. end;
  767. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  768. //TutlMenuItem/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  769. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  770. function TutlMenuItem.GetCount: Integer;
  771. begin
  772. result := fItems.Count;
  773. end;
  774. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  775. function TutlMenuItem.GetItems(const aIndex: Integer): TutlMenuItem;
  776. begin
  777. if (aIndex >= 0) and (aIndex < fItems.Count) then
  778. result := fItems[aIndex]
  779. else
  780. raise Exception.Create(format('TMenuItem.GetItems - index out of bounds (%d)', [aIndex]));
  781. end;
  782. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  783. function TutlMenuItem.GetMenuPath: String;
  784. var
  785. m: TutlMenuItem;
  786. begin
  787. result := '';
  788. m := self;
  789. while Assigned(m) do begin
  790. if Length(result) > 0 then
  791. result := ' ' + result;
  792. result := m.GetString + result; //m.Command + result;
  793. if (m <> m.Parent) then
  794. m := m.Parent
  795. else
  796. m := nil;
  797. end;
  798. result := Trim(result);
  799. end;
  800. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  801. function TutlMenuItem.GetParamCount: Integer;
  802. begin
  803. result := fParameters.Count;
  804. end;
  805. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  806. function TutlMenuItem.GetParameters(const aIndex: Integer): TutlMenuParameter;
  807. begin
  808. if (aIndex >= 0) and (aIndex < fParameters.Count) then
  809. result := fParameters[aIndex]
  810. else
  811. raise Exception.Create(format('TMenuItem.GetParameters - index out of bounds', [aIndex]));
  812. end;
  813. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  814. function TutlMenuItem.GetParameterString: String;
  815. var
  816. i: Integer;
  817. begin
  818. result := '';
  819. for i := 0 to fParameters.Count-1 do begin
  820. if result <> '' then
  821. result := result + ' ';
  822. result := result + fParameters[i].GetString;
  823. end;
  824. end;
  825. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  826. procedure TutlMenuItem.SetCallback(aValue: TutlCallback);
  827. begin
  828. if fCallback = aValue then
  829. exit;
  830. fCallback := aValue;
  831. fExecutable := Assigned(fCallback);
  832. end;
  833. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  834. procedure TutlMenuItem.WriteConfig(const aMCF: TutlMCFSection);
  835. var
  836. i: Integer;
  837. begin
  838. with aMCF do begin
  839. SetString('command', fCommand);
  840. SetString('description', fDescription);
  841. SetBool ('executable', fExecutable);
  842. with Sections['parameters'] do begin
  843. SetInt('count', fParameters.Count);
  844. for i := 0 to fParameters.Count-1 do
  845. fParameters[i].WriteConfig(Sections[IntToStr(i)]);
  846. end;
  847. with Sections['menus'] do begin
  848. SetInt('count', fItems.Count);
  849. for i := 0 to fItems.Count-1 do
  850. fItems[i].WriteConfig(Sections[IntToStr(i)]);
  851. end;
  852. end;
  853. end;
  854. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  855. procedure TutlMenuItem.ReadConfig(const aMCF: TutlMCFSection);
  856. var
  857. c, i: Integer;
  858. s: String;
  859. p: TutlMenuParameter;
  860. begin
  861. fItems.Clear;
  862. fParameters.Clear;
  863. with aMCF do begin
  864. fCommand := GetString('command', '');
  865. fDescription := GetString('description', '');
  866. fExecutable := GetBool ('executable', false);
  867. with Sections['parameters'] do begin
  868. c := GetInt('count', 0);
  869. for i := 0 to c-1 do begin
  870. s := Sections[IntToStr(i)].GetString('classname', '');
  871. p := nil;
  872. if (s = TutlMenuParameterSingle.ClassName) then
  873. p := TutlMenuParameterSingle.Create(true, '', '', ptPreset)
  874. else if (s = TutlMenuParameterGroup.ClassName) then
  875. p := TutlMenuParameterGroup.Create(true, '', '');
  876. if Assigned(p) then begin
  877. fParameters.Add(p);
  878. p.ReadConfig(Sections[IntToStr(i)]);
  879. end;
  880. end;
  881. end;
  882. with Sections['menus'] do begin
  883. c := GetInt('count', 0);
  884. for i := 0 to c-1 do
  885. AddItem('', '', nil).ReadConfig(Sections[IntToStr(i)]);
  886. end;
  887. end;
  888. end;
  889. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  890. procedure TutlMenuItem.GetAutoCompleteStrings(const aList: TStrings; const aParameter: TutlMenuParameterList);
  891. var
  892. hasAllParam: Boolean;
  893. i: Integer;
  894. p: TutlMenuParameter;
  895. begin
  896. hasAllParam := true;
  897. for i := 0 to fParameters.Count-1 do begin
  898. p := fParameters[i];
  899. if not p.Optional then begin
  900. if not aParameter.HasParameter(p.Name) then begin
  901. p.GetAutoCompleteStrings(aList);
  902. hasAllParam := false;
  903. break;
  904. end;
  905. end else begin
  906. if not aParameter.HasParameter(p.Name) then
  907. p.GetAutoCompleteStrings(aList);
  908. end;
  909. end;
  910. if hasAllParam then begin
  911. for i := 0 to fItems.Count-1 do
  912. aList.Add(fItems[i].Command);
  913. if Command <> 'help' then
  914. aList.Add('help');
  915. end;
  916. end;
  917. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  918. function TutlMenuItem.GetString: String;
  919. var
  920. s: String;
  921. begin
  922. result := Command;
  923. s := ParameterString;
  924. if (Command <> '') and (s <> '') then
  925. result := result + ' ';
  926. result := result + s;
  927. end;
  928. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  929. function TutlMenuItem.AddItem(const aCmd, aDesc: String; const aCallback: TutlCallback): TutlMenuItem;
  930. begin
  931. result := TutlMenuItem.Create(self, aCmd, aDesc, aCallback);
  932. fItems.Add(result);
  933. end;
  934. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  935. function TutlMenuItem.AddItem(const aItem: TutlMenuItem): TutlMenuItem;
  936. begin
  937. result := aItem;
  938. fItems.Add(result);
  939. end;
  940. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  941. procedure TutlMenuItem.DelItem(const aIndex: Integer);
  942. begin
  943. if (aIndex >= 0) and (aIndex < fItems.Count) then
  944. fItems.Delete(aIndex)
  945. else
  946. raise Exception.Create(format('TMenuItem.DelItem - index out of bounds (%d)', [aIndex]));
  947. end;
  948. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  949. function TutlMenuItem.AddParameter(const aParameter: TutlMenuParameter): TutlMenuParameter;
  950. begin
  951. result := aParameter;
  952. result.fParent := self;
  953. fParameters.Add(result);
  954. end;
  955. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  956. procedure TutlMenuItem.DelParameter(const aIndex: Integer);
  957. begin
  958. if (aIndex >= 0) and (aIndex < fParameters.Count) then
  959. fParameters.Delete(aIndex)
  960. else
  961. raise Exception.Create(format('TMenuItem.DelParameter - index out of bounds (%d)', [aIndex]));
  962. end;
  963. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  964. constructor TutlMenuItem.Create(const aParent: TutlMenuItem; const aCmd, aDesc: String; const aCallback: TutlCallback);
  965. begin
  966. inherited Create;
  967. fParent := aParent;
  968. fCommand := aCmd;
  969. fDescription := aDesc;
  970. fCallback := aCallback;
  971. fExecutable := Assigned(fCallback);
  972. fItems := TutlMenuItemList.Create(true);
  973. fParameters := TutlMenuParameterList.Create(true);
  974. if aCmd <> 'help' then begin
  975. fHelpItem := TutlMenuItem.Create(self, 'help', 'shows the help. use ''all'' to display the menu tree. use ''detail'' to display command details', nil);
  976. with fHelpItem.AddParameter(TutlMenuParameterGroup.Create(true, 'mode', 'specify how to display the help menu')) as TutlMenuParameterGroup do begin
  977. AddParameter('all', 'displays the complete menu tree', ptPreset);
  978. AddParameter('detail', 'displays detailed information', ptPreset);
  979. end;
  980. end;
  981. end;
  982. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  983. destructor TutlMenuItem.Destroy;
  984. begin
  985. FreeAndNil(fItems);
  986. FreeAndNil(fParameters);
  987. FreeAndNil(fHelpItem);
  988. inherited Destroy;
  989. end;
  990. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  991. //TutlCommandMenu//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  992. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  993. function TutlCommandMenu.GetCmdParameter: TutlMenuParameterList;
  994. begin
  995. result := fCmdParameter;
  996. end;
  997. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  998. procedure TutlCommandMenu.SplitCmdString(const aText: String; const aChar: Char);
  999. var
  1000. i: Integer;
  1001. Buffer: String;
  1002. Quote: Boolean;
  1003. begin
  1004. fCmdStack.Clear;
  1005. Buffer := '';
  1006. Quote := false;
  1007. for i := 1 to Length(aText) do begin
  1008. if (aText[i] = aChar) and not Quote then begin
  1009. Buffer := Trim(Buffer);
  1010. fCmdStack.Add(Buffer);
  1011. Buffer := '';
  1012. end else if (aText[i] = '"') then
  1013. Quote := not Quote
  1014. else
  1015. Buffer := Buffer + aText[i];
  1016. end;
  1017. if Buffer <> '' then begin
  1018. Buffer := Trim(Buffer);
  1019. fCmdStack.Add(Buffer);
  1020. end;
  1021. end;
  1022. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1023. function TutlCommandMenu.ParseCommand: TutlParseResult;
  1024. procedure RestoreParameterStack(const aMenu: TutlMenuItem);
  1025. var
  1026. p: TutlMenuParameter;
  1027. begin
  1028. p := fCmdParameter.Seek;
  1029. while Assigned(p) and (p.Parent = aMenu) do begin
  1030. fCmdStack.Push(p.Value);
  1031. fCmdParameter.Pop;
  1032. p := fCmdParameter.Seek;
  1033. end;
  1034. end;
  1035. function BackTrack(const aMenu: TutlMenuItem): TutlParseResult;
  1036. var
  1037. s, cmd: String;
  1038. OptionalParamCount: Integer;
  1039. i, c: Integer;
  1040. m: TutlMenuItem;
  1041. p: TutlMenuParameter;
  1042. begin
  1043. //Stack is empty
  1044. if (fCmdStack.Count = 0) then begin
  1045. if Assigned(aMenu.Callback) or (aMenu = fHelpItem) then
  1046. result := prSuccess
  1047. else
  1048. result := prIncompleteCmd;
  1049. fCurrentMenu := aMenu;
  1050. exit;
  1051. end;
  1052. s := fCmdStack.Pop;
  1053. cmd := LowerCase(s);
  1054. //find command
  1055. m := nil;
  1056. for i := 0 to aMenu.Count-1 do
  1057. if (LowerCase(aMenu[i].Command) = cmd) then begin
  1058. m := aMenu[i];
  1059. break;
  1060. end;
  1061. if not Assigned(m) and (cmd = 'help') then begin
  1062. m := fHelpItem;
  1063. m.fParent := aMenu;
  1064. end;
  1065. if Assigned(m) then begin
  1066. //count optional parameters
  1067. c := 0;
  1068. for i := 0 to m.ParamCount-1 do
  1069. if (m.Parameters[i].Optional) then
  1070. inc(c);
  1071. OptionalParamCount := (1 shl c) - 1;
  1072. //backtrack optional parameters
  1073. while (OptionalParamCount >= 0) do begin
  1074. result := prSuccess;
  1075. fCurrentParamCount := 0;
  1076. c := 0;
  1077. for i := 0 to m.ParamCount-1 do begin
  1078. p := m.Parameters[i];
  1079. if not p.Optional or (((OptionalParamCount shr c) and 1) = 1) then begin
  1080. if (fCmdStack.Count <= 0) then begin
  1081. result := prInvalidParamCount;
  1082. RestoreParameterStack(m);
  1083. fCurrentMenu := m;
  1084. break;
  1085. end else if (LowerCase(fCmdStack.Seek) = 'help') then begin
  1086. break;
  1087. end else if not (p.SetValue(fCmdStack.Seek)) then begin
  1088. result := prInvalidParam;
  1089. RestoreParameterStack(m);
  1090. fCurrentMenu := m;
  1091. break;
  1092. end else begin
  1093. inc(fCurrentParamCount);
  1094. fCmdParameter.Push(p);
  1095. fCmdStack.Pop;
  1096. end;
  1097. if p.Optional then
  1098. inc(c);
  1099. end;
  1100. end;
  1101. if (result = prSuccess) then begin
  1102. result := BackTrack(m);
  1103. if result = prUnknownCommand then
  1104. fCurrentMenu := aMenu;
  1105. end;
  1106. if result <> prSuccess then
  1107. dec(OptionalParamCount)
  1108. else
  1109. OptionalParamCount := -1;
  1110. end;
  1111. end else begin
  1112. fCmdStack.Push(s);
  1113. fUnknownCmd := s;
  1114. result := prUnknownCommand;
  1115. end;
  1116. end;
  1117. begin
  1118. fCmdParameter.Clear;
  1119. fCurrentParamCount := 0;
  1120. fInvalidParam := '';
  1121. fUnknownCmd := '';
  1122. fCurrentMenu := nil;
  1123. result := BackTrack(self);
  1124. end;
  1125. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1126. procedure TutlCommandMenu.ExecuteCommand(const aCmd: String);
  1127. var
  1128. r: TutlParseResult;
  1129. p: TutlMenuParameter;
  1130. begin
  1131. fLastCmd := aCmd;
  1132. SplitCmdString(aCmd, ' ');
  1133. r := ParseCommand;
  1134. case r of
  1135. prSuccess:
  1136. if (fCurrentMenu = fHelpItem) then begin
  1137. p := nil;
  1138. if (fCmdParameter.Count > 0) and (fCmdParameter[fCmdParameter.Count-1].Parent = fHelpItem) then
  1139. p := fCmdParameter[fCmdParameter.Count-1];
  1140. if not Assigned(p) then
  1141. DisplayHelp(fHelpItem.Parent)
  1142. else if (p.Value = 'all') then
  1143. DisplayHelp(fHelpItem.Parent, hoAll)
  1144. else if (p.Value = 'detail') then
  1145. DisplayHelp(fHelpItem.Parent, hoDetail)
  1146. else
  1147. DisplayHelp(fHelpItem.Parent);
  1148. end else
  1149. fCurrentMenu.Callback(self);
  1150. prInvalidParam:
  1151. DisplayInvalidParam(fInvalidParam);
  1152. prInvalidParamCount:
  1153. DisplayInvalidParamCount(fCurrentParamCount);
  1154. prIncompleteCmd:
  1155. DisplayIncompleteCommand;
  1156. prUnknownCommand:
  1157. DisplayUnknownCommand(fUnknownCmd);
  1158. end;
  1159. end;
  1160. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1161. procedure TutlCommandMenu.DisplayHelp(const aRefMenu: TutlMenuItem; const aOption: TutlHelpOption);
  1162. var
  1163. maxLength: Integer;
  1164. function AddStr(const aOld, aNew, aPostfix: String): String;
  1165. begin
  1166. result := aOld;
  1167. if (aOld <> '') and (aNew <> '') then
  1168. result := result + aPostfix;
  1169. result := result + aNew;
  1170. end;
  1171. function GetMaxLength(const aItem: TutlMenuItem): Integer;
  1172. var
  1173. i, l: Integer;
  1174. m: TutlMenuItem;
  1175. begin
  1176. result := 0;
  1177. for i := 0 to aItem.Count-1 do begin
  1178. m := aItem[i];
  1179. l := Length(m.GetString);
  1180. if l > result then
  1181. result := l;
  1182. if (aOption = hoAll) then begin
  1183. l := GetMaxLength(m) + 2;
  1184. if l > result then
  1185. result := l;
  1186. end;
  1187. end;
  1188. end;
  1189. function FillStr(const aStr: String; const aChar: Char; const aLength: Integer): String;
  1190. begin
  1191. result := aStr + StringOfChar(aChar, aLength-Length(aStr));
  1192. end;
  1193. procedure AddHelpText(var aMsg: String; const aPrefix: String; const aItem: TutlMenuItem);
  1194. begin
  1195. if (aMsg <> '') then
  1196. aMsg := aMsg + sLineBreak;
  1197. aMsg := aMsg + FillStr(aPrefix + aItem.GetString, ' ', maxLength) + ' ' + aItem.Description;
  1198. end;
  1199. procedure AddHelpItems(var aMsg: String; const aPrefix: String; const aItem: TutlMenuItem; const aDisplayHelpItem: Boolean = false);
  1200. var
  1201. i: Integer;
  1202. m: TutlMenuItem;
  1203. begin
  1204. for i := 0 to aItem.Count-1 do begin
  1205. m := aItem[i];
  1206. AddHelpText(aMsg, aPrefix, m);
  1207. if (aOption = hoAll) then
  1208. AddHelpItems(aMsg, aPrefix+' ', m);
  1209. end;
  1210. if aDisplayHelpItem then
  1211. AddHelpText(aMsg, aPrefix, fHelpItem);
  1212. end;
  1213. procedure DisplayGroupParameters(var aMsg: String; const aParamGroup: TutlMenuParameterGroup);
  1214. var
  1215. i, maxLen, l: Integer;
  1216. p: TutlMenuParameter;
  1217. begin
  1218. maxLen := 0;
  1219. for i := 0 to aParamGroup.Count-1 do begin
  1220. l := Length(aParamGroup[i].GetString([]));
  1221. if l > maxLen then
  1222. maxLen := l;
  1223. end;
  1224. inc(maxLen, 3);
  1225. for i := 0 to aParamGroup.Count-1 do begin
  1226. p := aParamGroup[i];
  1227. aMsg := aMsg + sLineBreak + ' ' +
  1228. FillStr(p.GetString([]), ' ', maxLen) +
  1229. p.Description;
  1230. end;
  1231. end;
  1232. procedure DisplayParameters(var aMsg: String);
  1233. var
  1234. i, maxLen, l: Integer;
  1235. p: TutlMenuParameter;
  1236. begin
  1237. maxLen := 0;
  1238. for i := 0 to aRefMenu.ParamCount-1 do begin
  1239. l := Length(aRefMenu.Parameters[i].GetString);
  1240. if (l > maxLen) then
  1241. maxLen := l;
  1242. end;
  1243. inc(maxLen, 3);
  1244. aMsg := aMsg + sLineBreak + 'Parameters:';
  1245. if (aRefMenu.ParamCount > 0) then begin
  1246. for i := 0 to aRefMenu.ParamCount-1 do begin
  1247. p := aRefMenu.Parameters[i];
  1248. aMsg := aMsg + sLineBreak + ' ' + FillStr(p.GetString, ' ', maxLen);
  1249. if p.Optional then
  1250. aMsg := aMsg + '(optional) '
  1251. else
  1252. aMsg := aMsg + ' ';
  1253. aMsg := aMsg + p.Description;
  1254. if (p is TutlMenuParameterGroup) then
  1255. DisplayGroupParameters(aMsg, p as TutlMenuParameterGroup);
  1256. end;
  1257. end else
  1258. aMsg := aMsg + sLineBreak + ' [no Parameters]';
  1259. end;
  1260. var
  1261. menu: TutlMenuItem;
  1262. msg: String;
  1263. begin
  1264. if Assigned(aRefMenu) then
  1265. menu := aRefMenu
  1266. else
  1267. menu := self;
  1268. msg := AddStr(menu.MenuPath, menu.ParameterString, ' ');
  1269. msg := AddStr(msg, menu.Description, ' - ');
  1270. fHelpItem.fParent := nil;
  1271. if (aOption = hoDetail) then
  1272. msg := msg + sLineBreak + 'Submenus / Commands:';
  1273. maxLength := max(GetMaxLength(menu), Length(fHelpItem.GetString)) + 2;
  1274. if (msg = '') then
  1275. msg := sLineBreak;
  1276. AddHelpItems(msg, ' ', menu, true);
  1277. if (aRefMenu is TutlConsoleMenu) then
  1278. AddHelpText(msg, ' ', (self as TutlConsoleMenu).fExitMenu);
  1279. if (aOption = hoDetail) then
  1280. DisplayParameters(msg);
  1281. utlLogger.Log(Self, msg, []);
  1282. end;
  1283. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1284. procedure TutlCommandMenu.DisplayIncompleteCommand;
  1285. function GetMenuPath: String;
  1286. var
  1287. s: String;
  1288. begin
  1289. s := '';
  1290. if Assigned(fCurrentMenu) then
  1291. s := fCurrentMenu.MenuPath;
  1292. if (s <> '') then
  1293. result := s+' '
  1294. else
  1295. result := '';
  1296. end;
  1297. begin
  1298. utlLogger.Error(Self, 'incomplete command! type "%shelp" to get further information.', [GetMenuPath]);
  1299. end;
  1300. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1301. procedure TutlCommandMenu.DisplayUnknownCommand(const aCmd: String);
  1302. function GetMenuPath: String;
  1303. var
  1304. s: String;
  1305. begin
  1306. s := '';
  1307. if Assigned(fCurrentMenu) then
  1308. s := fCurrentMenu.MenuPath;
  1309. if (s <> '') then
  1310. result := s+' '
  1311. else
  1312. result := '';
  1313. end;
  1314. function GetCommand: String;
  1315. begin
  1316. if (aCmd <> '') then
  1317. result := ' "'+aCmd+'"'
  1318. else
  1319. result := '';
  1320. end;
  1321. begin
  1322. utlLogger.Error(Self, 'unknown command%s! type "%shelp" to get further information.', [GetCommand, GetMenuPath]);
  1323. end;
  1324. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1325. procedure TutlCommandMenu.DisplayInvalidParamCount(const aParamCount: Integer);
  1326. function GetMenuPath: String;
  1327. var
  1328. s: String;
  1329. begin
  1330. s := '';
  1331. if Assigned(fCurrentMenu) then
  1332. s := fCurrentMenu.MenuPath;
  1333. if (s <> '') then
  1334. result := s+' '
  1335. else
  1336. result := '';
  1337. end;
  1338. function GetParamCount: String;
  1339. var
  1340. i, c: Integer;
  1341. begin
  1342. result := ' (';
  1343. if (aParamCount >= 0) then begin
  1344. result := result + IntToStr(aParamCount);
  1345. end;
  1346. if Assigned(fCurrentMenu) then begin
  1347. c := 0;
  1348. for i := 0 to fCurrentMenu.ParamCount-1 do
  1349. if not fCurrentMenu.Parameters[i].Optional then
  1350. inc(c);
  1351. result := result + ', expected '+IntToStr(c);
  1352. end;
  1353. result := result + ')';
  1354. if (result = ' ()') then
  1355. result := '';
  1356. end;
  1357. begin
  1358. utlLogger.Log(Self, 'invalid parameter count%s! type "%shelp" to get further information.', [GetParamCount, GetMenuPath]);
  1359. end;
  1360. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1361. procedure TutlCommandMenu.DisplayInvalidParam(const aParam: String);
  1362. function GetMenuPath: String;
  1363. var
  1364. s: String;
  1365. begin
  1366. s := '';
  1367. if Assigned(fCurrentMenu) then
  1368. s := fCurrentMenu.MenuPath;
  1369. if (s <> '') then
  1370. result := s+' '
  1371. else
  1372. result := '';
  1373. end;
  1374. function GetParam: String;
  1375. begin
  1376. if (aParam <> '') then
  1377. result := ' "'+aParam+'"'
  1378. else
  1379. result := '';
  1380. end;
  1381. begin
  1382. utlLogger.Log(Self, 'invalid parameter%s! type "%shelp" to get further information.', [GetParam, GetMenuPath]);
  1383. end;
  1384. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1385. procedure TutlMenuItem.LoadFromStream(const aStream: TStream);
  1386. var
  1387. mcf: TutlMCFFile;
  1388. begin
  1389. mcf := TutlMCFFile.Create(nil);
  1390. try
  1391. mcf.LoadFromStream(aStream);
  1392. ReadConfig(mcf);
  1393. finally
  1394. mcf.Free;
  1395. end;
  1396. end;
  1397. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1398. procedure TutlMenuItem.SaveToStream(const aStream: TStream);
  1399. var
  1400. mcf: TutlMCFFile;
  1401. begin
  1402. mcf := TutlMCFFile.Create(nil);
  1403. try
  1404. WriteConfig(mcf);
  1405. mcf.SaveToStream(aStream);
  1406. finally
  1407. mcf.Free;
  1408. end;
  1409. end;
  1410. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1411. constructor TutlCommandMenu.Create(const aHelp: String);
  1412. begin
  1413. inherited Create(nil, '', aHelp, nil);
  1414. fCmdStack := TutlStringStack.Create;
  1415. fCmdParameter := TutlMenuParameterStack.Create(False);
  1416. end;
  1417. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1418. destructor TutlCommandMenu.Destroy;
  1419. begin
  1420. FreeAndNil(fCmdStack);
  1421. FreeAndNil(fCmdParameter);
  1422. inherited Destroy;
  1423. end;
  1424. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1425. //TutlCommandPrompt/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1426. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1427. procedure TutlCommandPrompt.SetPrefix(aValue: String);
  1428. begin
  1429. if fPrefix = aValue then
  1430. exit;
  1431. DelInput(true);
  1432. delete(fCurrent, 1, Length(fPrefix));
  1433. if fHistoryID >= 0 then
  1434. delete(fHistoryBackup, 1, Length(fPrefix));
  1435. fPrefix := aValue;
  1436. fStartIndex := Length(fPrefix)+1;
  1437. fCurrent := fPrefix + fCurrent;
  1438. if fHistoryID >= 0 then
  1439. fHistoryBackup := fPrefix + fHistoryBackup;
  1440. RestoreInput;
  1441. end;
  1442. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1443. function TutlCommandPrompt.GetCurrent: String;
  1444. begin
  1445. if fHistoryID = -1 then
  1446. result := copy(fCurrent, fStartIndex, MaxInt)
  1447. else
  1448. result := copy(fHistoryBackup, fStartIndex, MaxInt);
  1449. end;
  1450. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1451. procedure TutlCommandPrompt.SetCurrent(aValue: String);
  1452. begin
  1453. DelInput;
  1454. fCurrent := fPrefix + aValue;
  1455. fHistoryID := -1;
  1456. RestoreInput;
  1457. end;
  1458. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1459. procedure TutlCommandPrompt.SetHiddenChar(aValue: Char);
  1460. begin
  1461. if fHiddenChar = aValue then
  1462. exit;
  1463. DelInput(true);
  1464. fHiddenChar := aValue;
  1465. RestoreInput;
  1466. end;
  1467. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1468. procedure TutlCommandPrompt.CursorToStart;
  1469. begin
  1470. Write(StringOfChar(#8, fCurID-fStartIndex));
  1471. fCurID := fStartIndex;
  1472. end;
  1473. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1474. procedure TutlCommandPrompt.CursorToEnd;
  1475. begin
  1476. Write(Copy(fCurrent, fCurID, MaxInt));
  1477. fCurID := Length(fCurrent)+1;
  1478. end;
  1479. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1480. procedure TutlCommandPrompt.DelInput(const aAll: Boolean);
  1481. var
  1482. l: Integer;
  1483. begin
  1484. if not aAll then
  1485. l := fCurID - fStartIndex
  1486. else
  1487. l := fCurID - 1;
  1488. Write(StringOfChar(#08, l));
  1489. Write(StringOfChar(' ', l));
  1490. Write(StringOfChar(#08, l));
  1491. dec(fCurID, l);
  1492. end;
  1493. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1494. procedure TutlCommandPrompt.RestoreInput;
  1495. begin
  1496. if (fHiddenChar <> #0) then begin
  1497. while fCurID < fStartIndex do begin
  1498. Write(fCurrent[fCurID]);
  1499. inc(fCurID);
  1500. end;
  1501. Write(StringOfChar(fHiddenChar, Length(fCurrent) - fCurID + 1))
  1502. end else
  1503. Write(Copy(fCurrent, fCurID, MaxInt));
  1504. fCurID := Length(fCurrent)+1;
  1505. end;
  1506. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1507. procedure TutlCommandPrompt.CursorRight;
  1508. begin
  1509. if fCurID <= Length(fCurrent) then begin
  1510. if (fHiddenChar <> #0) then
  1511. Write(fHiddenChar)
  1512. else
  1513. Write(fCurrent[fCurID]);
  1514. inc(fCurID);
  1515. end;
  1516. end;
  1517. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1518. procedure TutlCommandPrompt.CursorLeft;
  1519. begin
  1520. if fCurID > fStartIndex then begin
  1521. Write(#8);
  1522. dec(fCurID, 1);
  1523. end;
  1524. end;
  1525. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1526. procedure TutlCommandPrompt.DelChar(const aBeforeCursor: Boolean);
  1527. begin
  1528. if not aBeforeCursor and (fCurID <= Length(fCurrent)) then begin
  1529. Delete(fCurrent, fCurID, 1);
  1530. Write(copy(fCurrent, fCurID, MaxInt), ' ');
  1531. Write(StringOfChar(#8, Length(fCurrent)-fCurID+2));
  1532. end else if aBeforeCursor and (fCurID > fStartIndex) then begin
  1533. Delete(fCurrent, fCurID-1, 1);
  1534. Write(#8, copy(fCurrent, fCurID-1, MaxInt), ' ');
  1535. Write(StringOfChar(#8, Length(fCurrent)-fCurID+3));
  1536. dec(fCurID);
  1537. end;
  1538. end;
  1539. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1540. procedure TutlCommandPrompt.WriteChar(const c: Char);
  1541. begin
  1542. if (fCurID <= Length(fCurrent)) then begin
  1543. Write('#', copy(fCurrent, fCurID, MaxInt));
  1544. Write(StringOfChar(#8, Length(fCurrent)-fCurID+2));
  1545. end;
  1546. Insert(c, fCurrent, fCurID);
  1547. inc(fCurID, 1);
  1548. if (fHiddenChar <> #0) then
  1549. Write(fHiddenChar)
  1550. else
  1551. Write(c);
  1552. end;
  1553. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1554. function TutlCommandPrompt.ReadLnEx: String;
  1555. var
  1556. c: Byte;
  1557. key: Char;
  1558. CtrlKey: Boolean;
  1559. s: String;
  1560. tabPressed: Boolean;
  1561. begin
  1562. fHistoryID := -1;
  1563. CtrlKey := false;
  1564. tabPressed := false;
  1565. fConsoleCS.Enter;
  1566. try
  1567. DelInput(true);
  1568. RestoreInput;
  1569. finally
  1570. fConsoleCS.Leave;
  1571. end;
  1572. while fRunning do begin
  1573. key := ReadKey;
  1574. c := Ord(key) and $FF;
  1575. fConsoleCS.Enter;
  1576. try
  1577. if (CtrlKey) then begin
  1578. CtrlKey := false;
  1579. case c of
  1580. 72: begin //KEY_UP
  1581. if (fHistoryID+1 < fHistory.Count) then begin
  1582. if (fHistoryID = -1) then
  1583. fHistoryBackup := fCurrent;
  1584. CursorToEnd;
  1585. DelInput;
  1586. inc(fHistoryID);
  1587. fCurrent := fPrefix + fHistory[fHistoryID];
  1588. RestoreInput;
  1589. end;
  1590. end;
  1591. 80: begin //KEY_DOWN
  1592. if (fHistoryID >= 0) then begin
  1593. CursorToEnd;
  1594. DelInput;
  1595. dec(fHistoryID);
  1596. if (fHistoryID >= 0) then
  1597. fCurrent := fPrefix + fHistory[fHistoryID]
  1598. else
  1599. fCurrent := fHistoryBackup;
  1600. RestoreInput;
  1601. end;
  1602. end;
  1603. 75: begin //KEY_LEFT
  1604. CursorLeft;
  1605. end;
  1606. 77: begin //KEY_RIGHT
  1607. CursorRight;
  1608. end;
  1609. 82: begin //KEY_INSERT
  1610. end;
  1611. 83: begin //KEY_DELETE
  1612. DelChar(false);
  1613. end;
  1614. 71: begin //KEY_HOME
  1615. CursorToStart;
  1616. end;
  1617. 79: begin //KEY_END
  1618. CursorToEnd;
  1619. end;
  1620. 73: begin //KEY_PGUP
  1621. end;
  1622. 81: begin //KEY_PGDOWN
  1623. end;
  1624. end;
  1625. end else begin
  1626. if (tabPressed) then
  1627. tabPressed := (c = VK_TAB);
  1628. case c of
  1629. VK_UNKNOWN: begin
  1630. CtrlKey := true;
  1631. end;
  1632. VK_BACK: begin
  1633. DelChar(true);
  1634. end;
  1635. VK_TAB: begin
  1636. CursorToEnd;
  1637. DelInput;
  1638. s := fCurrent;
  1639. fCurrent := fPrefix +
  1640. AutoComplete(copy(fCurrent, fStartIndex, MaxInt), tabPressed);
  1641. RestoreInput;
  1642. if s <> fCurrent then
  1643. tabPressed := false
  1644. else
  1645. tabPressed := not tabPressed;
  1646. end;
  1647. VK_RETURN: begin //RETURN
  1648. break;
  1649. end;
  1650. VK_ESCAPE: begin
  1651. Reset;
  1652. end;
  1653. else
  1654. WriteChar(Chr(c));
  1655. end;
  1656. end;
  1657. finally
  1658. fConsoleCS.Leave;
  1659. end;
  1660. end;
  1661. fConsoleCS.Enter;
  1662. try
  1663. WriteLn;
  1664. finally
  1665. fConsoleCS.Leave;
  1666. end;
  1667. result := copy(fCurrent, Length(fPrefix)+1, MaxInt);
  1668. Reset;
  1669. end;
  1670. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1671. function TutlCommandPrompt.AutoComplete(const aInput: String; const aDisplayPossibilities: Boolean): String;
  1672. begin
  1673. result := aInput;
  1674. if Assigned(fOnAutoComplete) then
  1675. result := fOnAutoComplete(self, aInput, aDisplayPossibilities);
  1676. end;
  1677. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1678. procedure TutlCommandPrompt.AddHistory(const aInput: String);
  1679. begin
  1680. if fHistoryEnabled and (fHiddenChar = #0) and
  1681. ((fHistory.Count <= 0) or (fHistory[0] <> aInput)) then
  1682. fHistory.Insert(0, aInput);
  1683. end;
  1684. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1685. procedure TutlCommandPrompt.DoInput;
  1686. begin
  1687. if Assigned(fOnInput) then
  1688. fOnInput(self, fInput);
  1689. end;
  1690. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1691. procedure TutlCommandPrompt.Start;
  1692. begin
  1693. Reset;
  1694. fRunning := true;
  1695. while fRunning do begin
  1696. fInput := ReadLnEx;
  1697. AddHistory(fInput);
  1698. DoInput;
  1699. end;
  1700. end;
  1701. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1702. procedure TutlCommandPrompt.Stop;
  1703. begin
  1704. fRunning := false;
  1705. end;
  1706. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1707. procedure TutlCommandPrompt.Reset;
  1708. begin
  1709. CursorToEnd;
  1710. DelInput(true);
  1711. fCurrent := fPrefix;
  1712. fCurID := 1;
  1713. Restore;
  1714. end;
  1715. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1716. procedure TutlCommandPrompt.Clear;
  1717. begin
  1718. fConsoleCS.Enter;
  1719. try
  1720. DelInput(true);
  1721. finally
  1722. fConsoleCS.Leave;
  1723. end;
  1724. end;
  1725. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1726. procedure TutlCommandPrompt.Restore;
  1727. begin
  1728. fConsoleCS.Enter;
  1729. try
  1730. RestoreInput;
  1731. finally
  1732. fConsoleCS.Leave;
  1733. end;
  1734. end;
  1735. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1736. constructor TutlCommandPrompt.Create(const aConsoleCS: syncobjs.TCriticalSection);
  1737. begin
  1738. inherited Create;
  1739. fConsoleCS := aConsoleCS;
  1740. fPrefix := COMMAND_PROMPT_PREFIX;
  1741. fHistory := TStringList.Create;
  1742. fHistoryEnabled := true;
  1743. fStartIndex := Length(fPrefix)+1;
  1744. end;
  1745. destructor TutlCommandPrompt.Destroy;
  1746. begin
  1747. FreeAndNil(fHistory);
  1748. inherited Destroy;
  1749. end;
  1750. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1751. //TutlConsoleMenu//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1752. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1753. procedure TutlConsoleMenu.CommandInput(aSender: TObject; const aCmd: String);
  1754. begin
  1755. if fIsAsking then begin
  1756. fIsAsking := false;
  1757. fCommandPrompt.Current := fInputBackup;
  1758. fCommandPrompt.Prefix := COMMAND_PROMPT_PREFIX;
  1759. fCommandPrompt.OnAutoComplete := @AutoComplete;
  1760. fCommandPrompt.HiddenChar := #0;
  1761. fCommandPrompt.HistoryEnabled := true;
  1762. DoAnswer(aCmd);
  1763. end else begin
  1764. utlLogger.Debug(Self, 'CMD: ' + aCmd, []);
  1765. ExecuteCommand(aCmd);
  1766. end;
  1767. end;
  1768. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1769. function TutlConsoleMenu.AutoComplete(aSender: TObject; const aCmd: String;
  1770. const aDisplayPossibilities: Boolean): String;
  1771. var
  1772. r: TutlParseResult;
  1773. s, cmd: String;
  1774. c: Char;
  1775. cmdList: TStringList;
  1776. i, CharIndex, MaxLength: Integer;
  1777. function TestParam(const aParam: String): Boolean;
  1778. var
  1779. i: Integer;
  1780. c: QWord;
  1781. begin
  1782. if aParam = '[integer]' then
  1783. result := TryStrToInt(s, i)
  1784. else if aParam = '[hex]' then
  1785. result := AnsiStartsStr('0x', s) and TryStrToQWord('$' + copy(s, 3, MaxInt), c)
  1786. else
  1787. result := true;
  1788. end;
  1789. function NextChar: Char;
  1790. var
  1791. i: Integer;
  1792. c: String;
  1793. begin
  1794. result := #0;
  1795. try
  1796. for i := 0 to cmdList.Count-1 do begin
  1797. c := cmdList[i];
  1798. if (Length(c) > 0) and (c[1] <> '[') then begin
  1799. if (Length(c) >= CharIndex) then begin
  1800. if (result = #0) then
  1801. result := c[CharIndex]
  1802. else if (result <> c[CharIndex]) then begin
  1803. result := #0;
  1804. exit;
  1805. end;
  1806. end;
  1807. end;
  1808. end;
  1809. finally
  1810. inc(CharIndex);
  1811. end;
  1812. end;
  1813. function GetMaxLength: Integer;
  1814. var
  1815. i, l: Integer;
  1816. begin
  1817. result := 0;
  1818. for i := 0 to cmdList.Count-1 do begin
  1819. l := Length(cmdList[i]);
  1820. if l > result then
  1821. result := l;
  1822. end;
  1823. end;
  1824. begin
  1825. SplitCmdString(aCmd, ' ');
  1826. s := '';
  1827. if (Length(aCmd) > 0) and (aCmd[Length(aCmd)] <> ' ') then begin
  1828. s := fCmdStack[fCmdStack.Count-1];
  1829. fCmdStack.Delete(fCmdStack.Count-1);
  1830. end;
  1831. r := ParseCommand;
  1832. result := aCmd;
  1833. if (r in [prSuccess, prIncompleteCmd, prInvalidParamCount]) and Assigned(fCurrentMenu) then begin
  1834. cmdList := TStringList.Create;
  1835. try
  1836. fCurrentMenu.GetAutoCompleteStrings(cmdList, fCmdParameter);
  1837. if (s <> '') then begin
  1838. for i := cmdList.Count-1 downto 0 do begin
  1839. cmd := cmdList[i];
  1840. if (Length(cmd) > 0) and (cmd[1] = '[') then begin
  1841. if not TestParam(cmd) then
  1842. cmdList.Delete(i);
  1843. end else if not AnsiStartsStr(s, cmd) then
  1844. cmdList.Delete(i);
  1845. end;
  1846. end;
  1847. CharIndex := Length(s)+1;
  1848. c := NextChar;
  1849. while c <> #0 do begin
  1850. result := result + c;
  1851. c := NextChar;
  1852. end;
  1853. if (cmdList.Count = 1) then
  1854. result := result + ' ';
  1855. if aDisplayPossibilities then begin
  1856. WriteLn('');
  1857. s := '';
  1858. MaxLength := GetMaxLength+5;
  1859. for i := 0 to cmdList.Count-1 do begin
  1860. cmd := cmdList[i];
  1861. s := s + cmd + StringOfChar(' ', MaxLength-Length(cmd));
  1862. if ((i+1) mod 5) = 0 then begin
  1863. writeln(s);
  1864. s := '';
  1865. end;
  1866. end;
  1867. if (s <> '') then
  1868. WriteLn(s);
  1869. if (cmdList.Count = 0) then
  1870. WriteLn('[no possible commands]');
  1871. end;
  1872. finally
  1873. cmdList.Free;
  1874. end;
  1875. end;
  1876. end;
  1877. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1878. procedure TutlConsoleMenu.DoAnswer(const aInput: String);
  1879. begin
  1880. if Assigned(fOnAnswer) then
  1881. fOnAnswer(self, aInput);
  1882. end;
  1883. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1884. procedure TutlConsoleMenu.ExecuteCommand(const aCmd: String);
  1885. begin
  1886. if (LowerCase(AnsiLeftStr(trim(aCmd), 4)) = 'exit') then
  1887. ExitMenu
  1888. else
  1889. inherited ExecuteCommand(aCmd);
  1890. end;
  1891. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1892. procedure TutlConsoleMenu.StartMenu;
  1893. begin
  1894. fCommandPrompt.Start;
  1895. end;
  1896. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1897. procedure TutlConsoleMenu.ExitMenu;
  1898. begin
  1899. fCommandPrompt.Stop;
  1900. end;
  1901. procedure TutlConsoleMenu.Ask(const aQuestion: String; const aHidden: Boolean; const aOnAnswer: TutlInputEvent);
  1902. begin
  1903. if Assigned(aOnAnswer) then
  1904. fOnAnswer := aOnAnswer;
  1905. fIsAsking := true;
  1906. fInputBackup := fCommandPrompt.Current;
  1907. if aHidden then
  1908. fCommandPrompt.HiddenChar := '*'
  1909. else
  1910. fCommandPrompt.HiddenChar := #0;
  1911. fCommandPrompt.HistoryEnabled := false;
  1912. fCommandPrompt.OnAutoComplete := nil;
  1913. fCommandPrompt.Prefix := aQuestion;
  1914. fCommandPrompt.Current := '';
  1915. end;
  1916. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1917. constructor TutlConsoleMenu.Create(const aHelp: String; const aConsoleCS: syncobjs.TCriticalSection);
  1918. begin
  1919. inherited Create(aHelp);
  1920. fExitMenu := TutlMenuItem.Create(self, 'exit', 'exit programm', nil);
  1921. fCommandPrompt := TutlCommandPrompt.Create(aConsoleCS);
  1922. fCommandPrompt.OnAutoComplete := @AutoComplete;
  1923. fCommandPrompt.OnInput := @CommandInput;
  1924. end;
  1925. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1926. destructor TutlConsoleMenu.Destroy;
  1927. begin
  1928. FreeAndNil(fCommandPrompt);
  1929. FreeAndNil(fExitMenu);
  1930. inherited Destroy;
  1931. end;
  1932. end.