1{ 2 This file is part of the Free Pascal run time library. 3 Copyright (c) 2003 by the Free Pascal development team 4 5 CustomApplication class. 6 7 See the file COPYING.FPC, included in this distribution, 8 for details about the copyright. 9 10 This program is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 14 **********************************************************************} 15{$mode objfpc} 16{$h+} 17unit CustApp; 18 19Interface 20 21uses SysUtils,Classes,singleinstance; 22 23Type 24 TStringArray = Array of string; 25 TExceptionEvent = Procedure (Sender : TObject; E : Exception) Of Object; 26 TEventLogTypes = Set of TEventType; 27 28 { TCustomApplication } 29 30 TCustomApplication = Class(TComponent) 31 Private 32 FEventLogFilter: TEventLogTypes; 33 FOnException: TExceptionEvent; 34 FSingleInstance: TBaseSingleInstance; 35 FSingleInstanceClass: TBaseSingleInstanceClass; // set before FSingleInstance is created 36 FSingleInstanceEnabled: Boolean; // set before Initialize is called 37 FTerminated : Boolean; 38 FHelpFile, 39 FTitle : String; 40 FOptionChar : Char; 41 FCaseSensitiveOptions : Boolean; 42 FStopOnException : Boolean; 43 FExceptionExitCode : Integer; 44 function GetEnvironmentVar(VarName : String): String; 45 function GetExeName: string; 46 Function GetLocation : String; 47 function GetSingleInstance: TBaseSingleInstance; 48 procedure SetSingleInstanceClass( 49 const ASingleInstanceClass: TBaseSingleInstanceClass); 50 function GetTitle: string; 51 Protected 52 function GetOptionAtIndex(AIndex: Integer; IsLong: Boolean): String; 53 procedure SetTitle(const AValue: string); Virtual; 54 Function GetConsoleApplication : boolean; Virtual; 55 Procedure DoRun; Virtual; 56 Function GetParams(Index : Integer) : String;virtual; 57 function GetParamCount: Integer;Virtual; 58 Procedure DoLog(EventType : TEventType; const Msg : String); virtual; 59 Public 60 constructor Create(AOwner: TComponent); override; 61 destructor Destroy; override; 62 // Some Delphi methods. 63 procedure HandleException(Sender: TObject); virtual; 64 procedure Initialize; virtual; 65 procedure Run; 66 procedure ShowException(E: Exception);virtual; 67 procedure Terminate; virtual; 68 procedure Terminate(AExitCode : Integer) ; virtual; 69 // Extra methods. 70 function FindOptionIndex(Const S : String; Var Longopt : Boolean; StartAt : Integer = -1) : Integer; 71 Function GetOptionValue(Const S : String) : String; 72 Function GetOptionValue(Const C: Char; Const S : String) : String; 73 Function GetOptionValues(Const C: Char; Const S : String) : TStringArray; 74 Function HasOption(Const S : String) : Boolean; 75 Function HasOption(Const C : Char; Const S : String) : Boolean; 76 Function CheckOptions(Const ShortOptions : String; Const Longopts : TStrings; Opts,NonOpts : TStrings; AllErrors : Boolean = False) : String; 77 Function CheckOptions(Const ShortOptions : String; Const Longopts : Array of string; Opts,NonOpts : TStrings; AllErrors : Boolean = False) : String; 78 Function CheckOptions(Const ShortOptions : String; Const Longopts : TStrings; AllErrors : Boolean = False) : String; 79 Function CheckOptions(Const ShortOptions : String; Const LongOpts : Array of string; AllErrors : Boolean = False) : String; 80 Function CheckOptions(Const ShortOptions : String; Const LongOpts : String; AllErrors : Boolean = False) : String; 81 Function GetNonOptions(Const ShortOptions : String; Const Longopts : Array of string) : TStringArray; 82 Procedure GetNonOptions(Const ShortOptions : String; Const Longopts : Array of string; NonOptions : TStrings); 83 Procedure GetEnvironmentList(List : TStrings;NamesOnly : Boolean); 84 Procedure GetEnvironmentList(List : TStrings); 85 Procedure Log(EventType : TEventType; const Msg : String); 86 Procedure Log(EventType : TEventType; const Fmt : String; const Args : array of const); 87 // Delphi properties 88 property ExeName: string read GetExeName; 89 property HelpFile: string read FHelpFile write FHelpFile; 90 property Terminated: Boolean read FTerminated; 91 property Title: string read FTitle write SetTitle; 92 property OnException: TExceptionEvent read FOnException write FOnException; 93 // Extra properties 94 Property ConsoleApplication : Boolean Read GetConsoleApplication; 95 Property Location : String Read GetLocation; 96 Property Params [Index : integer] : String Read GetParams; 97 Property ParamCount : Integer Read GetParamCount; 98 Property EnvironmentVariable[envName : String] : String Read GetEnvironmentVar; 99 Property OptionChar : Char Read FoptionChar Write FOptionChar; 100 Property CaseSensitiveOptions : Boolean Read FCaseSensitiveOptions Write FCaseSensitiveOptions; 101 Property StopOnException : Boolean Read FStopOnException Write FStopOnException; 102 Property ExceptionExitCode : Longint Read FExceptionExitCode Write FExceptionExitCode; 103 Property EventLogFilter : TEventLogTypes Read FEventLogFilter Write FEventLogFilter; 104 Property SingleInstance: TBaseSingleInstance read GetSingleInstance; 105 Property SingleInstanceClass: TBaseSingleInstanceClass read FSingleInstanceClass write SetSingleInstanceClass; 106 Property SingleInstanceEnabled: Boolean read FSingleInstanceEnabled write FSingleInstanceEnabled; 107 end; 108 109var CustomApplication : TCustomApplication = nil; 110 111Implementation 112 113{$ifdef darwin} 114uses 115 MacOSAll; 116{$endif} 117 118{ TCustomApplication } 119 120function TCustomApplication.GetExeName: string; 121{$if defined(darwin)} 122var 123 mainBundle: CFBundleRef; 124 executableUrl: CFURLRef; 125 executableFSPath: CFStringRef; 126 utf16len: ptrint; 127 error: boolean; 128begin 129 error:=false; 130 { Get main bundle. This even works most of the time for command line 131 applications 132 } 133 mainbundle:=CFBundleGetMainBundle; 134 if assigned(mainbundle) then 135 begin 136 { get the URL pointing to the executable of the bundle } 137 executableUrl:=CFBundleCopyExecutableURL(mainBundle); 138 if assigned(executableUrl) then 139 begin 140 { convert the url to a POSIX path } 141 executableFSPath:=CFURLCopyFileSystemPath(executableUrl,kCFURLPOSIXPathStyle); 142 CFRelease(executableUrl); 143 { convert to UTF-8 -- this is not really clean since in theory the 144 ansi-encoding could be different, but 145 a) all file i/o routines on Darwin expect utf-8-encoded strings 146 b) there is no easy way to convert the Unix LANG encoding 147 setting to an equivalent CoreFoundation encoding 148 } 149 utf16len:=CFStringGetLength(executableFSPath); 150 // +1 for extra terminating #0 in the worst case, so the pos below 151 // will always find the #0 152 setlength(result,utf16len*3+1); 153 if CFStringGetCString(executableFSPath,@result[1],length(result),kCFStringEncodingUTF8) then 154 { truncate to actual length, #0 cannot appear in a file path } 155 setlength(result,pos(#0,result)-1) 156 else 157 error:=true; 158 CFRelease(executableFSPath); 159 end 160 else 161 error:=true; 162 end 163 else 164 error:=true; 165 if error then 166 { can't do better than this } 167 Result:=Paramstr(0); 168end; 169{$else darwin} 170begin 171 Result:=Paramstr(0); 172end; 173{$endif darwin} 174 175Procedure SysGetEnvironmentList(List : TStrings;NamesOnly : Boolean); 176 177var 178 s : string; 179 i,l,j,count : longint; 180 181begin 182 count:=GetEnvironmentVariableCount; 183 if count>0 then 184 for j:=1 to count do 185 begin 186 s:=GetEnvironmentString(j); 187 l:=Length(s); 188 If NamesOnly then 189 begin 190 I:=pos('=',s); 191 If (I>0) then 192 S:=Copy(S,1,I-1); 193 end; 194 List.Add(S); 195 end; 196end; 197 198function TCustomApplication.GetEnvironmentVar(VarName : String): String; 199begin 200 Result:=GetEnvironmentVariable(VarName); 201end; 202 203procedure TCustomApplication.GetEnvironmentList(List: TStrings; 204 NamesOnly: Boolean); 205 206begin 207 // Routine must be in custapp.inc 208 SysGetEnvironmentList(List,NamesOnly); 209end; 210 211procedure TCustomApplication.GetEnvironmentList(List: TStrings); 212 213begin 214 GetEnvironmentList(List,False); 215end; 216 217function TCustomApplication.GetLocation: String; 218begin 219 Result:=ExtractFilePath(GetExeName); 220end; 221 222function TCustomApplication.GetParamCount: Integer; 223begin 224 Result:=System.ParamCount; 225end; 226 227function TCustomApplication.GetTitle: string; 228begin 229 Result:=FTitle; 230end; 231 232function TCustomApplication.GetParams(Index: Integer): String; 233begin 234 Result:=ParamStr(Index); 235end; 236 237function TCustomApplication.GetSingleInstance: TBaseSingleInstance; 238begin 239 if FSingleInstance = nil then 240 begin 241 if FSingleInstanceClass=Nil then 242 Raise ESingleInstance.Create('No single instance provider class set! Include a single-instance class unit such as advsingleinstance'); 243 FSingleInstance := FSingleInstanceClass.Create(Self); 244 end; 245 Result := FSingleInstance; 246end; 247 248procedure TCustomApplication.SetTitle(const AValue: string); 249begin 250 FTitle:=AValue; 251end; 252 253function TCustomApplication.GetConsoleApplication: boolean; 254begin 255 Result:=IsConsole; 256end; 257 258procedure TCustomApplication.DoRun; 259begin 260 if Assigned(FSingleInstance) then 261 if FSingleInstance.IsServer then 262 FSingleInstance.ServerCheckMessages; 263 264 // Override in descendent classes. 265end; 266 267procedure TCustomApplication.DoLog(EventType: TEventType; const Msg: String); 268 269begin 270 // Do nothing, override in descendants 271end; 272 273procedure TCustomApplication.Log(EventType: TEventType; const Msg: String); 274 275begin 276 If (FEventLogFilter=[]) or (EventType in FEventLogFilter) then 277 DoLog(EventType,Msg); 278end; 279 280procedure TCustomApplication.Log(EventType: TEventType; const Fmt: String; 281 const Args: array of const); 282begin 283 try 284 Log(EventType, Format(Fmt, Args)); 285 except 286 On E : Exception do 287 Log(etError,Format('Error formatting message "%s" with %d arguments: %s',[Fmt,Length(Args),E.Message])); 288 end 289end; 290 291constructor TCustomApplication.Create(AOwner: TComponent); 292begin 293 inherited Create(AOwner); 294 FOptionChar:='-'; 295 FCaseSensitiveOptions:=True; 296 FStopOnException:=False; 297 FSingleInstanceClass := DefaultSingleInstanceClass; 298end; 299 300destructor TCustomApplication.Destroy; 301begin 302 inherited Destroy; 303end; 304 305procedure TCustomApplication.HandleException(Sender: TObject); 306begin 307 If Not (ExceptObject is Exception) then 308 SysUtils.showexception(ExceptObject,ExceptAddr) 309 else 310 begin 311 If Not Assigned(FOnexception) then 312 ShowException(Exception(ExceptObject)) 313 else 314 FOnException(Sender,Exception(ExceptObject)); 315 end; 316 If FStopOnException then 317 Terminate(ExceptionExitCode); 318end; 319 320 321procedure TCustomApplication.Initialize; 322begin 323 FTerminated:=False; 324 if FSingleInstanceEnabled then 325 begin 326 case SingleInstance.Start of 327 siClient: 328 begin 329 SingleInstance.ClientPostParams; 330 FTerminated:=True; 331 end; 332 siNotResponding: 333 FTerminated:=True; 334 end; 335 end; 336end; 337 338procedure TCustomApplication.Run; 339 340begin 341 Repeat 342 Try 343 DoRun; 344 except 345 HandleException(Self); 346 end; 347 Until FTerminated; 348end; 349 350procedure TCustomApplication.SetSingleInstanceClass( 351 const ASingleInstanceClass: TBaseSingleInstanceClass); 352begin 353 Assert((FSingleInstance = nil) and (ASingleInstanceClass <> nil)); 354 FSingleInstanceClass := ASingleInstanceClass; 355end; 356 357procedure TCustomApplication.ShowException(E: Exception); 358 359begin 360 Sysutils.ShowException(E,ExceptAddr) 361end; 362 363procedure TCustomApplication.Terminate; 364begin 365 Terminate(ExitCode); 366end; 367 368procedure TCustomApplication.Terminate(AExitCode : Integer) ; 369 370begin 371 FTerminated:=True; 372 ExitCode:=AExitCode; 373end; 374 375function TCustomApplication.GetOptionAtIndex(AIndex : Integer; IsLong: Boolean): String; 376 377Var 378 P : Integer; 379 O : String; 380 381begin 382 Result:=''; 383 If (AIndex=-1) then 384 Exit; 385 If IsLong then 386 begin // Long options have form --option=value 387 O:=Params[AIndex]; 388 P:=Pos('=',O); 389 If (P=0) then 390 P:=Length(O); 391 Delete(O,1,P); 392 Result:=O; 393 end 394 else 395 begin // short options have form '-o value' 396 If (AIndex<ParamCount) then 397 if (Copy(Params[AIndex+1],1,1)<>OptionChar) then 398 Result:=Params[AIndex+1]; 399 end; 400 end; 401 402 403function TCustomApplication.GetOptionValue(const S: String): String; 404begin 405 Result:=GetoptionValue(#255,S); 406end; 407 408function TCustomApplication.GetOptionValue(const C: Char; const S: String 409 ): String; 410 411Var 412 B : Boolean; 413 I : integer; 414 415begin 416 Result:=''; 417 I:=FindOptionIndex(C,B); 418 If (I=-1) then 419 I:=FindOptionIndex(S,B); 420 If I<>-1 then 421 Result:=GetOptionAtIndex(I,B); 422end; 423 424function TCustomApplication.GetOptionValues(const C: Char; const S: String): TStringArray; 425 426Var 427 I,Cnt : Integer; 428 B : Boolean; 429 430begin 431 SetLength(Result,ParamCount); 432 Cnt:=0; 433 I:=-1; 434 Repeat 435 I:=FindOptionIndex(C,B,I); 436 If I<>-1 then 437 begin 438 Inc(Cnt); 439 Dec(I); 440 end; 441 Until I=-1; 442 Repeat 443 I:=FindOptionIndex(S,B,I); 444 If I<>-1 then 445 begin 446 Inc(Cnt); 447 Dec(I); 448 end; 449 Until I=-1; 450 SetLength(Result,Cnt); 451 Cnt:=0; 452 I:=-1; 453 Repeat 454 I:=FindOptionIndex(C,B,I); 455 If (I<>-1) then 456 begin 457 Result[Cnt]:=GetOptionAtIndex(I,False); 458 Inc(Cnt); 459 Dec(i); 460 end; 461 Until (I=-1); 462 I:=-1; 463 Repeat 464 I:=FindOptionIndex(S,B,I); 465 If I<>-1 then 466 begin 467 Result[Cnt]:=GetOptionAtIndex(I,True); 468 Inc(Cnt); 469 Dec(i); 470 end; 471 Until (I=-1); 472end; 473 474function TCustomApplication.HasOption(const S: String): Boolean; 475 476Var 477 B : Boolean; 478 479begin 480 Result:=FindOptionIndex(S,B)<>-1; 481end; 482 483function TCustomApplication.FindOptionIndex(const S: String; 484 var Longopt: Boolean; StartAt : Integer = -1): Integer; 485 486Var 487 SO,O : String; 488 I,P : Integer; 489 490begin 491 If Not CaseSensitiveOptions then 492 SO:=UpperCase(S) 493 else 494 SO:=S; 495 Result:=-1; 496 I:=StartAt; 497 if (I=-1) then 498 I:=ParamCount; 499 While (Result=-1) and (I>0) do 500 begin 501 O:=Params[i]; 502 // - must be seen as an option value 503 If (Length(O)>1) and (O[1]=FOptionChar) then 504 begin 505 Delete(O,1,1); 506 LongOpt:=(Length(O)>0) and (O[1]=FOptionChar); 507 If LongOpt then 508 begin 509 Delete(O,1,1); 510 P:=Pos('=',O); 511 If (P<>0) then 512 O:=Copy(O,1,P-1); 513 end; 514 If Not CaseSensitiveOptions then 515 O:=UpperCase(O); 516 If (O=SO) then 517 Result:=i; 518 end; 519 Dec(i); 520 end; 521end; 522 523function TCustomApplication.HasOption(const C: Char; const S: String): Boolean; 524 525Var 526 B : Boolean; 527 528begin 529 Result:=(FindOptionIndex(C,B)<>-1) or (FindOptionIndex(S,B)<>-1); 530end; 531 532 533function TCustomApplication.CheckOptions(const ShortOptions: String; 534 const Longopts: TStrings; AllErrors: Boolean): String; 535 536begin 537 Result:=CheckOptions(ShortOptions,LongOpts,Nil,Nil,AllErrors); 538end; 539 540ResourceString 541 SErrInvalidOption = 'Invalid option at position %d: "%s"'; 542 SErrNoOptionAllowed = 'Option at position %d does not allow an argument: %s'; 543 SErrOptionNeeded = 'Option at position %d needs an argument : %s'; 544 545function TCustomApplication.CheckOptions(const ShortOptions: String; 546 const Longopts: TStrings; Opts, NonOpts: TStrings; AllErrors: Boolean 547 ): String; 548 549Var 550 I,J,L,P : Integer; 551 O,OV,SO : String; 552 UsedArg,HaveArg : Boolean; 553 554 Function FindLongOpt(S : String) : boolean; 555 556 Var 557 I : integer; 558 559 begin 560 Result:=Assigned(LongOpts); 561 if Not Result then 562 exit; 563 If CaseSensitiveOptions then 564 begin 565 I:=LongOpts.Count-1; 566 While (I>=0) and (LongOpts[i]<>S) do 567 Dec(i); 568 end 569 else 570 begin 571 S:=UpperCase(S); 572 I:=LongOpts.Count-1; 573 While (I>=0) and (UpperCase(LongOpts[i])<>S) do 574 Dec(i); 575 end; 576 Result:=(I<>-1); 577 end; 578 579 Procedure AddToResult(Const Msg : string); 580 581 begin 582 If (Result<>'') then 583 Result:=Result+sLineBreak; 584 Result:=Result+Msg; 585 end; 586 587begin 588 If CaseSensitiveOptions then 589 SO:=Shortoptions 590 else 591 SO:=LowerCase(Shortoptions); 592 Result:=''; 593 I:=1; 594 While (I<=ParamCount) and ((Result='') or AllErrors) do 595 begin 596 O:=Paramstr(I); 597 If (Length(O)=0) or (O[1]<>FOptionChar) then 598 begin 599 If Assigned(NonOpts) then 600 NonOpts.Add(O); 601 end 602 else 603 begin 604 If (Length(O)<2) then 605 AddToResult(Format(SErrInvalidOption,[i,O])) 606 else 607 begin 608 HaveArg:=False; 609 OV:=''; 610 // Long option ? 611 If (O[2]=FOptionChar) then 612 begin 613 Delete(O,1,2); 614 J:=Pos('=',O); 615 If J<>0 then 616 begin 617 HaveArg:=true; 618 OV:=O; 619 Delete(OV,1,J); 620 O:=Copy(O,1,J-1); 621 end; 622 // Switch Option 623 If FindLongopt(O) then 624 begin 625 If HaveArg then 626 AddToResult(Format(SErrNoOptionAllowed,[I,O])); 627 end 628 else 629 begin // Required argument 630 If FindLongOpt(O+':') then 631 begin 632 If Not HaveArg then 633 AddToResult(Format(SErrOptionNeeded,[I,O])); 634 end 635 else 636 begin // Optional Argument. 637 If not FindLongOpt(O+'::') then 638 AddToResult(Format(SErrInvalidOption,[I,O])); 639 end; 640 end; 641 end 642 else // Short Option. 643 begin 644 HaveArg:=(I<ParamCount) and (Length(ParamStr(I+1))>0) and (ParamStr(I+1)[1]<>FOptionChar); 645 UsedArg:=False; 646 If Not CaseSensitiveOptions then 647 O:=LowerCase(O); 648 L:=Length(O); 649 J:=2; 650 While ((Result='') or AllErrors) and (J<=L) do 651 begin 652 P:=Pos(O[J],SO); 653 If (P=0) or (O[j]=':') then 654 AddToResult(Format(SErrInvalidOption,[I,O[J]])) 655 else 656 begin 657 If (P<Length(SO)) and (SO[P+1]=':') then 658 begin 659 // Required argument 660 If ((P+1)=Length(SO)) or (SO[P+2]<>':') Then 661 If (J<L) or not haveArg then // Must be last in multi-opt !! 662 AddToResult(Format(SErrOptionNeeded,[I,O[J]])); 663 O:=O[j]; // O is added to arguments. 664 UsedArg:=True; 665 end; 666 end; 667 Inc(J); 668 end; 669 HaveArg:=HaveArg and UsedArg; 670 If HaveArg then 671 begin 672 Inc(I); // Skip argument. 673 OV:=Paramstr(I); 674 end; 675 end; 676 If HaveArg and ((Result='') or AllErrors) then 677 If Assigned(Opts) then 678 Opts.Add(O+'='+OV); 679 end; 680 end; 681 Inc(I); 682 end; 683end; 684 685function TCustomApplication.CheckOptions(const ShortOptions: String; 686 const Longopts: array of string; Opts, NonOpts: TStrings; AllErrors: Boolean 687 ): String; 688Var 689 L : TStringList; 690 I : Integer; 691 692begin 693 L:=TStringList.Create; 694 Try 695 For I:=0 to High(LongOpts) do 696 L.Add(LongOpts[i]); 697 Result:=CheckOptions(ShortOptions,L,Opts,NonOpts,AllErrors); 698 Finally 699 L.Free; 700 end; 701end; 702 703function TCustomApplication.CheckOptions(const ShortOptions: String; 704 const LongOpts: array of string; AllErrors: Boolean): String; 705 706Var 707 L : TStringList; 708 I : Integer; 709 710begin 711 L:=TStringList.Create; 712 Try 713 For I:=0 to High(LongOpts) do 714 L.Add(LongOpts[i]); 715 Result:=CheckOptions(ShortOptions,L,AllErrors); 716 Finally 717 L.Free; 718 end; 719end; 720 721function TCustomApplication.CheckOptions(const ShortOptions: String; 722 const LongOpts: String; AllErrors: Boolean): String; 723 724Const 725 SepChars = ' '#10#13#9; 726 727Var 728 L : TStringList; 729 Len,I,J : Integer; 730 731begin 732 L:=TStringList.Create; 733 Try 734 I:=1; 735 Len:=Length(LongOpts); 736 While I<=Len do 737 begin 738 While Isdelimiter(SepChars,LongOpts,I) do 739 Inc(I); 740 J:=I; 741 While (J<=Len) and Not IsDelimiter(SepChars,LongOpts,J) do 742 Inc(J); 743 If (I<=J) then 744 L.Add(Copy(LongOpts,I,(J-I))); 745 I:=J+1; 746 end; 747 Result:=CheckOptions(Shortoptions,L,AllErrors); 748 Finally 749 L.Free; 750 end; 751end; 752 753function TCustomApplication.GetNonOptions(const ShortOptions: String; 754 const Longopts: array of string): TStringArray; 755 756Var 757 NO : TStrings; 758 I : Integer; 759 760begin 761 No:=TStringList.Create; 762 try 763 GetNonOptions(ShortOptions,LongOpts,No); 764 SetLength(Result,NO.Count); 765 For I:=0 to NO.Count-1 do 766 Result[I]:=NO[i]; 767 finally 768 NO.Free; 769 end; 770end; 771 772procedure TCustomApplication.GetNonOptions(const ShortOptions: String; 773 const Longopts: array of string; NonOptions: TStrings); 774 775Var 776 S : String; 777 778begin 779 S:=CheckOptions(ShortOptions,LongOpts,Nil,NonOptions,true); 780 if (S<>'') then 781 Raise EListError.Create(S); 782end; 783 784end. 785