{$mode objfpc}
{$modeswitch autoderef}
unit dgetopt;

interface

uses
  dstrings;

const
    DEFAULT_HELP = 'Underdeveloped :)';

const
    ARGUMENT_NO         =   0;
    ARGUMENT_REQUIRED   =   1;
    ARGUMENT_OPTIONAL   =   2;

type
POpt = ^TOpt;
PFreeOpt = ^TFreeOpt;
POpts = ^TOpts;
PArg = ^TArg;
PArgs = ^TArgs;

TArrayOfString = array of AnsiString;

TArgHandler = procedure (Args: PArgs);

TOpt = object
  FLongNames: TArrayOfString;
  FCharNames: AnsiString;
  FRequired: Boolean;
  FArgQual: LongInt;
  FArgTitle: AnsiString;
  FHasOptionalValue: Boolean;
  FOptionalValue: AnsiString;
  FHasDefaultValue: Boolean;
  FDefaultValue: AnsiString;
  FHelp: AnsiString;
  FHandler: TArgHandler;
  constructor Init;
  destructor Done; virtual;
  function ToShortString: AnsiString;
  function IsName(const Name: AnsiString): Boolean;
  function IsChar(C: AnsiChar): Boolean;
  function GetName: AnsiString;
  procedure AddShortName(C: AnsiChar);
  function GetShortNames: AnsiString;
  function AddLongName(const Name: AnsiString): POpt;
  function GetLongNames: TArrayOfString;
  function GetChar: AnsiChar; inline;
  function GetArgQual: LongInt; inline;
  function SetArgQual(Qual: LongInt): POpt;
  function GetArgTitle: AnsiString;
  function NoArgument: POpt;
  function RequiredArgument(const Title: AnsiString): POpt;
  function OptionalArgument(const Title: AnsiString): POpt;
  function OptionalValue(const Value: AnsiString; const Title: AnsiString): POpt;
  function HasOptionalValue: Boolean;
  function GetOptionalValue: AnsiString;
  function DefaultValue(const Value: AnsiString): POpt;
  function HasDefaultValue: Boolean;
  function GetDefaultValue: AnsiString;
  function Required: POpt;
  function Optional: POpt;
  function IsRequired: Boolean;
  function Help(const Descr: AnsiString): POpt;
  function GetHelp: AnsiString;
  function Handler(_Hanlder: TArgHandler): POpt;
end;

TFreeOpt = object
  FPos: LongInt;
  FTitle: AnsiString;
  FHelp: AnsiString;
  FNamedAlias: POpt;
  constructor Init(const Title, Help: AnsiString; NamedAlias: POpt);
  destructor Done; virtual;
end;

// Options rules
TOpts = object
  FTitle: AnsiString;
  FCustomCmdLine: AnsiString;
  FAllowSingleDashForLong: Boolean;
  FOpts: array of TOpt;
  FAllowUnknownCharOptions: Boolean;
  FAllowUnknownLongOptions: Boolean;
  FFreeArgsMin: LongInt;
  FFreeArgsMax: LongInt;
  FFreeOpts: array of TFreeOpt;
  FDefaultFreeOpt: TFreeOpt;
  FCustomDefaultOpt: Boolean;
public
  constructor Init;
  destructor Done; virtual;
  procedure SetTitle(const Title: AnsiString);
  function FindLongOption(const Name: AnsiString): POpt;
  function FindCharOption(C: AnsiChar): POpt;
  function HasLongOption(const Name: AnsiString): Boolean;
  function HasCharOption(C: AnsiChar): Boolean;
  function GetLongOption(const Name: AnsiString): POpt;
  function GetCharOption(C: AnsiChar): POpt;
  function HasAnyShortOption: boolean;
  function HasAnyLongOption: boolean;
  function AddOption(const Opt: TOpt): POpt;
  procedure AddCharOptions(const OptString: AnsiString);
  function AddCharOption(C: AnsiChar; Help: AnsiString = ''): POpt; overload;
  function AddCharOption(C: AnsiChar; ArgQual: LongInt; Help: AnsiString = ''): POpt; overload;
  function AddLongOption(const Name: AnsiString; Help: AnsiString = ''): POpt; overload;
  function AddLongOption(const Name: AnsiString; ArgQual: LongInt; Help: AnsiString = ''): POpt; overload;
  function AddLongOption(C: AnsiChar; const Name: AnsiString; Help: AnsiString = ''): POpt; overload;
  function AddHelpOption(C: AnsiChar = '?'): POpt;
  function AddVersionOption(C: AnsiChar = 'V'): POpt;
  function CharOption(C: AnsiChar): POpt;
  function IndexOf(Opt: POpt): LongInt;
  procedure SetCmdLineDescr(Descr: AnsiString);
  procedure SetFreeArgsMin(N: LongInt);
  procedure SetFreeArgsMax(N: LongInt);
  procedure SetFreeArgsNum(N: LongInt); overload;
  procedure SetFreeArgsNum(Min, Max: LongInt); overload;
  procedure SetFreeArgTitle(P: LongInt; const Title: AnsiString; const Help: AnsiString); overload;
  procedure SetFreeArgDefaultTitle(const Title: AnsiString; const Help: AnsiString = '');
  procedure SetAllowSingleDashForLong(Value: Boolean);
  function GetFreeArgTitle(N: LongInt): AnsiString;
  function GetFreeArgHelp(N: LongInt): AnsiString;
  // TODO: support Colorizer (Colors argument)
  function FormatOption(Opt: POpt; Color: LongInt): AnsiString;
  procedure PrintOpts(Required: Boolean; Stream: Pointer; Colors: LongInt);
  procedure PrintUsage(const Prog: AnsiString; Stream: Pointer; Colors: LongInt);
  procedure PrintCmdLine(const Prog: AnsiString; Stream: Pointer; Colors: LongInt);
  procedure PrintFreeArgsDescr(const Prog: AnsiString; Stream: Pointer; Colors: LongInt);
end;

TArg = record
  Value: AnsiString;
  Opt: POpt;
end;

// Parsed options
TArgs = object
private
  FOpts: POpts;
  FArgs: array of TArg;
public
  constructor Init; overload;
  constructor Init(constref Opts: TOpts); overload;
  destructor Done; virtual;
  procedure PrintUsage(Colors: LongInt);
  procedure ParseParamStr(constref Opts: TOpts; Start: LongInt = 0; Count: LongInt = -1);
  function FindOptParseResult(Opt: POpt; IncludeDefault: Boolean = False): PArg;
  function FindLongOptParseResult(const Name: AnsiString; IncludeDefault: Boolean = False): PArg;
  function FindCharOptParseResult(const C: AnsiChar; IncludeDefault: Boolean = False): PArg;
  function GetProgramName: AnsiString;
  function GetFreeArgsPos: LongInt;
  function GetFreeArgsCount: LongInt;
  function Has(Opt: POpt; IncludeDefault: Boolean = False): Boolean; overload;
  function Get(Opt: POpt; IncludeDefault: Boolean = True): TAnsiView; overload;
  function Get(Opt: POpt; Def: TAnsiView): TAnsiView; overload;
  function Has(const Name: AnsiString; IncludeDefault: Boolean = True): Boolean; overload;
  function Get(const Name: AnsiString; IncludeDefault: Boolean = True): TAnsiView; overload;
  function Get(const Name: AnsiString; Def: TAnsiView): TAnsiView; overload;
  function HasC(C: AnsiChar; IncludeDefault: Boolean = True): Boolean; overload;
  function GetC(C: AnsiChar; IncludeDefault: Boolean = True): TAnsiView; overload;
  function GetC(C: AnsiChar; Def: TAnsiView): TAnsiView; overload;
  property Args[Name: AnsiString]: TAnsiView read Get; default;
end;

function IsAllowedShortName(C: AnsiChar): Boolean;
function IsAllowedLongName(const Name: AnsiString; Pos: PInteger): Boolean;

var
  Opts: TOpts;
  Args: TArgs;

implementation

procedure PrintUsageAndExit(Args: PArgs);
begin
  Args.PrintUsage(0);
  Halt(0);
end;

procedure PrintVersionAndExit(Args: PArgs);
begin
end;

constructor TOpt.Init;
begin
  //SetLength(FLongNames, 0);
  //FCharNames := '';
  FRequired := False;
  FArgQual := ARGUMENT_NO;
  FArgTitle := '';
  FHasOptionalValue := False;
  //FOptionalValue := '';
  FHasDefaultValue := False;
  //FDefaultValue := '';
  //FHelp := '';
end;

destructor TOpt.Done;
begin
end;

function TOpt.ToShortString: AnsiString;
begin
  if Length(FCharNames) > 0 then begin
    Exit(FCharNames[1]);
  end else if Length(FLongNames) > 0 then begin
    Exit(FLongNames[0]);
  end else
    Exit('');
end;

function TOpt.IsName(const Name: AnsiString): Boolean;
var
  I: LongInt;
begin
  for I := 0 to High(FLongNames) do
    if FLongNames[I] = Name then
      Exit(True);
  Exit(False);
end;

function TOpt.IsChar(C: AnsiChar): Boolean;
var
  I: LongInt;
begin
  for I := 1 to Length(FCharNames) do
    if FCharNames[I] = C then
      Exit(True);
  Exit(False);
end;

function TOpt.GetName: AnsiString;
begin
  if Length(FLongNames) > 0 then begin
    Exit(FLongNames[0]);
  end else
    Exit('');
end;

procedure TOpt.AddShortName(C: AnsiChar);
begin
  FCharNames := FCharNames + C;
end;

function TOpt.GetShortNames: AnsiString;
begin
  Result := FCharNames;
end;

function TOpt.AddLongName(const Name: AnsiString): POpt;
begin
  SetLength(FLongNames, Length(FLongNames) + 1);
  FLongNames[High(FLongNames)] := Name;
  Result := @FLongNames[High(FLongNames)];
end;

function TOpt.GetLongNames: TArrayOfString;
begin
  Result := FLongNames;
end;

function TOpt.GetChar: AnsiChar; inline;
begin
  if Length(FCharNames) > 0 then begin
    Exit(FCharNames[1]);
  end else
    Exit(#0);
end;

function TOpt.GetArgQual: LongInt; inline;
begin
  Result := FArgQual;
end;

function TOpt.SetArgQual(Qual: LongInt): POpt;
begin
  FArgQual := Qual;
  Result := @Self;
end;

function TOpt.GetArgTitle: AnsiString;
begin
  Result := FArgTitle;
end;

function TOpt.NoArgument: POpt;
begin
  FArgQual := ARGUMENT_NO;
  Result := @Self;
end;

function TOpt.RequiredArgument(const Title: AnsiString): POpt;
begin
  FArgQual := ARGUMENT_REQUIRED;
  FArgTitle := Title;
  Result := @Self;
end;

function TOpt.OptionalArgument(const Title: AnsiString): POpt;
begin
  FArgQual := ARGUMENT_OPTIONAL;
  FArgTitle := Title;
  Result := @Self;
end;

function TOpt.OptionalValue(const Value: AnsiString; const Title: AnsiString): POpt;
begin
  FHasOptionalValue := True;
  FOptionalValue := Value;
  Result := OptionalArgument(Title);
end;

function TOpt.HasOptionalValue: Boolean;
begin
  Result := FHasOptionalValue;
end;

function TOpt.GetOptionalValue: AnsiString;
begin
  Result := FOptionalValue;
end;

function TOpt.DefaultValue(const Value: AnsiString): POpt;
begin
  FHasDefaultValue := True;
  FDefaultValue := Value;
  Result := @Self;
end;

function TOpt.HasDefaultValue: Boolean;
begin
  Result := FHasDefaultValue;
end;

function TOpt.GetDefaultValue: AnsiString;
begin
  Result := FDefaultValue;
end;

function TOpt.Required: POpt;
begin
  FRequired := True;
  Result := @Self;
end;

function TOpt.Optional: POpt;
begin
  FRequired := False;
  Result := @Self;
end;

function TOpt.IsRequired: Boolean;
begin
  Result := FRequired;
end;

function TOpt.Help(const Descr: AnsiString): POpt;
begin
  FHelp := Descr;
  Result := @Self;
end;

function TOpt.GetHelp: AnsiString;
begin
  Result := FHelp;
end;

function TOpt.Handler(_Hanlder: TArgHandler): POpt;
begin
  FHandler := _Hanlder;
  Result := @Self;
end;

constructor TFreeOpt.Init(const Title, Help: AnsiString; NamedAlias: POpt);
begin
  FTitle := Title;
  FHelp := Help;
  FNamedAlias := NamedAlias;
end;

destructor TFreeOpt.Done;
begin
end;

constructor TOpts.Init;
begin
end;

destructor TOpts.Done;
begin
end;

procedure TOpts.SetTitle(const Title: AnsiString);
begin
  FTitle := Title;
end;

function TOpts.FindLongOption(const Name: AnsiString): POpt;
var
  I: LongInt;
begin
  for I := 0 to High(FOpts) do
    if FOpts[I].IsName(Name) then
      Exit(@FOpts[I]);
  Exit(nil);
end;

function TOpts.FindCharOption(C: AnsiChar): POpt;
var
  I: LongInt;
begin
  for I := 0 to High(FOpts) do
    if FOpts[I].IsChar(C) then
      Exit(@FOpts[I]);
  Exit(nil);
end;

function TOpts.HasLongOption(const Name: AnsiString): Boolean;
begin
  Result := FindLongOption(Name) <> nil;
end;

function TOpts.HasCharOption(C: AnsiChar): Boolean;
begin
  Result := FindCharOption(C) <> nil;
end;

function TOpts.GetLongOption(const Name: AnsiString): POpt;
begin
  Result := FindLongOption(Name);
end;

function TOpts.GetCharOption(C: AnsiChar): POpt;
begin
  Result := FindCharOption(C);
end;

function TOpts.HasAnyShortOption: boolean;
var
  I: LongInt;
begin
  for I := 0 to High(FOpts) do
    if Length(FOpts[I].FCharNames) > 0 then
      Exit(True);
  Exit(False);
end;

function TOpts.HasAnyLongOption: boolean;
var
  I: LongInt;
begin
  for I := 0 to High(FOpts) do
    if Length(FOpts[I].FLongNames) > 0 then
      Exit(True);
  Exit(False);
end;

function TOpts.AddOption(const Opt: TOpt): POpt;
begin
  SetLength(FOpts, Length(FOpts) + 1);
  FOpts[High(FOpts)] := Opt;
  Result := @FOpts[High(FOpts)];
end;

procedure TOpts.AddCharOptions(const OptString: AnsiString);
var
  I: LongInt;
  C: AnsiChar;
  Qual: LongInt;
begin
  I := 1;
  while I <= Length(OptString) do begin
    C := OptString[I];
    Inc(I);
    Qual := ARGUMENT_NO;
    if (I <= Length(OptString)) and (OptString[I] = ':') then begin
      Qual := ARGUMENT_REQUIRED;
      Inc(I);
    end;
    if (I <= Length(OptString)) and (OptString[I] = ':') then begin
      Qual := ARGUMENT_OPTIONAL;
      Inc(I);
    end;
    AddCharOption(C, Qual);
  end;
end;

function TOpts.AddCharOption(C: AnsiChar; Help: AnsiString = ''): POpt;
begin
  Result := AddCharOption(C, ARGUMENT_NO, Help);
end;

function TOpts.AddCharOption(C: AnsiChar; ArgQual: LongInt; Help: AnsiString = ''): POpt;
var
  Opt: TOpt;
begin
  Opt.Init;
  Opt.AddShortName(C);
  Opt.Help(Help);
  Opt.SetArgQual(ArgQual);
  Result := AddOption(Opt);
end;

function TOpts.AddLongOption(const Name: AnsiString; Help: AnsiString = ''): POpt;
begin
  Result := AddLongOption(Name, ARGUMENT_NO, Help);
end;

function TOpts.AddLongOption(const Name: AnsiString; ArgQual: LongInt; Help: AnsiString = ''): POpt;
var
  Opt: TOpt;
begin
  Opt.Init;
  Opt.AddLongName(Name);
  Opt.Help(Help);
  Opt.SetArgQual(ArgQual);
  Result := AddOption(Opt);
end;

function TOpts.AddLongOption(C: AnsiChar; const Name: AnsiString; Help: AnsiString = ''): POpt;
var
  Opt: TOpt;
begin
  Opt.Init;
  Opt.AddShortName(C);
  Opt.AddLongName(Name);
  Opt.Help(Help);
  Opt.SetArgQual(ARGUMENT_NO);
  Result := AddOption(Opt);
end;

function TOpts.AddHelpOption(C: AnsiChar = '?'): POpt;
var
  Opt: POpt;
begin
  Opt := FindLongOption('help');
  if Opt <> nil then begin
    if not Opt.IsChar(C) then
      Opt.AddShortName(C);
    if (C = '?') and not Opt.IsChar('h') then
      Opt.AddShortName('h');
    Exit(Opt);
  end;
  Result := AddLongOption(C, 'help', 'print usage')
           .SetArgQual(ARGUMENT_NO)
           .Handler(@PrintUsageAndExit);
  if C = '?' then
    Result.AddShortName('h');
end;

function TOpts.AddVersionOption(C: AnsiChar = 'V'): POpt;
var
  Opt: POpt;
begin
  Opt := FindLongOption('version');
  if Opt <> nil then begin
    if not Opt.IsChar(C) then
      Opt.AddShortName(C);
    Exit(Opt);
  end;
  Result := AddLongOption(C, 'version', 'print version')
           .SetArgQual(ARGUMENT_NO)
           .Handler(@PrintVersionAndExit);
end;

function TOpts.CharOption(C: AnsiChar): POpt;
var
  Opt: POpt;
begin
  Opt := FindCharOption(C);
  if Opt <> nil then begin
    Exit(Opt);
  end else
    Exit(AddCharOption(C));
end;

function TOpts.IndexOf(Opt: POpt): LongInt;
var
  I: LongInt;
begin
  for I := 0 to High(FOpts) do
    if @FOpts[I] = Opt then
      Exit(I);
  Result := -1;
end;

procedure TOpts.SetCmdLineDescr(Descr: AnsiString);
begin
  FCustomCmdLine := Descr;
end;

procedure TOpts.SetFreeArgsMin(N: LongInt);
begin
  FFreeArgsMin := N;
end;

procedure TOpts.SetFreeArgsMax(N: LongInt);
begin
  FFreeArgsMax := N;
end;

procedure TOpts.SetFreeArgsNum(N: LongInt);
begin
  FFreeArgsMin := N;
  FFreeArgsMax := N;
end;

procedure TOpts.SetFreeArgsNum(Min, Max: LongInt);
begin
  FFreeArgsMin := Min;
  FFreeArgsMax := Max;
end;

procedure TOpts.SetFreeArgTitle(P: LongInt; const Title: AnsiString; const Help: AnsiString);
begin
  // TODO
end;

procedure TOpts.SetFreeArgDefaultTitle(const Title: AnsiString; const Help: AnsiString = '');
begin
  FDefaultFreeOpt.FTitle := Title;
  FDefaultFreeOpt.FHelp := Help;
  FCustomDefaultOpt := True;
end;

procedure TOpts.SetAllowSingleDashForLong(Value: Boolean);
begin
  FAllowSingleDashForLong := Value;
end;

function TOpts.GetFreeArgTitle(N: LongInt): AnsiString;
var
  I: LongInt;
begin
  for I := 0 to High(FFreeOpts) do
    if FFreeOpts[I].FPos = I then
      Result := FFreeOpts[I].FTitle;
  Result := FDefaultFreeOpt.FTitle;
end;

function TOpts.GetFreeArgHelp(N: LongInt): AnsiString;
var
  I: LongInt;
begin
  for I := 0 to High(FFreeOpts) do
    if FFreeOpts[I].FPos = I then
      Result := FFreeOpts[I].FHelp;
  Result := FDefaultFreeOpt.FHelp;
end;

function TOpts.FormatOption(Opt: POpt; Color: LongInt): AnsiString;
var
  I: LongInt;
begin
  Result := '';
  for I := 0 to High(Opt.FLongNames) do begin
    if Result <> '' then
      Result := Result + ', ';
    Result := Result + '--' + Opt.FLongNames[I]
  end;
  for I := 1 to Length(Opt.FCharNames) do begin
    if Result <> '' then
      Result := Result + ', ';
    Result := Result + '-' + Opt.FCharNames[I]
  end;
  if Opt.FArgQual <> ARGUMENT_NO then begin
    Result := Result + ' <' + Opt.FArgTitle + '>';
  end;
end;

procedure TOpts.PrintOpts(Required: Boolean; Stream: Pointer; Colors: LongInt);
begin
end;

procedure TOpts.PrintUsage(const Prog: AnsiString; Stream: Pointer; Colors: LongInt);
var
  I, LeftWidth, Width: LongInt;
  Opt: POpt;
  LeftColumns: array of AnsiString;
  LeftPadding, MaxPadding: AnsiString;
  Stage: LongInt;
begin
  if FTitle <> '' then
    Writeln(FTitle);
  PrintCmdLine(Prog, Stream, Colors);

  LeftWidth := 0;
  SetLength(LeftColumns, 0);
  for I := 0 to High(FOpts) do begin
    Opt := @FOpts[I];
    SetLength(LeftColumns, Length(LeftColumns) + 1);
    LeftColumns[High(LeftColumns)] := FormatOption(Opt, Colors);
    Width := Length(LeftColumns[High(LeftColumns)]);
    if Width > LeftWidth then
       LeftWidth := Width;
  end;

  if LeftWidth > 25 then
    LeftWidth := 25;
  MaxPadding := Space(25);
  LeftPadding := Space(LeftWidth);

  for Stage := 0 to 1 do begin
    if Stage = 0 then begin
      Writeln('Required arguments:');
    end else begin
      Writeln('Optional arguments:');
    end;
    for I := 0 to High(FOpts) do begin
      if (Stage = 0) = (FOpts[I].FArgQual = ARGUMENT_REQUIRED) then begin
        if Length(LeftColumns[I]) > LeftWidth then begin
          Writeln('    ', LeftColumns[I]);
          Write('    ', MaxPadding, ' ');
        end else begin
          Write('    ', LeftColumns[I], Space(LeftWidth - Length(LeftColumns[I])), ' ');
        end;
        Writeln(FOpts[I].FHelp);
      end;
    end;
  end;
end;

procedure TOpts.PrintCmdLine(const Prog: AnsiString; Stream: Pointer; Colors: LongInt);
var 
  I: LongInt;
begin
  Write('Usage: ', Prog, ' ');
  Write('[options]');
  for I := 0 to High(FFreeOpts) do begin
    // TODO
  end;
  Writeln;
end;

procedure TOpts.PrintFreeArgsDescr(const Prog: AnsiString; Stream: Pointer; Colors: LongInt);
begin
  // TODO
end;

function IsAllowedShortName(C: AnsiChar): Boolean;
begin
  Result := C in ['?', 'a'..'z', 'A'..'Z'];
end;

function IsAllowedLongName(const Name: AnsiString; Pos: PInteger): Boolean;
var
  I: LongInt;
begin
  for I := 1 to Length(Name) do begin
    if not (Name[I] in ['-', '_', 'a'..'z', 'A'..'Z']) then begin
      if Pos <> nil then
        Pos^ := I;
      Exit(False);
    end;
  end;
  Exit(True);
end;

constructor TArgs.Init;
begin
end;

constructor TArgs.Init(constref Opts: TOpts);
begin
  ParseParamStr(Opts);
end;

destructor TArgs.Done;
begin
end;

procedure TArgs.PrintUsage(Colors: LongInt);
begin
  FOpts.PrintUsage(ParamStr(0), nil, Colors);
end;

procedure TArgs.ParseParamStr(constref Opts: TOpts; Start: LongInt = 0; Count: LongInt = -1);
var
  I: LongInt;
  A, B, Name: AnsiString;
  Opt: POpt;
begin
  FOpts := @Opts;
  if Count = -1 then
    Count := ParamCount;
  I := Start + 1;
  while I <= Start + Count do begin
    A := ParamStr(I);
    if (Length(A) > 1) and (A[1] = '-') then begin
      if A[2] = '-' then begin
        Name := Copy(A, 3, Length(A) - 2);
        Opt := Opts.FindLongOption(Name);
        if Opt = nil then begin
          // TODO Error
          Writeln(stderr, 'Unknown option ', A);
          Halt(1);
        end;
        SetLength(FArgs, Length(FArgs) + 1);
        FArgs[High(FArgs)].Opt := Opt;
        if Opt.FArgQual <> ARGUMENT_NO then begin
          Inc(I);
          if I > Start + Count then begin
            // TODO Error
            Writeln(stderr, 'No option for ', A);
            Halt(1);
          end else begin
            FArgs[High(FArgs)].Value := ParamStr(I);
          end;
        end;
        if Opt.FHandler <> nil then begin
          Opt.FHandler(@Self);
        end;
      end else begin
        Opt := Opts.FindCharOption(A[2]);
        if Opt = nil then begin
          // TODO Error
          Writeln(stderr, 'Unknown option ', A);
          Halt(1);
        end;
        SetLength(FArgs, Length(FArgs) + 1);
        FArgs[High(FArgs)].Opt := Opt;
        if Opt.FArgQual <> ARGUMENT_NO then begin
          Inc(I);
          if I > Start + Count then begin
            // TODO Error
            Writeln(stderr, 'No option for ', A);
            Halt(1);
          end else begin
            FArgs[High(FArgs)].Value := ParamStr(I);
          end;
        end;
        if Opt.FHandler <> nil then begin
          Opt.FHandler(@Self);
        end;
      end;
    end;
    Inc(I);
  end;
end;

function TArgs.FindOptParseResult(Opt: POpt; IncludeDefault: Boolean = False): PArg;
var
  I: LongInt;
begin
  for I := 0 to High(FArgs) do
    if FArgs[I].Opt = Opt then
      Exit(@FArgs[I]);
  Exit(nil);
end;

function TArgs.FindLongOptParseResult(const Name: AnsiString; IncludeDefault: Boolean = False): PArg;
var
  I: LongInt;
begin
  for I := 0 to High(FArgs) do
    if FArgs[I].Opt.IsName(Name) then
      Exit(@FArgs[I]);
  Exit(nil);
end;

function TArgs.FindCharOptParseResult(const C: AnsiChar; IncludeDefault: Boolean = False): PArg;
var
  I: LongInt;
begin
  for I := 0 to High(FArgs) do
    if FArgs[I].Opt.IsChar(C) then
      Exit(@FArgs[I]);
  Exit(nil);
end;

function TArgs.GetProgramName: AnsiString;
begin
  Result := 'TODO progname in dgetopt'
end;

function TArgs.GetFreeArgsPos: LongInt;
begin
  Result := -1;
end;

function TArgs.GetFreeArgsCount: LongInt;
begin
  Result := -1;
end;

function TArgs.Has(Opt: POpt; IncludeDefault: Boolean = False): Boolean; overload;
var
  I: LongInt;
begin
  for I := 0 to High(FArgs) do
    if FArgs[I].Opt = Opt then
      Exit(True);
  Exit(False);
end;

function TArgs.Get(Opt: POpt; IncludeDefault: Boolean = True): TAnsiView; overload;
var
  I: LongInt;
begin
  for I := 0 to High(FArgs) do
    if FArgs[I].Opt = Opt then
      Exit(FArgs[I].Value);
  Exit('');
end;

function TArgs.Get(Opt: POpt; Def: TAnsiView): TAnsiView; overload;
var
  I: LongInt;
begin
  for I := 0 to High(FArgs) do
    if FArgs[I].Opt = Opt then
      Exit(FArgs[I].Value);
  Exit(Def);
end;

function TArgs.Has(const Name: AnsiString; IncludeDefault: Boolean = True): Boolean; overload;
var
  I: LongInt;
begin
  for I := 0 to High(FArgs) do
    if FArgs[I].Opt.IsName(Name) then
      Exit(True);
  Exit(False);
end;

function TArgs.Get(const Name: AnsiString; IncludeDefault: Boolean = True): TAnsiView; overload;
var
  I: LongInt;
begin
  for I := 0 to High(FArgs) do
    if FArgs[I].Opt.IsName(Name) then
      Exit(FArgs[I].Value);
  Exit('');
end;

function TArgs.Get(const Name: AnsiString; Def: TAnsiView): TAnsiView; overload;
var
  I: LongInt;
begin
  for I := 0 to High(FArgs) do
    if FArgs[I].Opt.IsName(Name) then
      Exit(FArgs[I].Value);
  Exit(Def);
end;

function TArgs.HasC(C: AnsiChar; IncludeDefault: Boolean = True): Boolean; overload;
var
  I: LongInt;
begin
  for I := 0 to High(FArgs) do
    if FArgs[I].Opt.IsChar(C) then
      Exit(True);
  Exit(False);
end;

function TArgs.GetC(C: AnsiChar; IncludeDefault: Boolean = True): TAnsiView; overload;
var
  I: LongInt;
begin
  for I := 0 to High(FArgs) do
    if FArgs[I].Opt.IsChar(C) then
      Exit(FArgs[I].Value);
  Exit('');
end;

function TArgs.GetC(C: AnsiChar; Def: TAnsiView): TAnsiView; overload;
var
  I: LongInt;
begin
  for I := 0 to High(FArgs) do
    if FArgs[I].Opt.IsChar(C) then
      Exit(FArgs[I].Value);
  Exit(Def);
end;

initialization
  Opts.Init;
  Args.Init;
finalization
  Args.Done;
  Opts.Done;
end.
