unit Main;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, XPMan, Mask, JvExMask, JvToolEdit, ComCtrls,
  JvComponent, JvSearchFiles, dpp_PascalParser, JvComponentBase;

type
  TFormMain = class(TForm)
    BtnExecute: TButton;
    BtnQuit: TButton;
    ProgressBar: TProgressBar;
    DEditDir: TJvDirectoryEdit;
    XPManifest1: TXPManifest;
    JvSearchFiles: TJvSearchFiles;
    CheckBoxSubDirs: TCheckBox;
    EditLogPath: TEdit;
    Label1: TLabel;
    procedure BtnQuitClick(Sender: TObject);
    procedure BtnExecuteClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private-Deklarationen }
    procedure ProcessFile(const Filename: string);
    function ParseUses(Parser: TPascalParser): Boolean;
  public
    { Public-Deklarationen }
  end;

var
  FormMain: TFormMain;

implementation

uses
  StrUtils;

{$R *.dfm}

var
  ConditionStack: TStrings;

procedure TFormMain.BtnQuitClick(Sender: TObject);
begin
  Close;
end;

procedure TFormMain.BtnExecuteClick(Sender: TObject);
var
  Dir: string;
  i: Integer;
begin
  Dir := DEditDir.Text;
  BtnExecute.Enabled := False;
  try
    JvSearchFiles.RootDirectory := Dir;
    if CheckBoxSubDirs.Checked then
      JvSearchFiles.DirOption := doIncludeSubDirs
    else
      JvSearchFiles.DirOption := doExcludeSubDirs;

    if JvSearchFiles.Search then
    begin
      ProgressBar.Position := 0;
      ProgressBar.Max := JvSearchFiles.Files.Count;
      for i := 0 to JvSearchFiles.Files.Count - 1 do
      begin
        if Pos('\cvs\', AnsiLowerCase(JvSearchFiles.Files[i])) = 0 then
          ProcessFile(JvSearchFiles.Files[i]);
        ProgressBar.StepIt;
        Application.ProcessMessages;
      end;
    end;
  finally
    BtnExecute.Enabled := True;
  end;
end;

function NextToken(Parser: TPascalParser; out Token: PTokenInfo): Boolean;
begin
  Result := True;
  while Parser.GetToken(Token) do
  begin
    if Token.Kind <> tkComment then
      Exit
    else
    begin
      if Token.ExKind = tekOption then
      begin
        if AnsiStartsText('{$ENDIF', Token.Value) or AnsiStartsText('{$IFEND', Token.Value) then
        begin
          ConditionStack.Delete(ConditionStack.Count - 1);
        end
        else
        if AnsiStartsText('{$IF', Token.Value) then
        begin
          ConditionStack.Add(Token.Value);
        end;
      end;
    end;
  end;
  Result := False;
end;

// returns True if the "JclUnitVersioning" unit is in the uses list
function TFormMain.ParseUses(Parser: TPascalParser): Boolean;
var
  Token: PTokenInfo;
begin
  Result := False;

  while NextToken(Parser, Token) do
  begin
    case Token.Kind of
      tkSymbol:
        if Token.Value = ';' then
          Break; // uses-end
      tkIdent:
        if CompareText(Token.Value, 'in') = 0 then
        begin
          if CompareText(Token.Value, 'JclUnitVersioning') = 0 then
          begin
            Result := True;
            Exit;
          end;
        end;
      //tkString: ;
    end;
  end;
end;

procedure TFormMain.ProcessFile(const Filename: string);
var
  s: string;
  Stream: TFileStream;
  Token: PTokenInfo;
  UsesToken, ImplToken, InitToken, FiniToken, LastEndToken: TTokenInfo;
  Parser: TPascalParser;
  InImplementation: Boolean;
  Modified, HasImplUses, HasInit, HasFini: Boolean;
  sl: TStrings;
  ConstDecl: string;
begin
  Modified := False;
  Stream := TFileStream.Create(Filename, fmOpenReadWrite or fmShareExclusive);
  ConditionStack := TStringList.Create;
  try
    SetLength(s, Stream.Size);
    Stream.Read(s[1], Length(s));

    Parser := TPascalParser.Create('', s);
    try
      s := ''; // release memory
      FillChar(ImplToken, 0, SizeOf(ImplToken));
      FillChar(LastEndToken, 0, SizeOf(LastEndToken));

      HasInit := False;
      HasFini := False;
      HasImplUses := False;
      InImplementation := False;
      while NextToken(Parser, Token) do
      begin
        if Token.Kind = tkIdent then
        begin
          if CompareText(Token.Value, 'implementation') = 0 then
          begin
            ImplToken := Token^;
            InImplementation := True;
          end
          else if InImplementation then
          begin
            if CompareText(Token.Value, 'uses') = 0 then
            begin
              HasImplUses := True;
              UsesToken := Token^; // save
              if not ParseUses(Parser) then
              begin
                Parser.Insert(UsesToken.EndIndex + 1,
                              sLineBreak +
                              '  {$IFDEF UNITVERSIONING}' + sLineBreak +
                              '  JclUnitVersioning,' + sLineBreak +
                              '  {$ENDIF UNITVERSIONING}');
                Parser.Index := UsesToken.EndIndex + 1;
                Modified := True;
              end;
            end
            else if CompareText(Token.Value, 'initialization') = 0 then
            begin
              HasInit := True;
              InitToken := Token^;
              if ConditionStack.Count > 0 then
                ShowMessage('initialization is IFDEFed: ' + Filename);
            end
            else if CompareText(Token.Value, 'finalization') = 0 then
            begin
              HasFini := True;
              FiniToken := Token^;
              if ConditionStack.Count > 0 then
                ShowMessage('finalization is IFDEFed: ' + Filename);
            end
            else if CompareText(Token.Value, 'end') = 0 then
               LastEndToken := Token^;
          end;
        end;
      end;

      if not HasImplUses and InImplementation then
      begin
        Modified := True;
        // do not change the text here because this would offset the index of
        // InitToken and LastEndToken
      end;

      if Modified then
      begin
        ConstDecl :=
          'const' + sLineBreak +
          '  UnitVersioning: TUnitVersionInfo = (' + sLineBreak +
          '    RCSfile: ''$' + 'RCSfile$'';' + sLineBreak +
          '    Revision: ''$' + 'Revision$'';' + sLineBreak +
          '    Date: ''$' + 'Date$'';' + sLineBreak +
          '    LogPath: ''' + Trim(EditLogPath.Text) + '''' + sLineBreak +
          '  );' + sLineBreak;

        if not HasInit then
        begin
          if LastEndToken.StartIndex = 0 then
            raise Exception.CreateFmt('Invalid .pas file: %s', [Filename]);
          Parser.Insert(LastEndToken.StartIndex,
                        '{$IFDEF UNITVERSIONING}' + sLineBreak +
                        ConstDecl +
                        sLineBreak +
                        'initialization' + sLineBreak +
                        '  RegisterUnitVersion(HInstance, UnitVersioning);' + sLineBreak +
                        sLineBreak +
                        'finalization' + sLineBreak +
                        '  UnregisterUnitVersion(HInstance);' + sLineBreak +
                        '{$ENDIF UNITVERSIONING}' + sLineBreak +
                        sLineBreak);
        end
        else
        begin
          if not HasFini then
          begin
            if LastEndToken.StartIndex = 0 then
              raise Exception.CreateFmt('Invalid .pas file: %s', [Filename]);
            Parser.Insert(LastEndToken.StartIndex,
                          sLineBreak +
                          '{$IFDEF UNITVERSIONING}' + sLineBreak +
                          'finalization' + sLineBreak +
                          '  UnregisterUnitVersion(HInstance);' + sLineBreak +
                          '{$ENDIF UNITVERSIONING}' + sLineBreak +
                          sLineBreak);
          end
          else
          begin
           Parser.Insert(FiniToken.EndIndex + 1,
                        sLineBreak +
                        '  {$IFDEF UNITVERSIONING}' + sLineBreak +
                        '  UnregisterUnitVersion(HInstance);' + sLineBreak +
                        '  {$ENDIF UNITVERSIONING}');
          end;

          Parser.Insert(InitToken.EndIndex + 1,
                        sLineBreak +
                        '  {$IFDEF UNITVERSIONING}' + sLineBreak +
                        '  RegisterUnitVersion(HInstance, UnitVersioning);' + sLineBreak +
                        '  {$ENDIF UNITVERSIONING}' + sLineBreak);

          Parser.Insert(InitToken.StartIndex,
                        '{$IFDEF UNITVERSIONING}' + sLineBreak +
                        ConstDecl +
                        '{$ENDIF UNITVERSIONING}' + sLineBreak +
                        sLineBreak);
        end;
      end;

      if not HasImplUses and InImplementation then
      begin
        Parser.Insert(ImplToken.EndIndex + 1,
                      sLineBreak +
                      sLineBreak +
                      '{$IFDEF UNITVERSIONING}' + sLineBreak +
                      'uses' + sLineBreak +
                      '  JclUnitVersioning;' + sLineBreak +
                      '{$ENDIF UNITVERSIONING}');
      end;

      if Modified then
      begin
        sl := TStringList.Create;
        sl.Text := Parser.Text;
        sl.SaveToFile(ExtractFilePath(Filename) + '\new\' + ExtractFileName(Filename));
        sl.Free;
        {Stream.Position := 0;
        Stream.Size := 0; // truncate
        Stream.Write(Parser.Text[1], Length(Parser.Text));}
      end;

    finally
      Parser.Free;
    end;
  finally
    FreeAndNil(ConditionStack);
    Stream.Free;
  end;
end;

procedure TFormMain.FormCreate(Sender: TObject);
begin
  DEditDir.Text := ExtractFileDir(ExtractFileDir(ExtractFileDir(ParamStr(0)))) + PathDelim + 'run';
end;

end.
