mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-11 18:22:36 +02:00
444 lines
10 KiB
ObjectPascal
444 lines
10 KiB
ObjectPascal
unit CommandLineOptions;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, Contnrs;
|
|
|
|
type
|
|
|
|
{ TOption }
|
|
|
|
TOption = class
|
|
Names: array of String;
|
|
Values: array of String;
|
|
Identifier: Integer;
|
|
HasArg: Boolean;
|
|
Present: Boolean;
|
|
Help: String;
|
|
MultipleValues: Boolean;
|
|
function LongestName: String;
|
|
function Value: String;
|
|
procedure AddValue(AValue: String);
|
|
end;
|
|
TCommandLineOptions = class;
|
|
|
|
|
|
TOptionReadError = procedure(Sender: TObject; ErrorMessage: String) of object;
|
|
|
|
{ TCommandLineOptions }
|
|
|
|
TCommandLineOptions = class
|
|
private
|
|
FOnError: TOptionReadError;
|
|
FOptions: TObjectList;
|
|
FUnassignedArgs: TStringList;
|
|
FStopReading: Boolean;
|
|
function FindOptionByName(AName: String): TOption;
|
|
function FindOptionByIdentifier(AIdentifier: Integer): TOption;
|
|
procedure DoError(ErrorMessage: String); virtual;
|
|
public
|
|
// first setup options
|
|
procedure SetOptions(ShortOptions: String; LongOptions: array of String);
|
|
procedure AddOption(OptionNames: array of String; HasArg: Boolean = False; Help: String = ''; CanUseMultipleTimes: Boolean = False; Identifier: Integer = -1);
|
|
// read from commandline
|
|
procedure ReadOptions;
|
|
|
|
// string based
|
|
function HasOption(AName: String): Boolean;
|
|
function OptionValue(AName:String): String;
|
|
function OptionValues(AName: String): TStrings;
|
|
|
|
// tag based
|
|
function HasOption(AIdentifier: Integer): Boolean;
|
|
function OptionValue(AIdentifier: Integer): String;
|
|
function OptionValues(AIdentifier: Integer): TStrings;
|
|
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
function PrintHelp(MaxLineWidth: Integer): TStrings; virtual;
|
|
property OnError: TOptionReadError read FOnError write FOnError;
|
|
property OptionsMalformed: Boolean read FStopReading;
|
|
end;
|
|
resourcestring
|
|
ErrUnknownOption = 'Option unknown: "%s"';
|
|
ErrArgNeededNotPossible = 'Option "%s" requires an argument but an argument is not possible. (Hint: Use "%s" as last option in group "-%s" or use long option --%s)';
|
|
ErrArgumentNeeded = 'Option "%s" requires an argument';
|
|
ErrOptionHasNoArgument = 'Option "%s" does not accept arguments';
|
|
ErrOnlyOneInstance = 'Option "%s" cannot be used more than once';
|
|
ErrNoEqualsAllowed = 'Symbol "=" not allowed in argument group "-%s"';
|
|
implementation
|
|
|
|
{ TOption }
|
|
|
|
function TOption.LongestName: String;
|
|
var
|
|
N: String;
|
|
begin
|
|
Result := '';
|
|
for N in Names do
|
|
begin
|
|
if Length(N) > Length(Result) then
|
|
Result := N;
|
|
end;
|
|
end;
|
|
|
|
function TOption.Value: String;
|
|
begin
|
|
if Length(Values) > 0 then
|
|
Exit(Values[0])
|
|
else
|
|
Result := '';
|
|
end;
|
|
|
|
procedure TOption.AddValue(AValue: String);
|
|
begin
|
|
SetLength(Values, Length(Values)+1);
|
|
Values[High(Values)] := AValue;
|
|
end;
|
|
|
|
{ TCommandLineOptions }
|
|
|
|
function TCommandLineOptions.FindOptionByName(AName: String): TOption;
|
|
var
|
|
Opt: TOption;
|
|
N: String;
|
|
begin
|
|
Result := Nil;
|
|
for Pointer(Opt) in FOptions do
|
|
begin
|
|
for N in Opt.Names do
|
|
if AName = N then
|
|
Exit(Opt)
|
|
end;
|
|
end;
|
|
|
|
function TCommandLineOptions.FindOptionByIdentifier(AIdentifier: Integer
|
|
): TOption;
|
|
begin
|
|
Result := Nil;
|
|
end;
|
|
|
|
procedure TCommandLineOptions.DoError(ErrorMessage: String);
|
|
begin
|
|
FStopReading:=True;
|
|
if Assigned(FOnError) then
|
|
FOnError(Self, ErrorMessage)
|
|
else
|
|
WriteLn(ErrorMessage);
|
|
end;
|
|
|
|
procedure TCommandLineOptions.SetOptions(ShortOptions: String;
|
|
LongOptions: array of String);
|
|
var
|
|
L: String;
|
|
S: String;
|
|
HasArg: Boolean;
|
|
P,
|
|
E: PChar;
|
|
begin
|
|
P:= PChar(ShortOptions);
|
|
E := P + Length(ShortOptions);
|
|
|
|
for L in LongOptions do
|
|
begin
|
|
S := P[0];
|
|
if P+1 < E then
|
|
HasArg:=P[1] = ':';
|
|
Inc(P, 1+Ord(HasArg));
|
|
AddOption([S, L], HasArg);
|
|
end;
|
|
end;
|
|
|
|
procedure TCommandLineOptions.AddOption(OptionNames: array of String;
|
|
HasArg: Boolean; Help: String; CanUseMultipleTimes: Boolean; Identifier: Integer);
|
|
var
|
|
Opt: TOption;
|
|
C: Integer;
|
|
begin
|
|
Opt := TOption.Create;
|
|
C := Length(OptionNames);
|
|
SetLength(Opt.Names, C);
|
|
for C := Low(OptionNames) to High(OptionNames) do
|
|
Opt.Names[C] := OptionNames[C];
|
|
Opt.HasArg:=HasArg;
|
|
Opt.Identifier:=Identifier;
|
|
Opt.MultipleValues:=CanUseMultipleTimes;
|
|
Opt.Help:=Help;
|
|
FOptions.Add(Opt);
|
|
end;
|
|
|
|
procedure TCommandLineOptions.ReadOptions;
|
|
var
|
|
OptIndex: Integer;
|
|
procedure ReadOption(S, G: String; OptionPossible: Boolean);
|
|
var
|
|
Opt: TOption;
|
|
Arg: String;
|
|
HasEq: Integer = 0;
|
|
begin
|
|
HasEq := Pos('=', S);
|
|
if HasEq > 0 then
|
|
begin
|
|
Arg := Copy(S, HasEq+1, Length(S));
|
|
S := Copy(S,1, HasEq-1);
|
|
end;
|
|
|
|
Opt := FindOptionByName(S);
|
|
if Opt = Nil then
|
|
begin
|
|
DoError(Format(ErrUnknownOption, [S]));
|
|
Exit;
|
|
end;
|
|
if Opt.HasArg and not OptionPossible then
|
|
begin
|
|
DoError(Format(ErrArgNeededNotPossible, [S, S, G, Opt.LongestName]));
|
|
Exit;
|
|
end;
|
|
|
|
if Opt.HasArg then
|
|
begin
|
|
if (OptIndex = Paramcount) and (HasEq = 0) then
|
|
begin
|
|
DoError(Format(ErrArgumentNeeded, [S]));
|
|
Exit;
|
|
end;
|
|
if Opt.Present and not Opt.MultipleValues then
|
|
begin
|
|
DoError(Format(ErrOnlyOneInstance, [S]));
|
|
Exit;
|
|
end;
|
|
|
|
// Verify???
|
|
|
|
if HasEq = 0 then
|
|
begin
|
|
Arg := ParamStr(OptIndex+1);
|
|
Inc(OptIndex);
|
|
end;
|
|
Opt.AddValue(Arg);
|
|
end
|
|
else if HasEq > 0 then
|
|
begin
|
|
DoError(Format(ErrOptionHasNoArgument, [S]));
|
|
end;
|
|
|
|
Opt.Present:=True;
|
|
end;
|
|
|
|
procedure ReadSingleOptions(S: String);
|
|
var
|
|
I: Integer;
|
|
begin
|
|
if S[1] = '-' then // its a long option with 2 dashes : --option
|
|
ReadOption(Copy(S,2,Length(S)), '', True)
|
|
else // short options put together : -abcdefg
|
|
begin
|
|
if Pos('=', S) > 0 then
|
|
begin
|
|
DoError(Format(ErrNoEqualsAllowed,[S]));
|
|
Exit;
|
|
end;
|
|
for I := 1 to Length(S) do
|
|
ReadOption(S[I], S, I = Length(S));
|
|
end;
|
|
end;
|
|
var
|
|
RawOpt: String;
|
|
begin
|
|
OptIndex:=0;
|
|
while OptIndex < Paramcount do
|
|
begin
|
|
if FStopReading then
|
|
Exit;
|
|
Inc(OptIndex);
|
|
RawOpt := ParamStr(OptIndex);
|
|
if (RawOpt[1] = '-') and (RawOpt <> '-') then // '-' is treated as an unassigned arg.
|
|
ReadSingleOptions(Copy(RawOpt,2,Length(RawOpt)))
|
|
else
|
|
FUnassignedArgs.Add(RawOpt);
|
|
end;
|
|
end;
|
|
|
|
function TCommandLineOptions.HasOption(AName: String): Boolean;
|
|
var
|
|
Opt: TOption;
|
|
begin
|
|
Result := True;
|
|
Opt := FindOptionByName(AName);
|
|
if (Opt = nil) or not(Opt.Present) then
|
|
Result := False;
|
|
end;
|
|
|
|
function TCommandLineOptions.OptionValue(AName: String): String;
|
|
var
|
|
Opt: TOption;
|
|
begin
|
|
Opt := FindOptionByName(AName);
|
|
Result := Opt.Value;
|
|
end;
|
|
|
|
function TCommandLineOptions.OptionValues(AName: String): TStrings;
|
|
var
|
|
Opt: TOption;
|
|
S: String;
|
|
begin
|
|
Opt := FindOptionByName(AName);
|
|
Result := TStringList.Create;
|
|
if Opt = nil then
|
|
Exit;
|
|
for S in Opt.Values do
|
|
Result.Add(S);
|
|
end;
|
|
|
|
function TCommandLineOptions.HasOption(AIdentifier: Integer): Boolean;
|
|
var
|
|
Opt: TOption;
|
|
begin
|
|
Result := False;
|
|
Opt := FindOptionByIdentifier(AIdentifier);
|
|
if Opt = nil then
|
|
Exit;
|
|
|
|
Result := Opt.Present;
|
|
end;
|
|
|
|
function TCommandLineOptions.OptionValue(AIdentifier: Integer): String;
|
|
var
|
|
Opt: TOption;
|
|
begin
|
|
Result := '';
|
|
Opt := FindOptionByIdentifier(AIdentifier);
|
|
if Opt = nil then
|
|
Exit;
|
|
|
|
Result := Opt.Value;
|
|
end;
|
|
|
|
function TCommandLineOptions.OptionValues(AIdentifier: Integer): TStrings;
|
|
var
|
|
Opt: TOption;
|
|
Tmp: String;
|
|
begin
|
|
Result := TStringList.Create;
|
|
Opt := FindOptionByIdentifier(AIdentifier);
|
|
if Opt = nil then
|
|
Exit;
|
|
|
|
for Tmp in Opt.Values do
|
|
Result.Add(Tmp);
|
|
end;
|
|
|
|
constructor TCommandLineOptions.Create;
|
|
begin
|
|
FOptions := TObjectList.create(True);
|
|
FUnassignedArgs := TStringList.Create;
|
|
end;
|
|
|
|
destructor TCommandLineOptions.Destroy;
|
|
begin
|
|
FOptions.Clear;
|
|
FOptions.Free;
|
|
FUnassignedArgs.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TCommandLineOptions.PrintHelp(MaxLineWidth: Integer): TStrings;
|
|
var
|
|
Padding: array [0..255] of char;
|
|
function Space(Orig: String; LengthNeeded: Integer; Before: Boolean = False): String;
|
|
begin
|
|
if not Before then
|
|
Result := Orig+Copy(Padding,0,LengthNeeded-Length(Orig))
|
|
else
|
|
Result := Copy(Padding,0,LengthNeeded-Length(Orig))+Orig;
|
|
end;
|
|
|
|
var
|
|
Opt: TOption;
|
|
Tmp: String;
|
|
Line: String;
|
|
LinePart: String;
|
|
I, J: Integer;
|
|
S,L,D: TStringList; // short opt, long opt, description
|
|
SL, LL: String; // short line, long line
|
|
SLL, LLL: Integer; //short line length, long line length
|
|
LineSize: Integer;
|
|
Gap: Integer;
|
|
begin
|
|
FillChar(Padding, 256, ' ');
|
|
S := TStringList.Create;
|
|
L := TStringList.Create;
|
|
D := TStringList.Create;
|
|
Result := TStringList.Create;
|
|
for I := 0 to FOptions.Count-1 do
|
|
begin
|
|
SL := '';
|
|
LL := '';
|
|
Line := '';
|
|
Opt := TOption(FOptions.Items[I]);
|
|
for Tmp in Opt.Names do
|
|
if Length(Tmp) = 1 then
|
|
SL := SL + ' -' + Tmp
|
|
else
|
|
LL := LL + ' --' + Tmp;
|
|
|
|
S.Add(SL);
|
|
L.Add(LL);
|
|
D.Add(Opt.Help);
|
|
end;
|
|
SLL := 0;
|
|
LLL := 0;
|
|
|
|
for Tmp in S do
|
|
if Length(Tmp) > SLL then
|
|
SLL := Length(Tmp);
|
|
|
|
for Tmp in L do
|
|
if Length(Tmp) > LLL then
|
|
LLL := Length(Tmp);
|
|
|
|
|
|
for I := 0 to S.Count-1 do
|
|
begin
|
|
LinePart := '';
|
|
SL := Space(S[I], SLL);
|
|
LL := Space(L[I], LLL);
|
|
Line := SL + ' ' + LL + ' '+ D[I];
|
|
if Length(Line) > MaxLineWidth then
|
|
begin
|
|
LineSize:=MaxLineWidth;
|
|
Gap := 0;
|
|
repeat
|
|
J := LineSize;
|
|
//if J > Length(Line) then J := Length(Line);
|
|
while (J > 0){ and (Length(Line) > 0)} do
|
|
begin
|
|
if (Line[J] = ' ') or (J = 1) then
|
|
begin
|
|
LinePart := Copy(Line, 1, J);
|
|
LinePart := Space(LinePart, Length(LinePart)+Gap, True);
|
|
Delete(Line,1,J);
|
|
Result.Add(LinePart);
|
|
break;
|
|
end;
|
|
Dec(J);
|
|
end;
|
|
Gap := SLL+1+LLL+4;
|
|
LineSize := MaxLineWidth-(Gap);
|
|
until Length(Line) = 0;
|
|
end
|
|
else
|
|
Result.Add(Line);
|
|
end;
|
|
S.Free;
|
|
L.Free;
|
|
D.Free;
|
|
|
|
end;
|
|
|
|
end.
|
|
|