[JEDI.NET-commits] main/run Jedi.System.CommandLine.pas,1.4,1.5
Status: Pre-Alpha
Brought to you by:
jedi_mbe
From: Marcel B. <jed...@us...> - 2005-06-16 13:18:16
|
Update of /cvsroot/jedidotnet/main/run In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv32276/main/run Modified Files: Jedi.System.CommandLine.pas Log Message: * Adapted to allow other types be handled * Added True/False values to Boolean arguments * Added property to specify what to do if a boolean argument doesn't have a value (always set, always reset, toggle, ignore, throw exception) * Added Enum handling Index: Jedi.System.CommandLine.pas =================================================================== RCS file: /cvsroot/jedidotnet/main/run/Jedi.System.CommandLine.pas,v retrieving revision 1.4 retrieving revision 1.5 diff -C2 -d -r1.4 -r1.5 *** Jedi.System.CommandLine.pas 14 Mar 2005 13:30:13 -0000 1.4 --- Jedi.System.CommandLine.pas 16 Jun 2005 13:18:02 -0000 1.5 *************** *** 20,32 **** Known Issues: - - Is only able to handle Boolean, Integer (unsigned Int16, Int32 and Int64) and String types. - Other types require the user to write their own processing method and register the method as a - command line argument. - If an argument starts with any of the starting symbols of the registered switches, an exception is thrown ('unknown argument'). Future version will allow you to specify an option if unknown switches should be ignored or result in an exception thrown. - - Boolean type switches not having a + or - behind the option work as a toggle at all times. - Future version will allow you to specify if the switch is a toggle or will result in the 'True' - value if neither '+' or '-' follows the switch. ---------------------------------------------------------------------------------------------------} // $Id$ --- 20,26 ---- *************** *** 71,74 **** --- 65,72 ---- '$Date$')] CommandLine = class + {$REGION 'Constructor'} + strict protected + constructor Create(arguments: array of &Object; responseFilePrefix: string); + {$ENDREGION} {$REGION 'Data'} strict private *************** *** 78,91 **** FResponseFilePrefix: string; {$ENDREGION} ! {$REGION 'Constructor'} ! strict protected ! constructor Create(arguments: array of &Object; responseFilePrefix: string); ! {$ENDREGION} ! {$REGION 'Nested type: Argument class'} ! protected type ! Argument = class (&Object, IComparable) {$REGION 'Constructor'} ! public constructor Create(matches: string; caseSensitive: Boolean; instance: &Object; memberInfo: MemberInfo); {$ENDREGION} --- 76,87 ---- FResponseFilePrefix: string; {$ENDREGION} ! {$REGION 'Argument class'} ! public type ! Argument = class abstract (&Object, IComparable) {$REGION 'Constructor'} ! strict private ! class constructor Create; ! strict protected constructor Create(matches: string; caseSensitive: Boolean; instance: &Object; memberInfo: MemberInfo); {$ENDREGION} *************** *** 96,99 **** --- 92,97 ---- FMatches: string; FMemberInfo: MemberInfo; + strict private + class var FTypeHandlers: Hashtable; {$ENDREGION} {$REGION 'IComparable method'} *************** *** 101,111 **** function CompareTo(obj: &Object): Integer; {$ENDREGION} ! {$REGION 'Type specific processing methods'} strict protected ! procedure ProcessBoolean(match, commandLine: string; var index: Integer); ! procedure ProcessInt(match, commandLine: string; var index: Integer); ! procedure ProcessString(match, commandLine: string; var index: Integer); {$ENDREGION} ! {$REGION 'Public method'} public procedure Process(commandLine: string; var index: Integer); --- 99,113 ---- function CompareTo(obj: &Object): Integer; {$ENDREGION} ! {$REGION 'Protected methods'} ! protected ! class function TypeHandlers: Hashtable; static; ! {$ENDREGION} ! {$REGION 'Abstract methods'} strict protected ! procedure ProcessImpl(match, commandLine: string; var index: Integer); virtual; abstract; {$ENDREGION} ! {$REGION 'Public methods'} ! public ! class procedure RegisterHandler(valueType, argumentType: &Type); static; public procedure Process(commandLine: string; var index: Integer); *************** *** 120,123 **** --- 122,210 ---- end; {$ENDREGION} + {$REGION 'Boolean argument class'} + public + type + BooleanArgument = class (Argument) + {$REGION 'Constructor'} + public + constructor Create(matches: string; caseSensitive: Boolean; instance: &Object; memberInfo: MemberInfo); + {$ENDREGION} + {$REGION 'No-value action enumeration'} + public + type + NoValueActionEnum = (Toggle, &Set, Reset, ThrowException, Ignore); + {$ENDREGION} + {$REGION 'Data'} + strict private + FFalseValues: array of string; + FNoValueAction: CommandLine.BooleanArgument.NoValueActionEnum; + FTrueValues: array of string; + {$ENDREGION} + {$REGION 'Overrides'} + strict protected + procedure ProcessImpl(match, commandLine: string; var index: Integer); override; + {$ENDREGION} + {$REGION 'Properties'} + public + property NoValueAction: CommandLine.BooleanArgument.NoValueActionEnum read FNoValueAction; + {$ENDREGION} + end; + {$ENDREGION} + {$REGION 'Custom-processing argument class'} + protected + type + CustomProcessingArgument = class (Argument) + {$REGION 'Constructor'} + public + constructor Create(matches: string; caseSensitive: Boolean; instance: &Object; memberInfo: MemberInfo); + {$ENDREGION} + {$REGION 'Overrides'} + strict protected + procedure ProcessImpl(match, commandLine: string; var index: Integer); override; + {$ENDREGION} + end; + {$ENDREGION} + {$REGION 'Enumeration argument class'} + public + type + EnumArgument = class (Argument) + {$REGION 'Constructor'} + public + constructor Create(matches: string; caseSensitive: Boolean; instance: &Object; memberInfo: MemberInfo); + {$ENDREGION} + {$REGION 'Overrides'} + strict protected + procedure ProcessImpl(match, commandLine: string; var index: Integer); override; + {$ENDREGION} + end; + {$ENDREGION} + {$REGION 'Integer argument class'} + public + type + IntegerArgument = class (Argument) + {$REGION 'Constructor'} + public + constructor Create(matches: string; caseSensitive: Boolean; instance: &Object; memberInfo: MemberInfo); + {$ENDREGION} + {$REGION 'Overrides'} + strict protected + procedure ProcessImpl(match, commandLine: string; var index: Integer); override; + {$ENDREGION} + end; + {$ENDREGION} + {$REGION 'String argument class'} + public + type + StringArgument = class (Argument) + {$REGION 'Constructor'} + public + constructor Create(matches: string; caseSensitive: Boolean; instance: &Object; memberInfo: MemberInfo); + {$ENDREGION} + {$REGION 'Overrides'} + strict protected + procedure ProcessImpl(match, commandLine: string; var index: Integer); override; + {$ENDREGION} + end; + {$ENDREGION} {$REGION 'Protected methods'} strict protected *************** *** 133,136 **** --- 220,225 ---- procedure RegisterType(&type: &Type); overload; procedure RegisterType(&type: &Type; instance: &Object); overload; + protected + function FindTypeHandler(&type: &Type): &Type; {$ENDREGION} {$REGION 'Public static methods'} *************** *** 151,155 **** '$Revision$', '$Date$'), ! AttributeUsage(AttributeTargets.Property or AttributeTargets.Method, AllowMultiple = True, &Inherited = False)] CommandLineArgumentAttribute = class (Attribute) {$REGION 'Data'} --- 240,244 ---- '$Revision$', '$Date$'), ! AttributeUsage(AttributeTargets.Property or AttributeTargets.Method, AllowMultiple = False, &Inherited = False)] CommandLineArgumentAttribute = class (Attribute) {$REGION 'Data'} *************** *** 199,208 **** {$ENDREGION} implementation {$AUTOBOX ON} ! {$REGION 'CommandLine'} constructor CommandLine.Create(arguments: array of &Object; responseFilePrefix: string); begin --- 288,397 ---- {$ENDREGION} + {$REGION 'Boolean options attribute'} + type + [JediSourceInfo( + '$Source$', + '$Revision$', + '$Date$'), + AttributeUsage(AttributeTargets.Property or AttributeTargets.Method, AllowMultiple = False, &Inherited = False)] + BooleanCommandLineArgumentAttribute = class (CommandLineArgumentAttribute) + {$REGION 'Constructor'} + public + constructor Create; + {$ENDREGION} + {$REGION 'Data'} + strict private + FFalseValues: ArrayList; + FNoValueAction: CommandLine.BooleanArgument.NoValueActionEnum; + FTrueValues: ArrayList; + {$ENDREGION} + {$REGION 'Property access methods'} + public + function get_FalseValue: string; + function get_FalseValueCount: Integer; + function get_FalseValues(index: Integer): string; + function get_TrueValue: string; + function get_TrueValueCount: Integer; + function get_TrueValues(index: Integer): string; + procedure set_FalseValue(value: string); + procedure set_TrueValue(value: string); + {$ENDREGION} + {$REGION 'Properties'} + protected + property FalseValueList: ArrayList read FFalseValues; + property TrueValueList: ArrayList read FTrueValues; + public + property FalseValue: string read get_FalseValue write set_FalseValue; + property FalseValueCount: Integer read get_FalseValueCount; + property FalseValues[&index: Integer]: string read get_FalseValues; + property NoValueAction: CommandLine.BooleanArgument.NoValueActionEnum read FNoValueAction write FNoValueAction; + property TrueValue: string read get_TrueValue write set_TrueValue; + property TrueValueCount: Integer read get_TrueValueCount; + property TrueValues[&index: Integer]: string read get_TrueValues; + {$ENDREGION} + end; + {$ENDREGION} + implementation {$AUTOBOX ON} ! {$REGION 'BooleanCommandLineArgumentAttribute'} ! constructor BooleanCommandLineArgumentAttribute.Create; ! begin ! inherited Create; ! FFalseValues := ArrayList.Create; ! FNoValueAction := Toggle; ! FTrueValues := ArrayList.Create; ! end; ! ! function BooleanCommandLineArgumentAttribute.get_FalseValue: string; ! begin ! if FFalseValues.Count = 0 then ! Result := '' ! else ! Result := string(FFalseValues[FFalseValues.Count - 1]); ! end; ! ! function BooleanCommandLineArgumentAttribute.get_FalseValueCount: Integer; ! begin ! Result := FFalseValues.Count; ! end; ! ! function BooleanCommandLineArgumentAttribute.get_FalseValues(index: Integer): string; ! begin ! Result := string(FFalseValues[index]); ! end; ! ! function BooleanCommandLineArgumentAttribute.get_TrueValue: string; ! begin ! if FTrueValues.Count = 0 then ! Result := '' ! else ! Result := string(FTrueValues[FTrueValues.Count - 1]); ! end; ! ! function BooleanCommandLineArgumentAttribute.get_TrueValueCount: Integer; ! begin ! Result := FTrueValues.Count; ! end; ! ! function BooleanCommandLineArgumentAttribute.get_TrueValues(index: Integer): string; ! begin ! Result := string(FTrueValues[index]); ! end; ! ! procedure BooleanCommandLineArgumentAttribute.set_FalseValue(value: string); ! begin ! FFalseValues.Add(value); ! end; ! ! procedure BooleanCommandLineArgumentAttribute.set_TrueValue(value: string); ! begin ! FTrueValues.Add(value); ! end; ! {$ENDREGION} + {$REGION 'CommandLine'} constructor CommandLine.Create(arguments: array of &Object; responseFilePrefix: string); begin *************** *** 286,289 **** --- 475,488 ---- end; + function CommandLine.FindTypeHandler(&type: &Type): &Type; + begin + Result := nil; + while (Result = nil) and (&type <> nil) do + begin + Result := System.Type(Argument.TypeHandlers[&type]); + &type := &type.BaseType; + end; + end; + class function CommandLine.GetLiteral(commandLine: string; var index: Integer): string; var *************** *** 368,378 **** attr: CommandLineArgumentAttribute); var thisMatch: string; argument: CommandLine.Argument; idx: Integer; begin for thisMatch in attr.GetMatches do begin ! argument := CommandLine.Argument.Create(thisMatch, attr.CaseSensitive, instance, memberInfo); idx := FArguments.BinarySearch(argument); if idx >= 0 then --- 567,593 ---- attr: CommandLineArgumentAttribute); var + argType: &Type; + ciArgument: ConstructorInfo; thisMatch: string; argument: CommandLine.Argument; idx: Integer; begin + if PropertyInfo(memberInfo) = nil then + argType := TypeOf(CommandLine.CustomProcessingArgument) + else + begin + argType := FindTypeHandler(PropertyInfo(memberInfo).PropertyType); + if argType = nil then + raise CommandLineException.Create(System.String.Format( + 'No argument handler defined for type {0}', PropertyInfo(memberInfo).PropertyType.FullName)); + end; + ciArgument := argType.GetConstructor(BindingFlags.Instance or BindingFlags.NonPublic or BindingFlags.Public, nil, + [TypeOf(System.String), TypeOf(System.Boolean), TypeOf(&Object), TypeOf(MemberInfo)], nil); + if ciArgument = nil then + raise CommandLineException.Create(System.String.Format( + 'Handler {0} has no compatible constructor', argType.FullName)); for thisMatch in attr.GetMatches do begin ! argument := CommandLine.Argument(ciArgument.Invoke([thisMatch, attr.CaseSensitive, instance, memberInfo])); idx := FArguments.BinarySearch(argument); if idx >= 0 then *************** *** 469,472 **** --- 684,702 ---- {$REGION 'CommandLine.Argument'} + class constructor CommandLine.Argument.Create; + begin + RegisterHandler(TypeOf(System.Boolean), TypeOf(CommandLine.BooleanArgument)); + RegisterHandler(TypeOf(System.Byte), TypeOf(CommandLine.IntegerArgument)); + RegisterHandler(TypeOf(System.Enum), TypeOf(CommandLine.EnumArgument)); + RegisterHandler(TypeOf(System.Int16), TypeOf(CommandLine.IntegerArgument)); + RegisterHandler(TypeOf(System.Int32), TypeOf(CommandLine.IntegerArgument)); + RegisterHandler(TypeOf(System.Int64), TypeOf(CommandLine.IntegerArgument)); + RegisterHandler(TypeOf(System.SByte), TypeOf(CommandLine.IntegerArgument)); + RegisterHandler(TypeOf(System.UInt16), TypeOf(CommandLine.IntegerArgument)); + RegisterHandler(TypeOf(System.UInt32), TypeOf(CommandLine.IntegerArgument)); + RegisterHandler(TypeOf(System.UInt64), TypeOf(CommandLine.IntegerArgument)); + RegisterHandler(TypeOf(System.String), TypeOf(CommandLine.StringArgument)); + end; + constructor CommandLine.Argument.Create(matches: string; caseSensitive: Boolean; instance: &Object; memberInfo: MemberInfo); *************** *** 476,480 **** raise ArgumentNullException.Create('memberInfo'); if matches = '' then ! raise ArgumentException.Create('Cannot me an empty string', 'matches'); FCaseSensitive := caseSensitive; FInstance := instance; --- 706,710 ---- raise ArgumentNullException.Create('memberInfo'); if matches = '' then ! raise ArgumentException.Create('Cannot be an empty string', 'matches'); FCaseSensitive := caseSensitive; FInstance := instance; *************** *** 511,516 **** var match: string; - pi: PropertyInfo; - params: array of &Object; begin match := commandLine.Substring(index, FMatches.Length); --- 741,744 ---- *************** *** 519,582 **** while (index < commandLine.Length) and (commandLine.Chars[index] = ' ') do Inc(index); ! pi := PropertyInfo(FMemberInfo); ! if pi = nil then begin ! params := ObjectArray.Create(match, commandLine, index); ! MethodInfo(FMemberInfo).Invoke(FInstance, params); ! index := Integer(params[2]); ! end ! else ! if TypeOf(System.Boolean).IsAssignableFrom(pi.PropertyType) then ! ProcessBoolean(match, commandLine, index) ! else ! if TypeOf(System.Int16).IsAssignableFrom(pi.PropertyType) or ! TypeOf(System.Int32).IsAssignableFrom(pi.PropertyType) or ! TypeOf(System.Int64).IsAssignableFrom(pi.PropertyType) then ! ProcessInt(match, commandLine, index) ! else ! if TypeOf(System.String).IsAssignableFrom(pi.PropertyType) then ! ProcessString(match, commandLine, index) end; ! procedure CommandLine.Argument.ProcessBoolean(match, commandLine: string; var index: Integer); var value: string; begin value := Jedi.System.CommandLine.CommandLine.GetLiteral(commandLine, index); - if value = '+' then - PropertyInfo(FMemberInfo).SetValue(FInstance, True, []) - else - if value = '-' then - PropertyInfo(FMemberInfo).SetValue(FInstance, False, []) - else if value = '' then ! PropertyInfo(FMemberInfo).SetValue(FInstance, not Boolean(PropertyInfo(FMemberInfo).GetValue(FInstance, [])), []) else ! raise CommandLineException.Create(System.String.Format('Invalid boolean value. Argument: {0}. Value: {1}', Matches, value)); end; ! procedure CommandLine.Argument.ProcessInt(match, commandLine: string; var index: Integer); var value: string; pi: PropertyInfo; - flags: BindingFlags; begin value := Jedi.System.CommandLine.CommandLine.GetLiteral(commandLine, index); ! pi := PropertyInfo(FMemberInfo); ! if FInstance = nil then ! flags := BindingFlags.Static ! else ! flags := BindingFlags.Instance; ! flags := flags or BindingFlags.Public or BindingFlags.NonPublic or BindingFlags.InvokeMethod; pi.SetValue( ! FInstance, ! pi.PropertyType.InvokeMember('Parse', flags, nil, FInstance, [value, NumberFormatInfo.InvariantInfo], [], ! CultureInfo.InvariantCulture, ['s', 'provider']), []); end; ! procedure CommandLine.Argument.ProcessString(match, commandLine: string; var index: Integer); begin ! PropertyInfo(FMemberInfo).SetValue(FInstance, Jedi.System.CommandLine.CommandLine.GetLiteral(commandLine, index), []); end; {$ENDREGION} --- 747,910 ---- while (index < commandLine.Length) and (commandLine.Chars[index] = ' ') do Inc(index); ! ProcessImpl(match, commandLine, index); ! end; ! ! class procedure CommandLine.Argument.RegisterHandler(valueType, argumentType: &Type); ! begin ! if valueType = nil then ! raise ArgumentNullException.Create('valueType'); ! if argumentType = nil then ! raise ArgumentNullException.Create('argumentType'); ! if not argumentType.IsSubclassOf(TypeOf(CommandLine.Argument)) then ! raise ArgumentException.Create( ! System.String.Format('Type handler must be derived from {0}', TypeOf(CommandLine.Argument).FullName), 'argumentType'); ! TypeHandlers[valueType] := argumentType; ! end; ! ! class function CommandLine.Argument.TypeHandlers: Hashtable; ! begin ! if FTypeHandlers = nil then ! FTypeHandlers := HashTable.Create; ! Result := FTypeHandlers; ! end; ! {$ENDREGION} ! ! {$REGION 'CommandLine.BooleanArgument'} ! constructor CommandLine.BooleanArgument.Create(matches: string; caseSensitive: Boolean; instance: &Object; ! memberInfo: MemberInfo); ! var ! attrs: array of &Object; ! boolOptAttr: BooleanCommandLineArgumentAttribute; ! begin ! inherited Create(matches, caseSensitive, instance, memberInfo); ! FFalseValues := StringArray.Create('-'); ! FNoValueAction := Toggle; ! FTrueValues := StringArray.Create('+'); ! attrs := memberInfo.GetCustomAttributes(TypeOf(BooleanCommandLineArgumentAttribute), False); ! if &Array(attrs).Length > 0 then begin ! boolOptAttr := BooleanCommandLineArgumentAttribute(attrs[0]); ! if boolOptAttr.FalseValueList.Count > 0 then ! begin ! FFalseValues := StringArray(boolOptAttr.FalseValueList.ToArray(TypeOf(System.String))); ! &Array.Sort(&Array(FFalseValues), CaseInsensitiveComparer.DefaultInvariant); ! end; ! FNoValueAction := boolOptAttr.NoValueAction; ! if boolOptAttr.TrueValueList.Count > 0 then ! begin ! FTrueValues := StringArray(boolOptAttr.TrueValueList.ToArray(TypeOf(System.String))); ! &Array.Sort(&Array(FTrueValues), CaseInsensitiveComparer.DefaultInvariant); ! end; ! end; end; ! procedure CommandLine.BooleanArgument.ProcessImpl(match, commandLine: string; var index: Integer); var value: string; + idx: Integer; begin value := Jedi.System.CommandLine.CommandLine.GetLiteral(commandLine, index); if value = '' then ! begin ! case NoValueAction of ! Toggle: ! PropertyInfo(MemberInfo).SetValue(Instance, not Boolean(PropertyInfo(MemberInfo).GetValue(Instance, [])), []); ! &Set: ! PropertyInfo(MemberInfo).SetValue(Instance, True, []); ! Reset: ! PropertyInfo(MemberInfo).SetValue(Instance, False, []); ! ThrowException: ! raise CommandLineException.Create(System.String.Format( ! 'Missing value. Argument: {0}', Matches)); ! end; ! end else ! begin ! idx := &Array.BinarySearch(&Array(FTrueValues), value, CaseInsensitiveComparer.DefaultInvariant); ! if idx >= 0 then ! PropertyInfo(MemberInfo).SetValue(Instance, True, []) ! else ! begin ! idx := &Array.BinarySearch(&Array(FFalseValues), value, CaseInsensitiveComparer.DefaultInvariant); ! if idx >= 0 then ! PropertyInfo(MemberInfo).SetValue(Instance, False, []) ! else ! raise CommandLineException.Create(System.String.Format( ! 'Invalid boolean value. Argument: {0}. Value: {1}', Matches, value)); ! end; ! end end; + {$ENDREGION} ! {$REGION 'CommandLine.CustomProcessingArgument'} ! constructor CommandLine.CustomProcessingArgument.Create(matches: string; caseSensitive: Boolean; instance: &Object; ! memberInfo: MemberInfo); ! begin ! inherited Create(matches, caseSensitive, instance, memberInfo); ! end; ! ! procedure CommandLine.CustomProcessingArgument.ProcessImpl(match, commandLine: string; var index: Integer); ! var ! params: array of &Object; ! begin ! params := ObjectArray.Create(match, commandLine, index); ! MethodInfo(MemberInfo).Invoke(Instance, params); ! index := Integer(params[2]); ! end; ! {$ENDREGION} ! ! {$REGION 'CommandLine.EnumArgument'} ! constructor CommandLine.EnumArgument.Create(matches: string; caseSensitive: Boolean; instance: &Object; ! memberInfo: MemberInfo); ! begin ! inherited Create(matches, caseSensitive, instance, memberInfo); ! end; ! ! procedure CommandLine.EnumArgument.ProcessImpl(match, commandLine: string; var index: Integer); var value: string; pi: PropertyInfo; begin value := Jedi.System.CommandLine.CommandLine.GetLiteral(commandLine, index); ! pi := PropertyInfo(MemberInfo); pi.SetValue( ! Instance, ! System.Enum.Parse(pi.PropertyType, value, True), []); end; + {$ENDREGION} ! {$REGION 'CommandLine.IntegerArgument'} ! constructor CommandLine.IntegerArgument.Create(matches: string; caseSensitive: Boolean; instance: &Object; ! memberInfo: MemberInfo); begin ! inherited Create(matches, caseSensitive, instance, memberInfo); ! end; ! ! procedure CommandLine.IntegerArgument.ProcessImpl(match, commandLine: string; var index: Integer); ! var ! value: string; ! pi: PropertyInfo; ! begin ! value := Jedi.System.CommandLine.CommandLine.GetLiteral(commandLine, index); ! pi := PropertyInfo(MemberInfo); ! pi.SetValue( ! Instance, ! pi.PropertyType.InvokeMember('Parse', BindingFlags.Static or BindingFlags.Public or BindingFlags.InvokeMethod, ! nil, Instance, [value, NumberFormatInfo.InvariantInfo], [], CultureInfo.InvariantCulture, ['s', 'provider']), ! []); ! end; ! {$ENDREGION} ! ! {$REGION 'CommandLine.StringArgument'} ! constructor CommandLine.StringArgument.Create(matches: string; caseSensitive: Boolean; instance: &Object; ! memberInfo: MemberInfo); ! begin ! inherited Create(matches, caseSensitive, instance, memberInfo); ! end; ! ! procedure CommandLine.StringArgument.ProcessImpl(match, commandLine: string; var index: Integer); ! begin ! PropertyInfo(MemberInfo).SetValue(Instance, Jedi.System.CommandLine.CommandLine.GetLiteral(commandLine, index), []); end; {$ENDREGION} |