|
@@ -0,0 +1,552 @@
|
|
|
+{ ***************************************************************************
|
|
|
+
|
|
|
+ Copyright (c) 2015-2019 Kike Pérez
|
|
|
+
|
|
|
+ Unit : Quick.Options
|
|
|
+ Description : Configuration group settings
|
|
|
+ Author : Kike Pérez
|
|
|
+ Version : 1.0
|
|
|
+ Created : 18/10/2019
|
|
|
+ Modified : 29/10/2019
|
|
|
+
|
|
|
+ This file is part of QuickLib: https://github.com/exilon/QuickLib
|
|
|
+
|
|
|
+ ***************************************************************************
|
|
|
+
|
|
|
+ Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
+ you may not use this file except in compliance with the License.
|
|
|
+ You may obtain a copy of the License at
|
|
|
+
|
|
|
+ http://www.apache.org/licenses/LICENSE-2.0
|
|
|
+
|
|
|
+ Unless required by applicable law or agreed to in writing, software
|
|
|
+ distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
+ See the License for the specific language governing permissions and
|
|
|
+ limitations under the License.
|
|
|
+
|
|
|
+ *************************************************************************** }
|
|
|
+
|
|
|
+unit Quick.Options;
|
|
|
+
|
|
|
+{$i QuickLib.inc}
|
|
|
+
|
|
|
+interface
|
|
|
+
|
|
|
+uses
|
|
|
+ Classes,
|
|
|
+ RTTI,
|
|
|
+ Quick.RTTI.Utils,
|
|
|
+ System.TypInfo,
|
|
|
+ System.SysUtils,
|
|
|
+ System.Generics.Collections,
|
|
|
+ System.Json,
|
|
|
+ Quick.Commons,
|
|
|
+ Quick.FileMonitor;
|
|
|
+
|
|
|
+type
|
|
|
+ Required = class(TCustomAttribute);
|
|
|
+
|
|
|
+ TValidationCustomAttribute = class(TCustomAttribute)
|
|
|
+ protected
|
|
|
+ fErrorMsg : string;
|
|
|
+ public
|
|
|
+ property ErrorMsg : string read fErrorMsg write fErrorMsg;
|
|
|
+ end;
|
|
|
+
|
|
|
+ Range = class(TValidationCustomAttribute)
|
|
|
+ private
|
|
|
+ fRangeMin : Double;
|
|
|
+ fRangeMax : Double;
|
|
|
+ public
|
|
|
+ constructor Create(aMin, aMax : Integer; const aErrorMsg : string = ''); overload;
|
|
|
+ constructor Create(aMin, aMax : Double; const aErrorMsg : string = ''); overload;
|
|
|
+ property Min : Double read fRangeMin write fRangeMax;
|
|
|
+ property Max : Double read fRangeMax write fRangeMax;
|
|
|
+ end;
|
|
|
+
|
|
|
+ StringLength = class(TValidationCustomAttribute)
|
|
|
+ private
|
|
|
+ fMaxLength : Integer;
|
|
|
+ public
|
|
|
+ constructor Create(aMaxLength : Integer; const aErrorMsg : string = '');
|
|
|
+ property MaxLength : Integer read fMaxLength write fMaxLength;
|
|
|
+ end;
|
|
|
+
|
|
|
+ TOptions = class;
|
|
|
+
|
|
|
+ TConfigureOptionsProc<T : TOptions> = reference to procedure(aOptions : T);
|
|
|
+
|
|
|
+ IOptionsValidator = interface
|
|
|
+ ['{C6A09F78-8E34-4689-B943-83620437B9EF}']
|
|
|
+ procedure ValidateOptions;
|
|
|
+ end;
|
|
|
+
|
|
|
+ IOptionsConfigure<T> = interface
|
|
|
+ ['{49258BEB-A21D-4C64-BA71-767B8DBD4D92}']
|
|
|
+ //function ConfigureOptions(aOptionsFunc : TConfigureOptionsProc<T>) : IOptionsValidator;
|
|
|
+ end;
|
|
|
+
|
|
|
+ TOptions = class(TInterfacedObject,IOptionsValidator)
|
|
|
+ private
|
|
|
+ fName : string;
|
|
|
+ procedure ValidateRequired(aProperty : TRttiProperty);
|
|
|
+ procedure ValidateStringLength(aProperty: TRttiProperty; aValidation : StringLength);
|
|
|
+ procedure ValidateRange(aProperty : TRttiProperty; aValidation : Range);
|
|
|
+ procedure DoValidateOptions; virtual;
|
|
|
+ public
|
|
|
+ constructor Create;
|
|
|
+ property Name : string read fName write fName;
|
|
|
+ procedure DefaultValues; virtual; abstract;
|
|
|
+ function ConfigureOptions<T : TOptions>(aOptionsFunc : TConfigureOptionsProc<T>) : IOptionsValidator;
|
|
|
+ procedure ValidateOptions;
|
|
|
+ end;
|
|
|
+
|
|
|
+ TOptions<T : TOptions> = record
|
|
|
+ private
|
|
|
+ fOptions : T;
|
|
|
+ public
|
|
|
+ constructor Create(aOptions : T);
|
|
|
+ function ConfigureOptions(aOptionsFunc : TConfigureOptionsProc<T>) : IOptionsValidator;
|
|
|
+ end;
|
|
|
+
|
|
|
+ IOptions<T : TOptions> = interface
|
|
|
+ ['{2779F946-2692-4F74-88AD-F35F5137057A}']
|
|
|
+ function GetSectionValue : T;
|
|
|
+ property Value : T read GetSectionValue;
|
|
|
+ end;
|
|
|
+
|
|
|
+ TOptionsClass = class of TOptions;
|
|
|
+
|
|
|
+ IOptionsContainer = interface
|
|
|
+ ['{A593C8BB-53CF-4AA4-9641-BF974E45CBD1}']
|
|
|
+ function AddSection(aOption : TOptionsClass; const aOptionsName : string = '') : TOptions;
|
|
|
+ function GetOptions(aOptionClass : TOptionsClass): TOptions;
|
|
|
+ function GetSection(aOptionsSection : TOptionsClass; aOptions : TOptions) : Boolean; overload;
|
|
|
+ end;
|
|
|
+
|
|
|
+ TSectionList = TObjectList<TOptions>;
|
|
|
+
|
|
|
+ IOptionsSerializer = interface
|
|
|
+ ['{7DECE203-4AAE-4C9D-86C8-B3D583DF7C8B}']
|
|
|
+ procedure Load(const aFilename : string; aSections : TSectionList);
|
|
|
+ procedure Save(const aFilename : string; aSections : TSectionList);
|
|
|
+ end;
|
|
|
+
|
|
|
+ TOptionsSerializer = class(TInterfacedObject,IOptionsSerializer)
|
|
|
+ public
|
|
|
+ procedure Load(const aFilename : string; aSections : TSectionList); virtual; abstract;
|
|
|
+ procedure Save(const aFilename : string; aSections : TSectionList); virtual; abstract;
|
|
|
+ end;
|
|
|
+
|
|
|
+ TFileModifiedEvent = reference to procedure;
|
|
|
+ TLoadConfigEvent = reference to procedure;
|
|
|
+
|
|
|
+ TOptionValue<T : TOptions> = class(TInterfacedObject,IOptions<T>)
|
|
|
+ private
|
|
|
+ fValue : T;
|
|
|
+ function GetSectionValue : T;
|
|
|
+ public
|
|
|
+ constructor Create(aValue : T);
|
|
|
+ property Value : T read GetSectionValue;
|
|
|
+ end;
|
|
|
+
|
|
|
+ TOptionsContainer = class(TInterfacedObject,IOptionsContainer)
|
|
|
+ private
|
|
|
+ fFilename : string;
|
|
|
+ fSerializer : IOptionsSerializer;
|
|
|
+ fSections : TSectionList;
|
|
|
+ fFileMonitor : TFileMonitor;
|
|
|
+ fOnFileModified : TFileModifiedEvent;
|
|
|
+ fLoaded : Boolean;
|
|
|
+ fReloadIfFileChanged : Boolean;
|
|
|
+ fOnConfigLoaded : TLoadConfigEvent;
|
|
|
+ fOnConfigReloaded : TLoadConfigEvent;
|
|
|
+ procedure CreateFileMonitor;
|
|
|
+ procedure FileModifiedNotify(MonitorNotify : TMonitorNotify);
|
|
|
+ procedure SetReloadIfFileChanged(const Value: Boolean);
|
|
|
+ function GetOptions(aOptionClass : TOptionsClass): TOptions; overload;
|
|
|
+ function GetOptions(aIndex : Integer) : TOptions; overload;
|
|
|
+ function GetSection(aOptionsSection : TOptionsClass; aOptions : TOptions) : Boolean; overload;
|
|
|
+ public
|
|
|
+ constructor Create(const aFilename : string; aOptionsSerializer : IOptionsSerializer; aReloadIfFileChanged : Boolean = False);
|
|
|
+ destructor Destroy; override;
|
|
|
+ property FileName : string read fFilename write fFilename;
|
|
|
+ property ReloadIfFileChanged : Boolean read fReloadIfFileChanged write SetReloadIfFileChanged;
|
|
|
+ property IsLoaded : Boolean read fLoaded;
|
|
|
+ property OnFileModified : TFileModifiedEvent read fOnFileModified write fOnFileModified;
|
|
|
+ property OnConfigLoaded : TLoadConfigEvent read fOnConfigLoaded write fOnConfigLoaded;
|
|
|
+ property OnConfigReloaded : TLoadConfigEvent read fOnConfigReloaded write fOnConfigReloaded;
|
|
|
+ property Items[aOptionClass : TOptionsClass] : TOptions read GetOptions; default;
|
|
|
+ property Items[aIndex : Integer] : TOptions read GetOptions; default;
|
|
|
+ function AddSection(aOption : TOptionsClass; const aSectionName : string = '') : TOptions; overload;
|
|
|
+ function AddSection<T : TOptions>(const aSectionName : string = '') : TOptions<T>; overload;
|
|
|
+ function GetSectionInterface<T : TOptions> : IOptions<T>;
|
|
|
+ function GetSection<T : TOptions>(const aSectionName : string = '') : T; overload;
|
|
|
+ function Count : Integer;
|
|
|
+ procedure Load;
|
|
|
+ procedure Save;
|
|
|
+ end;
|
|
|
+
|
|
|
+ IOptionsBuilder<T : TOptions> = interface
|
|
|
+ ['{1A1DC9A9-7F2D-4CC4-A772-6C7DBAB34424}']
|
|
|
+ function Options : T;
|
|
|
+ end;
|
|
|
+
|
|
|
+ TOptionsBuilder<T : TOptions> = class(TInterfacedObject,IOptionsBuilder<T>)
|
|
|
+ protected
|
|
|
+ fOptions : T;
|
|
|
+ public
|
|
|
+ constructor Create;
|
|
|
+ function Options : T;
|
|
|
+ end;
|
|
|
+
|
|
|
+ EOptionConfigureError = class(Exception);
|
|
|
+ EOptionLoadError = class(Exception);
|
|
|
+ EOptionSaveError = class(Exception);
|
|
|
+ EOptionValidationError = class(Exception);
|
|
|
+
|
|
|
+implementation
|
|
|
+
|
|
|
+{ TOptionsContainer }
|
|
|
+
|
|
|
+constructor TOptionsContainer.Create(const aFilename : string; aOptionsSerializer : IOptionsSerializer; aReloadIfFileChanged : Boolean = False);
|
|
|
+begin
|
|
|
+ fSerializer := aOptionsSerializer;
|
|
|
+ fSections := TSectionList.Create(True);
|
|
|
+ fFilename := aFilename;
|
|
|
+ fLoaded := False;
|
|
|
+ fReloadIfFileChanged := aReloadIfFileChanged;
|
|
|
+ if aReloadIfFileChanged then CreateFileMonitor;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TOptionsContainer.CreateFileMonitor;
|
|
|
+begin
|
|
|
+ fFileMonitor := TQuickFileMonitor.Create;
|
|
|
+ fFileMonitor.FileName := fFilename;
|
|
|
+ fFileMonitor.Interval := 2000;
|
|
|
+ fFileMonitor.Notifies := [TMonitorNotify.mnFileModified];
|
|
|
+ fFileMonitor.OnFileChange := FileModifiedNotify;
|
|
|
+ fFileMonitor.Enabled := True;
|
|
|
+end;
|
|
|
+
|
|
|
+destructor TOptionsContainer.Destroy;
|
|
|
+begin
|
|
|
+ if Assigned(fFileMonitor) then fFileMonitor.Free;
|
|
|
+ fSerializer := nil;
|
|
|
+ fSections.Free;
|
|
|
+ inherited;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TOptionsContainer.FileModifiedNotify(MonitorNotify: TMonitorNotify);
|
|
|
+begin
|
|
|
+ if MonitorNotify = TMonitorNotify.mnFileModified then
|
|
|
+ begin
|
|
|
+ if Assigned(fOnFileModified) then fOnFileModified;
|
|
|
+ if fReloadIfFileChanged then
|
|
|
+ begin
|
|
|
+ Load;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+function TOptionsContainer.AddSection(aOption : TOptionsClass; const aSectionName : string = '') : TOptions;
|
|
|
+var
|
|
|
+ option : TOptions;
|
|
|
+begin
|
|
|
+ option := aOption.Create;
|
|
|
+ if aSectionName.IsEmpty then option.Name := Copy(aOption.ClassName,2,aOption.ClassName.Length)
|
|
|
+ else option.Name := aSectionName;
|
|
|
+ fSections.Add(option);
|
|
|
+ Result := option;
|
|
|
+end;
|
|
|
+
|
|
|
+function TOptionsContainer.AddSection<T>(const aSectionName: string): TOptions<T>;
|
|
|
+var
|
|
|
+ option : TOptions;
|
|
|
+begin
|
|
|
+ option := TRTTI.CreateInstance<T>;
|
|
|
+ if aSectionName.IsEmpty then option.Name := Copy(T.ClassName,2,T.ClassName.Length)
|
|
|
+ else option.Name := aSectionName;
|
|
|
+ fSections.Add(option);
|
|
|
+ Result.Create(option);
|
|
|
+end;
|
|
|
+
|
|
|
+function TOptionsContainer.Count: Integer;
|
|
|
+begin
|
|
|
+ Result := fSections.Count;
|
|
|
+end;
|
|
|
+
|
|
|
+function TOptionsContainer.GetOptions(aIndex: Integer): TOptions;
|
|
|
+begin
|
|
|
+ Result := fSections[aIndex];
|
|
|
+end;
|
|
|
+
|
|
|
+function TOptionsContainer.GetSection(aOptionsSection: TOptionsClass; aOptions: TOptions): Boolean;
|
|
|
+var
|
|
|
+ option : TOptions;
|
|
|
+begin
|
|
|
+ Result := False;
|
|
|
+ for option in fSections do
|
|
|
+ begin
|
|
|
+ if option is TOptionsClass then
|
|
|
+ begin
|
|
|
+ aOptions := option as TOptionsClass;
|
|
|
+ Exit;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+function TOptionsContainer.GetOptions(aOptionClass : TOptionsClass) : TOptions;
|
|
|
+var
|
|
|
+ option : TOptions;
|
|
|
+begin
|
|
|
+ Result := nil;
|
|
|
+ for option in fSections do
|
|
|
+ begin
|
|
|
+ if option is TOptionsClass then Result := option as TOptionsClass;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+function TOptionsContainer.GetSection<T>(const aSectionName : string = '') : T;
|
|
|
+var
|
|
|
+ option : TOptions;
|
|
|
+begin
|
|
|
+ for option in fSections do
|
|
|
+ begin
|
|
|
+ if option is T then
|
|
|
+ begin
|
|
|
+ if (aSectionName.IsEmpty) or (CompareText(option.Name,aSectionName) = 0) then
|
|
|
+ begin
|
|
|
+ Result := option as T;
|
|
|
+ Exit;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+function TOptionsContainer.GetSectionInterface<T>: IOptions<T>;
|
|
|
+begin
|
|
|
+ Result := TOptionValue<T>.Create(Self.GetSection<T>);
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TOptionsContainer.Load;
|
|
|
+var
|
|
|
+ option : TOptions;
|
|
|
+begin
|
|
|
+ if FileExists(fFilename) then
|
|
|
+ begin
|
|
|
+ fSerializer.Load(fFilename,fSections);
|
|
|
+ if not fLoaded then
|
|
|
+ begin
|
|
|
+ fLoaded := True;
|
|
|
+ if Assigned(fOnConfigLoaded) then fOnConfigLoaded;
|
|
|
+ end
|
|
|
+ else if Assigned(fOnConfigReloaded) then fOnConfigReloaded;
|
|
|
+ end
|
|
|
+ else
|
|
|
+ begin
|
|
|
+ //if not exists file get default values
|
|
|
+ for option in fSections do option.DefaultValues;
|
|
|
+ //creates default file
|
|
|
+ Save;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TOptionsContainer.Save;
|
|
|
+var
|
|
|
+ laststate : Boolean;
|
|
|
+begin
|
|
|
+ //disable filemonitor to avoid detect manual save as a external file change
|
|
|
+ laststate := fFileMonitor.Enabled;
|
|
|
+ fFileMonitor.Enabled := False;
|
|
|
+ try
|
|
|
+ //save config file
|
|
|
+ fSerializer.Save(fFilename,fSections);
|
|
|
+ finally
|
|
|
+ //set last state
|
|
|
+ fFileMonitor.Enabled := laststate;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TOptionsContainer.SetReloadIfFileChanged(const Value: Boolean);
|
|
|
+begin
|
|
|
+ if Value = fReloadIfFileChanged then Exit;
|
|
|
+ fReloadIfFileChanged := Value;
|
|
|
+ if Assigned(fFileMonitor) then fFileMonitor.Free;
|
|
|
+ if fReloadIfFileChanged then CreateFileMonitor;
|
|
|
+end;
|
|
|
+
|
|
|
+{ TOptions }
|
|
|
+
|
|
|
+function TOptions.ConfigureOptions<T>(aOptionsFunc: TConfigureOptionsProc<T>): IOptionsValidator;
|
|
|
+var
|
|
|
+ value : TValue;
|
|
|
+begin
|
|
|
+ Result := Self;
|
|
|
+ if Assigned(aOptionsFunc) then
|
|
|
+ begin
|
|
|
+ value := Self;
|
|
|
+ aOptionsFunc(value.AsType<T>);
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+constructor TOptions.Create;
|
|
|
+begin
|
|
|
+ fName := '';
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TOptions.DoValidateOptions;
|
|
|
+var
|
|
|
+ ctx : TRttiContext;
|
|
|
+ rtype : TRttiType;
|
|
|
+ rprop : TRttiProperty;
|
|
|
+ attrib : TCustomAttribute;
|
|
|
+begin
|
|
|
+ ctx := TRttiContext.Create;
|
|
|
+ try
|
|
|
+ rtype := ctx.GetType(Self.ClassInfo);
|
|
|
+ for rprop in rtype.GetProperties do
|
|
|
+ begin
|
|
|
+ //check only published properties
|
|
|
+ if rprop.Visibility = TMemberVisibility.mvPublished then
|
|
|
+ begin
|
|
|
+ //check validation option attributes
|
|
|
+ for attrib in rprop.GetAttributes do
|
|
|
+ begin
|
|
|
+ if attrib is Required then ValidateRequired(rprop)
|
|
|
+ else if attrib is StringLength then ValidateStringLength(rprop,StringLength(attrib))
|
|
|
+ else if attrib is Range then ValidateRange(rprop,Range(attrib));
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ finally
|
|
|
+ ctx.Free;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TOptions.ValidateOptions;
|
|
|
+begin
|
|
|
+ try
|
|
|
+ DoValidateOptions;
|
|
|
+ except
|
|
|
+ on E : Exception do
|
|
|
+ begin
|
|
|
+ raise EOptionConfigureError.CreateFmt('Validation Options Error : %s',[e.Message]);
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TOptions.ValidateRange(aProperty: TRttiProperty; aValidation : Range);
|
|
|
+var
|
|
|
+ value : TValue;
|
|
|
+ msg : string;
|
|
|
+begin
|
|
|
+ value := aProperty.GetValue(Self);
|
|
|
+ if not value.IsEmpty then
|
|
|
+ begin
|
|
|
+ if value.Kind = tkFloat then
|
|
|
+ begin
|
|
|
+ if (value.AsExtended < aValidation.Min) or (value.AsExtended > aValidation.Max) then
|
|
|
+ begin
|
|
|
+ if aValidation.ErrorMsg.IsEmpty then msg := Format('Option "%s.%s" exceeds predefined range (%2f - %2f)',[Self.Name,aProperty.Name,aValidation.Min,aValidation.Max])
|
|
|
+ else msg := aValidation.ErrorMsg;
|
|
|
+ raise EOptionValidationError.Create(msg);
|
|
|
+ end;
|
|
|
+ end
|
|
|
+ else if value.Kind in [tkInteger,tkInt64] then
|
|
|
+ begin
|
|
|
+ if (value.AsInt64 < aValidation.Min) or (value.AsInt64 > aValidation.Max) then
|
|
|
+ begin
|
|
|
+ if aValidation.ErrorMsg.IsEmpty then msg := Format('Option "%s.%s" exceeds predefined range (%d - %d)',[Self.Name,aProperty.Name,Round(aValidation.Min),Round(aValidation.Max)])
|
|
|
+ else msg := aValidation.ErrorMsg;
|
|
|
+ raise EOptionValidationError.Create(msg);
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TOptions.ValidateRequired(aProperty: TRttiProperty);
|
|
|
+begin
|
|
|
+ if aProperty.GetValue(Self).IsEmpty then raise EOptionValidationError.CreateFmt('Option "%s.%s" is required',[Self.Name,aProperty.Name]);
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TOptions.ValidateStringLength(aProperty: TRttiProperty; aValidation : StringLength);
|
|
|
+var
|
|
|
+ value : TValue;
|
|
|
+ msg : string;
|
|
|
+begin
|
|
|
+ value := aProperty.GetValue(Self);
|
|
|
+ if (not value.IsEmpty) and (value.AsString.Length > aValidation.MaxLength) then
|
|
|
+ begin
|
|
|
+ if aValidation.ErrorMsg.IsEmpty then msg := Format('Option "%s.%s" exceeds max length (%d)',[Self.Name,aProperty.Name,aValidation.MaxLength])
|
|
|
+ else msg := aValidation.ErrorMsg;
|
|
|
+
|
|
|
+ raise EOptionValidationError.Create(msg);
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+{ Range }
|
|
|
+
|
|
|
+constructor Range.Create(aMin, aMax: Integer; const aErrorMsg : string = '');
|
|
|
+begin
|
|
|
+ fRangeMin := aMin;
|
|
|
+ fRangeMax := aMax;
|
|
|
+ fErrorMsg := aErrorMsg;
|
|
|
+end;
|
|
|
+
|
|
|
+constructor Range.Create(aMin, aMax: Double; const aErrorMsg: string);
|
|
|
+begin
|
|
|
+ fRangeMin := aMin;
|
|
|
+ fRangeMax := aMax;
|
|
|
+ fErrorMsg := aErrorMsg;
|
|
|
+end;
|
|
|
+
|
|
|
+{ StringLength }
|
|
|
+
|
|
|
+constructor StringLength.Create(aMaxLength: Integer; const aErrorMsg : string = '');
|
|
|
+begin
|
|
|
+ fMaxLength := aMaxLength;
|
|
|
+ fErrorMsg := aErrorMsg;
|
|
|
+end;
|
|
|
+
|
|
|
+
|
|
|
+{ TOptionValue<T> }
|
|
|
+
|
|
|
+constructor TOptionValue<T>.Create(aValue: T);
|
|
|
+begin
|
|
|
+ fValue := aValue;
|
|
|
+end;
|
|
|
+
|
|
|
+function TOptionValue<T>.GetSectionValue: T;
|
|
|
+begin
|
|
|
+ Result := fValue;
|
|
|
+end;
|
|
|
+
|
|
|
+{ TOptions<T> }
|
|
|
+
|
|
|
+function TOptions<T>.ConfigureOptions(aOptionsFunc: TConfigureOptionsProc<T>): IOptionsValidator;
|
|
|
+begin
|
|
|
+ if Assigned(aOptionsFunc) then Result := fOptions.ConfigureOptions<T>(aOptionsFunc)
|
|
|
+ else Result := fOptions;
|
|
|
+ fOptions._AddRef;
|
|
|
+end;
|
|
|
+
|
|
|
+constructor TOptions<T>.Create(aOptions: T);
|
|
|
+begin
|
|
|
+ fOptions := aOptions;
|
|
|
+end;
|
|
|
+
|
|
|
+{ TOptionsBuilder<T> }
|
|
|
+
|
|
|
+constructor TOptionsBuilder<T>.Create;
|
|
|
+begin
|
|
|
+ fOptions := (PTypeInfo(TypeInfo(T)).TypeData.ClassType.Create) as T;
|
|
|
+end;
|
|
|
+
|
|
|
+function TOptionsBuilder<T>.Options: T;
|
|
|
+begin
|
|
|
+ Result := fOptions;
|
|
|
+end;
|
|
|
+
|
|
|
+end.
|