-
-
Notifications
You must be signed in to change notification settings - Fork 135
/
Copy pathmormot.app.console.pas
376 lines (340 loc) · 12.8 KB
/
mormot.app.console.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
/// Console Applications Support
// - this unit is a part of the Open Source Synopse mORMot framework 2,
// licensed under a MPL/GPL/LGPL three license - see LICENSE.md
unit mormot.app.console;
{
*****************************************************************************
Some Features Dedicated to Console Apps
- ICommandLine for Parsing Command Line Arguments
*****************************************************************************
}
interface
{$I ..\mormot.defines.inc}
uses
sysutils,
classes,
variants,
mormot.core.base,
mormot.core.os,
mormot.core.unicode,
mormot.core.buffers,
mormot.core.text,
mormot.core.datetime,
mormot.core.rtti,
mormot.core.data,
mormot.core.variants;
{ ************ ICommandLine for Parsing Command Line Arguments }
type
/// an interface to process the command line switches over a console
// - as implemented e.g. by TCommandLine class
// - can implement any process, optionally with console interactivity
ICommandLine = interface
['{77AB427C-1025-488B-8E04-3E62C8100E62}']
/// returns a command line switch value as UTF-8 text
// - you can specify a prompt text, when asking for any missing switch
function AsUtf8(const Switch, Default: RawUtf8;
const Prompt: string): RawUtf8;
/// returns a command line switch value as RTL string text
// - you can specify a prompt text, when asking for any missing switch
function AsString(const Switch: RawUtf8; const Default: string;
const Prompt: string): string;
/// returns a command line switch value as integer
// - you can specify a prompt text, when asking for any missing switch
function AsInt(const Switch: RawUtf8; Default: Int64;
const Prompt: string): Int64;
/// returns a command line switch ISO-8601 value as date value
// - here dates are expected to be encoded with ISO-8601, i.e. YYYY-MM-DD
// - you can specify a prompt text, when asking for any missing switch
function AsDate(const Switch: RawUtf8; Default: TDateTime;
const Prompt: string): TDateTime;
/// returns a command line switch value as enumeration ordinal
// - RTTI will be used to check for the enumeration text, or plain integer
// value will be returned as ordinal value
// - you can specify a prompt text, when asking for any missing switch
function AsEnum(const Switch, Default: RawUtf8; TypeInfo: pointer;
const Prompt: string): integer;
/// returns all command line values as an array of UTF-8 text
// - i.e. won't interpret the various switches in the input parameters
// - as created e.g. by TCommandLine.CreateAsArray constructor
function AsArray: TRawUtf8DynArray;
/// serialize all recognized switches as UTF-8 JSON text
function AsJson(Format: TTextWriterJsonFormat): RawUtf8;
/// equals TRUE if the -noprompt switch has been supplied
// - may be used to force pure execution without console interaction,
// e.g. when run from another process
function NoPrompt: boolean;
/// change the console text color
// - do nothing if NoPrompt is TRUE
procedure TextColor(Color: TConsoleColor);
/// write some console text, with an optional color
// - will output the text even if NoPrompt is TRUE
procedure Text(const Fmt: RawUtf8; const Args: array of const;
Color: TConsoleColor = ccLightGray);
end;
/// a class to process the command line switches, with console interactivity
// - is able to redirect all Text() output to an internal UTF-8 storage,
// in addition or instead of the console (to be used e.g. from a GUI)
// - implements ICommandLine interface
TCommandLine = class(TInterfacedPersistent, ICommandLine)
private
fValues: TDocVariantData;
fNoPrompt: boolean;
fNoConsole: boolean;
fLines: TRawUtf8DynArray;
procedure SetNoConsole(value: boolean);
public
/// initialize the internal storage from the command line
// - will parse "-switch1 value1 -switch2 value2" layout
// - stand-alone "-switch1 -switch2 value2" will a create switch1=true value
constructor Create; overload; override;
/// initialize the internal storage from the command line
// - will set paramstr(firstParam)..paramstr(paramcount) in fValues as array
// - may be used e.g. for "val1 val2 val3" command line layout
constructor CreateAsArray(firstParam: integer);
/// initialize the internal storage with some ready-to-use switches
// - will also set the NoPrompt option, and set the supplied NoConsole value
// - may be used e.g. from a graphical interface instead of console mode
constructor Create(const switches: variant; aNoConsole: boolean = true);
reintroduce; overload;
/// initialize the internal storage with some ready-to-use name/value pairs
// - will also set the NoPrompt option, and set the supplied NoConsole value
// - may be used e.g. from a graphical interface instead of console mode
constructor Create(const NameValuePairs: array of const;
aNoConsole: boolean = true); reintroduce; overload;
/// returns a command line switch value as UTF-8 text
// - you can specify a prompt text, when asking for any missing switch
function AsUtf8(const Switch, Default: RawUtf8;
const Prompt: string): RawUtf8;
/// returns a command line switch value as RTL string text
// - you can specify a prompt text, when asking for any missing switch
function AsString(const Switch: RawUtf8; const Default: string;
const Prompt: string): string;
/// returns a command line switch value as integer
// - you can specify a prompt text, when asking for any missing switch
function AsInt(const Switch: RawUtf8; Default: Int64;
const Prompt: string): Int64;
/// returns a command line switch ISO-8601 value as date value
// - here dates are expected to be encoded with ISO-8601, i.e. YYYY-MM-DD
// - you can specify a prompt text, when asking for any missing switch
function AsDate(const Switch: RawUtf8; Default: TDateTime;
const Prompt: string): TDateTime;
/// returns a command line switch value as enumeration ordinal
// - RTTI will be used to check for the enumeration text, or plain integer
// value will be returned as ordinal value
// - you can specify a prompt text, when asking for any missing switch
function AsEnum(const Switch, Default: RawUtf8; TypeInfo: pointer;
const Prompt: string): integer;
/// returns all command line values as an array of UTF-8 text
// - i.e. won't interpret the various switches in the input parameters
// - as created e.g. by TCommandLine.CreateAsArray constructor
function AsArray: TRawUtf8DynArray;
/// serialize all recognized switches as UTF-8 JSON text
function AsJson(Format: TTextWriterJsonFormat): RawUtf8;
/// equals TRUE if the -noprompt switch has been supplied
// - may be used to force pure execution without console interaction,
// e.g. when run from another process
function NoPrompt: boolean;
/// change the console text color
// - do nothing if NoPrompt is TRUE
procedure TextColor(Color: TConsoleColor);
/// write some console text, with an optional color
// - will output the text even if NoPrompt=TRUE, but not if NoConsole=TRUE
// - will append the text to the internal storage, available from ConsoleText
procedure Text(const Fmt: RawUtf8; const Args: array of const;
Color: TConsoleColor = ccLightGray);
/// returns the UTF-8 text as inserted by Text() calls
// - line feeds will be included to the ConsoleLines[] values
function ConsoleText(const LineFeed: RawUtf8 = CRLF): RawUtf8;
/// low-level access to the internal switches storage
property Values: TDocVariantData
read fValues;
/// if Text() should be redirected to ConsoleText internal storage
// - and don't write anything to the console
// - should be associated with NoProperty = TRUE property
property NoConsole: boolean
read fNoConsole write SetNoConsole;
/// low-level access to the internal UTF-8 console lines storage
property ConsoleLines: TRawUtf8DynArray
read fLines;
end;
implementation
{ ************ ICommandLine for Parsing Command Line Arguments }
{ TCommandLine }
constructor TCommandLine.Create;
var
n, i: integer;
p, sw: RawUtf8;
begin
inherited Create;
n := ParamCount;
if n < 0 then
n := 0; // may equal -1 e.g. from a .so on MacOS
fValues.InitFast(n shr 1, dvObject);
for i := 1 to n do
begin
p := StringToUtf8(ParamStr(i));
if p <> '' then
if p[1] in ['-', '/'] then
begin
if {%H-}sw <> '' then
fValues.AddValue(sw, true); // -flag -switch value -> flag=true
sw := LowerCase(copy(p, 2, 100));
if sw = 'noprompt' then
begin
fNoPrompt := true;
sw := '';
end;
end
else if sw <> '' then
begin
fValues.AddValueFromText(sw, p, true);
sw := '';
end;
end;
if sw <> '' then
fValues.AddValue(sw, true); // trailing -flag
end;
constructor TCommandLine.Create(const switches: variant;
aNoConsole: boolean);
begin
inherited Create;
fValues.InitCopy(switches, JSON_FAST);
fNoPrompt := true;
fNoConsole := aNoConsole;
end;
constructor TCommandLine.Create(const NameValuePairs: array of const;
aNoConsole: boolean);
begin
inherited Create;
fValues.InitObject(NameValuePairs, JSON_FAST);
fNoPrompt := true;
fNoConsole := aNoConsole;
end;
constructor TCommandLine.CreateAsArray(firstParam: integer);
var
n, i: integer;
begin
inherited Create;
n := ParamCount;
if n < 0 then
n := 0; // may equal -1 e.g. from a .so on MacOS
fValues.InitFast(n, dvArray);
for i := firstParam to n do
fValues.AddItem(ParamStr(i));
end;
function TCommandLine.NoPrompt: boolean;
begin
result := fNoPrompt;
end;
function TCommandLine.ConsoleText(const LineFeed: RawUtf8): RawUtf8;
begin
result := RawUtf8ArrayToCsv(fLines, LineFeed);
end;
procedure TCommandLine.SetNoConsole(value: boolean);
begin
if value = fNoConsole then
exit;
if value then
fNoPrompt := true;
fNoConsole := false;
end;
procedure TCommandLine.TextColor(Color: TConsoleColor);
begin
if not fNoPrompt then
mormot.core.os.TextColor(Color);
end;
procedure TCommandLine.Text(const Fmt: RawUtf8; const Args: array of const;
Color: TConsoleColor);
var
msg: RawUtf8;
begin
FormatUtf8(Fmt, Args, msg);
if msg <> '' then
AddRawUtf8(fLines, msg);
if not fNoConsole then
ConsoleWrite(msg, Color);
end;
function TCommandLine.AsUtf8(const Switch, Default: RawUtf8;
const Prompt: string): RawUtf8;
var
i: integer;
begin
i := fValues.GetValueIndex(Switch);
if i >= 0 then
begin
// found
VariantToUtf8(fValues.Values[i], result{%H-});
fValues.Delete(i);
exit;
end;
result := Default;
if fNoPrompt or
(Prompt = '') or
not HasConsole then
exit;
TextColor(ccLightGray);
{$I-}
writeln(Prompt);
if ioresult <> 0 then
exit; // no console -> no prompt
TextColor(ccCyan);
write(Switch);
if Default <> '' then
write(' [', Default, '] ');
write(': ');
TextColor(ccWhite);
readln(result);
writeln;
ioresult;
{$I+}
TextColor(ccLightGray);
TrimSelf(result);
if result = '' then
result := Default;
end;
function TCommandLine.AsInt(const Switch: RawUtf8; Default: Int64;
const Prompt: string): Int64;
var
res: RawUtf8;
begin
res := AsUtf8(Switch, Int64ToUtf8(Default), Prompt);
result := GetInt64Def(pointer(res), Default);
end;
function TCommandLine.AsDate(const Switch: RawUtf8; Default: TDateTime;
const Prompt: string): TDateTime;
var
res: RawUtf8;
begin
res := AsUtf8(Switch, DateTimeToIso8601Text(Default), Prompt);
if res = '0' then
begin
result := 0;
exit;
end;
result := Iso8601ToDateTime(res);
if result = 0 then
result := Default;
end;
function TCommandLine.AsEnum(const Switch, Default: RawUtf8; TypeInfo: pointer;
const Prompt: string): integer;
var
res: RawUtf8;
begin
res := AsUtf8(Switch, Default, Prompt);
if not ToInteger(res, result) then
result := GetEnumNameValue(TypeInfo, pointer(res), length(res), true);
end;
function TCommandLine.AsArray: TRawUtf8DynArray;
begin
fValues.ToRawUtf8DynArray(result);
end;
function TCommandLine.AsJson(Format: TTextWriterJsonFormat): RawUtf8;
begin
result := fValues.ToJson('', '', Format);
end;
function TCommandLine.AsString(const Switch: RawUtf8;
const Default, Prompt: string): string;
begin
Utf8ToStringVar(AsUtf8(Switch, StringToUtf8(Default), Prompt), result{%H-});
end;
end.