Обычно проекты, созданные в среде разработке My Visual Database, содержать только один модуль со скриптами – файл script.pas. Однако, если программа содержит много дополнительного функционала, то размер этого файла может быть огромным. Поэтому вполне логично разделить скрипты на логические части и хранить их в отдельных файлах.

Модульная архитектура улучшает восприятие исходного текста, позволяет повторно использовать отдельные модули, копируя нужные файлы в новый проект. Поэтому очередное улучшение “Справочника разработчика” связано с отображением модульной структуры проектов.

Хранимые данные

Поскольку заполнение сведений о модулях будет автоматическим, а модули могут храниться в иерархической системе папок, то структуру хранения данных нужно изменить, добавив несколько полей:

В поле path будет храниться полное имя модуля, поля parentID, nullID и isGroup служат для организации древовидного представления информации о модулях.

Формы

Форма табличного представления является динамической, поэтому для её создания понадобится всего лишь несколько сточек в файле dform.ini

[Unit_Tree]
table=unit
fields=unit.name,unit.status,unit.description,unit.editDate
captions=Название,Статус,Описание,Дата ред.
sort=ParentID,name
parentField=parentID
masterField=id_project
isDetail=True
parentControl=frmMain.tshUnit
filter=id=-1Code language: SQL (Structured Query Language) (sql)

Форма редактирования традиционная:

Так как в “Справочнике разработчика” реализована система локализации, необходимо добавить в файлы “English.txt” и “Русский.txt” перевод для элементов данной формы редактирования.

Обновление списка модулей

Процедура обновления UpdateProjectList_UpdateUnitList() может вызываться как отдельно (по кнопке на главной форме), так и в цикле выполнения UpdateProjectList_UpdateList(), поэтому отображение прогресса выполнения сделано в ней опционально. Второе важное отличие – автоматическое создание родительских узлов дерева, которые соответствуют физическим папкам для хранения фалов.

procedure UpdateProjectList_UpdateUnitList( AIDProject:integer; AShowProgress:boolean);
// обновление информации по модулям проекта
// AIDProject - ID проекта
// AShowProgress - флаг отображения прогресса
var
  tmpScriptDir: string;
  tmpFileList: string;
  tmpFiles: array of string;
  i: integer;
  tmpCount: integer;
  tmpMaxCount: integer;
  tmpUnitName: string;
  tmpSQL: string;
  tmpDataSet:TDataSet;
  tmpFileName: string;
  tmpIDParent: string;
  tmpName: string; // короткое имя
  tmpDirs: array of string;

  function FindParent:string;
  var
    j: integer;
    s: string;
    tmpPredResult: string;
  // поиск родительской папки по максимальному совпадению путей
  begin
    tmpSQL := 'SELECT id FROM unit WHERE (id_project='+IntToStr(AIDProject)+') and (isGroup = 1) and (path = '+StrToSQL(ExtractFilePath(tmpUnitName))+')';
    Result := SQLExecute(tmpSQL);
    // если имя составное, а родитель не найден, то создать родителя!
    if (Result = '') and (tmpUnitName <> tmpName) then
    begin
      tmpPredResult := 'NULL';
      tmpDirs := SplitString(tmpUnitName,'\');
      s:='';
      for j:= 0 to Length(tmpDirs)-2 do
      begin
        if s<>'' then
          s := s+'\';
        s := s + tmpDirs[j];
        // проверяем папки
        tmpSQL := 'SELECT id FROM unit WHERE (isGroup = 1) and (id_project = '+IntToStr(AIDProject)+') and (path = '+StrToSQL(s)+') ';
        Result := SQLExecute(tmpSQL);
        if Result = '' then
        begin
          tmpSQL := 'INSERT INTO unit (id_project,path,name,status,ParentID,isGroup) VALUES ('+IntToStr(AIDProject)+','+StrToSQL(s)+','+StrToSQL(ExtractFileName(s))+','+'"Новая папка",'+tmpPredResult+',1)';
          SQLExecute(tmpSQL);
          Result := IntToStr( Last_Insert_ID() );
        end;
        tmpPredResult := Result;
      end;
    end
    else
    if Result = '' then
      Result := 'NULL';
  end;


begin
  if AShowProgress then
    Progress(0,0,'Обновление данных о модулях');
  try
    tmpScriptDir := SQLExecute('SELECT path FROM project WHERE id='+IntToStr(AIDProject))+'\script';

    if not DirectoryExists(tmpScriptDir) then
      exit;

    // проверка имеющихся записей
    tmpSQL := 'SELECT count(*) FROM unit WHERE id_project='+IntToStr(AIDProject);
    tmpMaxCount := SQLExecute(tmpSQL);
    tmpCount := 0;
    tmpSQL := 'SELECT * FROM unit WHERE id_project='+IntToStr(AIDProject);
    SQLQuery(tmpSQL,tmpDataSet);
    try
      while not tmpDataSet.EOF do
      begin
        if AShowProgress then
          Progress(tmpCount,tmpMaxCount,'Проверка папок и файлов')
        else
          Application.ProcessMessages;
        tmpFileName := Trim( tmpScriptDir +'\'+ tmpDataSet.FieldByName('path').asString );
        if (tmpDataSet.FieldByName('isGroup').asInteger = 0) AND (not FileExists( tmpFileName )) then
        begin
          tmpSQL := 'UPDATE unit SET status = "Модуль не найден" WHERE id = '+tmpDataSet.FieldByName('id').asString;
          SQLExecute(tmpSQL);
        end
        else
        if (tmpDataSet.FieldByName('isGroup').asInteger = 1) AND (not DirectoryExists( tmpFileName )) then
        begin
          tmpSQL := 'UPDATE unit SET status = "Папка не найдена" WHERE id = '+tmpDataSet.FieldByName('id').asString;
          SQLExecute(tmpSQL);
        end;
        tmpDataSet.Next;
        inc(tmpCount);
      end;
    finally
      tmpDataSet.Free;
    end;
    // добавление новых
    tmpFileList := Trim(GetFilesList(tmpScriptDir,'*.pas'));

    tmpFiles := SplitString(tmpFileList,chr(10));
    tmpMaxCount := Length(tmpFiles);
    for i:=0 to Length(tmpFiles)-1 do
    begin
      if AShowProgress then
        Progress(i,tmpMaxCount,'Анализ модулей ')
      else
        Application.ProcessMessages;

      tmpUnitName := Trim( tmpFiles[i] );
      tmpName := ExtractFileName(tmpUnitName);

      delete(tmpUnitName,1,Length(tmpScriptDir)+1);
      tmpSQL := 'SELECT count(*) FROM unit WHERE (id_project = '+IntToStr(AIDProject)+') AND (path = '+StrToSQL(tmpUnitName)+')';
      if SQLExecute(tmpSQL) = 0 then // модуль не найден
      begin
        //
        tmpIDParent := FindParent;
        tmpSQL := 'INSERT INTO unit (id_project,path,name,status,ParentID,isGroup) VALUES ('+IntToStr(AIDProject)+','+StrToSQL(tmpUnitName)+','+StrToSQL(tmpName)+','+'"Новый модуль",'+tmpIDParent+',0)';
        SQLExecute(tmpSQL);
      end;
    end;
  finally
    if AShowProgress then
      Progress();
  end;
end;
Code language: Delphi (delphi)

Процедура работает в две фазы:

  • Проверка имеющихся записей
  • Добавление новых

Отсутствие папки со скриптами не является ошибкой, так как могут быть проекты, отлично справляющиеся со своими задачами без единой строчки кода.

Форматирование исходных кодов

В модуль formatter.pas добавлена процедура Formatter_FormatDir(), которая умеет форматировать папки с исходными файлами.

const
  FORMATTER_PARAM_DIR = '-silent -config:%0.s -d %1.s -r '; //

procedure Formatter_FormatDir( APath:string );
// форматирование исходного текста Delphi внешней утилитой - все файлы в папке, рекурсивно
var
  tmpConfigFile: string;
  tmpFormatter: string;
  tmpParam: string;
begin
  // утилита и файл конфигурации лежат в специальной папке внутри нашего проекта
  tmpFormatter := ExtractFilePath( Application.ExeName ) + FORMATTER_EXE;
  tmpConfigFile := ExtractFilePath( Application.ExeName ) + FORMATTER_CONFIG;
  // если чего-то не нашли, то возбуждаем исключение
  if not FileExists(tmpFormatter) then
    RaiseException('Утилита форматирования не найдена: '+tmpFormatter)
  else
  if (FORMATTER_CONFIG <> '') and  not FileExists(tmpConfigFile) then
    RaiseException('Файл конфигурации не найден: '+tmpConfigFile)
  else
  begin
    // делаем конвертацию
    tmpParam := Format(FORMATTER_PARAM_DIR,[tmpConfigFile,APath]);
    OpenFile(tmpParam,tmpFormatter);
  end;
end;
Code language: Delphi (delphi)

Внимание! В файле настройки правил для утилиты formatter.exe необходимо изменить следующий параметр форматирования:

  <Option Name="DelphiFormatter.CompilerDirectivesCapitalization" Type="Borland.Together.OpenAPI3.Config.CAPITALIZATION" Category="DelphiCapitalization" Value="AsIs" Levels="Formatter" />Code language: HTML, XML (xml)

Просмотр файла

Если выделить модуль, а затем в нижней части экрана выбрать вкладку “Код”, то в ней отобразится исходный код модуля с подсветкой синтаксиса.

Другие доработки

Форма dtfUnit_Tree создала прецедент, в котором форма одновременно является зависимой и отображает древовидные данные, поэтому семантику описания динамических форм пришлось доработать, разделив понятия

  • parentField – поле для хранения ссылки на родительский узел дерева
  • masterField – поле для хранения связи с главной таблицей

Доработки также затронули следующие процедуры и функции: DTF_Create(), frmUpdateProjectList_btnUpdate_OnClick(), UserApp_GetMasterID(), UserApp_UpdateDD().

Ссылки

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *