Delphi 3. Библиотека программиста

       

Универсальный анализатор командных строк


Если меня что и раздражает в программировании, так это необходимость в десятый (или сотый) раз писать код для выполнения одной и той же задачи. С анализом командных строк дело обстоит именно так - он необходим во всех фильтрах без исключения, но после того как вы напишете этот код пару раз, задача становится на редкость скучной . Поэтому я и постарался создать некий обобщенный анализатор, который ценой минимальных усилий с моей стороны обрабатывает командную строку и присваивает нужные значения переменным. Благодаря этому я могу уделить больше внимания самому фильтру (то есть основной задаче), а не второстепенному анализатору.

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

Основная идея заключается в том, чтобы определить префиксы параметров, указать тип каждого параметра и задать значения по умолчанию. Структура, содержащая всю эту информацию, передается анализатору, который обрабатывает командную строку и присваивает значения найденным параметрам. Если при обработке строки происходит ошибка (скажем, обнаружи вается неизвестный параметр или там, где должен стоять переключатель, оказывается число), анализатор выдает сообщение об ошибке, прерывает работу и уведомляет вызывающую функцию. Ну как, просто? Да, просто сказать… запрограммировать несколько сложнее.

Информация об отдельном параметре хранится в виде записи OptionsRec, описанной в листинге 1.3. В нем приведен полный исходный текст всего модуля CmdLine. Создайте новый файл в редакторе, введите и сохраните код под именем CMDLINE.PAS.

Листинг 1.3. Модуль CmdLine

{ CMDLINE.PAS - Анализатор командной строки Автор: Джим Мишель Дата последней редакции: 04/05/97 } unit cmdline; interface type OptionType = (otBool, otInt, otString, otFilename); pOptionRec = ^OptionRec; OptionRec = record OptionChar : char; case Option : OptionType of otBool : (OnOff : Boolean); otInt : (Value : Integer); otString : (Param : ShortString); otFilename : (Filename : ShortString); end; pOptionsArray = ^OptionsArray; OptionsArray = Array [1..1] of OptionRec; { GetOptionRec - возвращает указатель на запись из передаваемого массива параметров Options, соответствующую заданному префиксу. Возвращает Nil, если префикс отсутствует в массиве. } function GetOptionRec ( Options : pOptionsArray; nOptions : Integer; OptionChar : char ) : pOptionRec; { ProcessCommandLine - обрабатывает командную строку в соответствии со списком параметров, переданным в массиве Options. Возвращает True при успешном завершении и False - в случае ошибки. } function ProcessCommandLine ( Options : pOptionsArray; nOptions : Integer ) : Boolean;

implementation uses SysUtils; { GetOptionRec - возвращает указатель на запись из передаваемого массива параметров Options, соответствующую заданному префиксу. Возвращает Nil, если префикс отсутствует в массиве. } function GetOptionRec ( Options : pOptionsArray; nOptions : Integer; OptionChar : char ) : pOptionRec; var i : Integer; begin Result := Nil; for i := 1 to nOptions do begin if (Options^[i].OptionChar = OptionChar) then begin Result := @Options^[i].OptionChar; Break; end; end; end; { ProcessBool Определяет состояние параметра-переключателя (вкл/выкл). Если в Param передается пустая строка, параметр считается включенным (+). В противном случае строка должна начинаться со знака + или -,в соответствии с которым присваивается значение переменной OnOff. } function ProcessBool ( Param : String; var OnOff : Boolean ) : Boolean; begin Result := True; if (Length (Param) = 0) then begin OnOff := True; Exit; end; case Param[1] of "+" : OnOff := True; "-" : OnOff := False; else begin WriteLn ("Error: + or - expected"); Result := False; end; end; end; { ProcessInt Извлекает целое число из переданного параметра командной строки. } function ProcessInt ( Param : String; var Value : Integer ) : Boolean; begin if (Length (Param) = 0) then begin Result := False; WriteLn ("Error: integer expected"); Exit; end; Result := True; try Value := StrToInt (Param); except WriteLn ("Error: integer expected"); Result := False; end; end; { ProcessString Копирует переданную строку в переменную Option. Проверка ошибок не выполняется, а пустая строка считается допустимым параметром. } function ProcessString ( Param : String; var Option : ShortString ) : Boolean; begin Option := Param; Result := True; end; { ProcessFilename Извлекает имя файла из переданного параметра командной строки. В настоящей реализации функция просто вызывает ProcessString и копирует строковый параметр в Filename. Возможно, в будущих версиях она будет проверять, является ли строка допустимым именем файла, или же будет использоваться для преобразования короткого имени в полное, включающее путь. } function ProcessFilename ( Param : String; var Filename : ShortString ) : Boolean; begin Result := ProcessString (Param, Filename); end; { CheckParam Проверяет, принадлежит ли аргумент командной строки Param заданному списку параметров. Если префикс будет признан допустимым, обрабатывает параметр в соответствии с его типом (логическим, целым, строковым или файловым). Возвращает True при правильной обработке и сохранении параметра и False в противном случае. } function CheckParam ( Param : String; Options : pOptionsArray; nOptions : Integer ) : Boolean; var Rec : pOptionRec; Option : String; begin Result := False; if (Param[1] in ["-", "/"]) then begin if (Length (Param) < 2) then begin WriteLn ("Invalid option"); end else begin Rec := GetOptionRec (Options, nOptions, Param[2]); if (Rec <> Nil) then begin Option := Copy (Param, 3, Length (Param) - 2); case Rec^.Option of otBool : Result := ProcessBool (Option, Rec.OnOff); otInt : Result := ProcessInt (Option, Rec^.Value); otString : Result := ProcessString (Option, Rec^.Param); otFilename : Result := ProcessFilename (Option, Rec^.Filename); else WriteLn ("Invalid option specification: ", Param[2]); end; end else begin WriteLn ("Invalid option character: ", Param[2]); end; end; end else begin WriteLn ("Error: options must start with - or /"); end; end; { ProcessCommandLine По заданному списку префиксов и типов параметров проверяет каждый аргумент командной строки и соответствующим образом присваивает значения информационным полям записей массива Options. Возвращает True, если все параметры были успешно обработаны и сохранены. } function ProcessCommandLine ( Options : pOptionsArray; nOptions : Integer ) : Boolean; var ParamNo : Integer; begin Result := True; for ParamNo := 1 to ParamCount do begin if (Not CheckParam (ParamStr (ParamNo), Options, nOptions)) then begin Result := False; Exit; end; end; end; end.

Перечисляемый тип OptionType описывает различные виды параметров, о которых известно функции ProcessCommandLine. Запись OptionRec содержит три поля: префикс, тип параметра и вариантную часть, в которой хранится значение данного параметра (если вы незнакомы с вариантными записями, просмотрите раздел справки с соответствующей информацией или купите простейший учебник по Паскалю в ближайшем книжном магазине).

Запись OptionRec оказывается не слишком эффективным решением, поскольку все записи независимо от типа параметра имеют максимальный размер из всех возможных вариантов. Размер типа ShortString  равен 256 байтам, поэтому большинство записей будет занимать гораздо больше места, чем действительно необходимо. Существует несколько способов решения этой проблемы, самый простой из них - использовать  указатели  на строки (вместо самих строк) для строковых и файловых типов. Я не реализовал эту возможность, поскольку она требует дополнительного кодирования.

Другая проблема тоже связана с типом ShortString. Самая длинная строка, которая может храниться в переменной типа ShortString, состоит из 255 символов, тогда как максимальная длина пути в Windows оказывается несколько длиннее (260 байт). Я рассчитывал воспользоваться типом Delphi AnsiString (то есть «длинной строкой»), но длинные строковые типы не могут входить в вариантную часть записи. И снова самым очевидным решением будет использование указателей.

Несмотря на эти проблемы, модуль CmdLine способен принести немало пользы. Дополнительные расходы памяти не особенно страшны, поскольку в большинстве программ используется совсем немного параметров, и нас уже не страшит дурацкое ограничение в 64 Кбайт на размер статических данных. (Помните, мы живем в обширном 32-разрядном мире!) С ограничением на длину имени дело обстоит посложнее, но лично у меня найдется не так уж много знакомых, которым захотелось бы вводить 256-символьный путь в командной строке (точнее, таких вообще не найдется).

Модуль CmdLine содержит две функции, которые могут вызываться внешними программами: GetOptionRec и ProcessCommandLine. Функция GetOptionRec возвращает указатель на запись с заданным префиксным символом. Если такой записи не существует, GetOptionRec возвращает Nil. Вся настоящая работа выполняется в функции ProcessCommandLine. Вы передаете ей массив структур OptionRec, а она анализирует командную строку и заполняет поля значений для каждого параметра. Если ProcessCommandLine удается без ошибок обработать все аргументы командной строки, она возвращает True. Если в какой-то момент произойдет ошибка, функция немедленно прекращает работу, выдает сообщение об ошибке и возвращает значение False.



Содержание раздела