В первой части мы рассмотрели работу базового алгоритма xd и даже сделали большую часть ее реализации, но не сделали обработку аргументов утилиты и некоторый другой функционал. В этой части мы восполним пробелы и завершим создание утилиты, добавив ряд функций и некоторых улучшений.
Напомним, в предыдущей статье мы сделали практически все, что нужно: в частности, мы создали универсальную функцию, которая может работать с любым диапазоном данных, а также сформировали структуры данных, которые обслуживают функцию. Но, то что было создано, удобно лишь в библиотечном использовании, а не в пользовательском применении. Этот момент можно исправить в несколько этапов. Первым этапом можно назвать создание специальной функции-помощника, которая будет принимать не подготовленные аргументы в виде структур данных, а строковые аргументы, такие как если бы они поступили от пользователя.
Функцию-помощника можно описать следующим образом:
auto xd(Range)(Range range, string xfmt, string ofmt, bool repeated = false, bool reversed = false)
{
XDFORMAT chunkFormat;
// Default values from xd man
chunkFormat.width = WIDTH.DWORD;
chunkFormat.style = STYLE.HEXADECIMAL;
chunkFormat.order = ORDER.LITTLE;
// Default address style
STYLE offsetFormat = STYLE.DECIMAL;
if (xfmt.length >= 1)
{
auto firstChar = xfmt[0];
switch (firstChar)
{
case '1', 'b':
chunkFormat.width = WIDTH.BYTE;
break;
case '2', 'w':
chunkFormat.width = WIDTH.WORD;
break;
case '4', 'l':
chunkFormat.width = WIDTH.DWORD;
break;
case '8', 'v':
chunkFormat.width = WIDTH.QWORD;
break;
case 'c':
chunkFormat.width = WIDTH.BYTE;
chunkFormat.isASCII = true;
break;
default:
break;
}
if (xfmt.length >= 2)
{
char secondChar = xfmt[1];
if ((firstChar == 'b') || (firstChar == 'w') || (firstChar == 'l') || (firstChar == 'v'))
{
if (xfmt.length >= 3)
{
secondChar = xfmt[2];
}
}
switch (secondChar)
{
case 'o':
chunkFormat.style = STYLE.OCTAL;
break;
case 'd':
chunkFormat.style = STYLE.DECIMAL;
break;
case 'x':
chunkFormat.style = STYLE.HEXADECIMAL;
break;
default:
break;
}
}
}
if (ofmt.length >= 1)
{
switch (ofmt[0])
{
case 'o':
offsetFormat = STYLE.OCTAL;
break;
case 'd':
offsetFormat = STYLE.DECIMAL;
break;
case 'x':
offsetFormat = STYLE.HEXADECIMAL;
break;
default:
break;
}
}
if ((chunkFormat.width == WIDTH.DWORD) && (reversed))
{
chunkFormat.order = ORDER.BIG;
}
writeln(chunkFormat);
xd(range, chunkFormat, offsetFormat, repeated);
}Данная функция работает аналогично той, что была в первой части статьи, только в качестве аргументов для формата вывода кусков и адреса используются обычные строковые аргументы. Также для предоставления некоторых возможностей — в частности, для вывода повторений кусков с помощью символа «*», используются булевы флаги.
В принципе, в самой функции-помощнике уже сделаны необходимые проверки, а также уже сформирован интерфейс, который можно использовать для того, чтобы организовать взаимодействие с пользователем, однако, все равно нужно делать захват переданных из командной строки аргументов. Получение данных из командной строки мы решили сделать в соответствии с духом минимализма, не используя сторонних библиотек и даже не привлекая стандартной. Сделана обработка способом типичным для программ на C/C++: в функцию main обычно передается строковый массив аргументов и был сделан ручной разбор этого строкового массива, так как этот массив имеет весьма специфическую структуру.
Таким образом, для завершения построения xd требуется только написать функцию main, которая выглядит так:
void main(string[] args)
{
if (args.length < 2)
{
writeln("xd [-u] [-r] [-s] [-a{odx}] [-c|{b1w2l4v8}{odx}] ... file ...\n");
}
else
{
string xfmt, afmt;
bool repeated, reversed;
string[] files;
foreach (arg; args[1..$])
{
if (arg.length >= 2)
{
if (arg[0] == '-')
{
switch (arg)
{
case "-u":
break;
case "-r":
repeated = true;
break;
case "-s":
reversed = true;
break;
case "-c":
xfmt = "c";
break;
default:
if (arg.length == 3)
{
if (arg[1] == 'a')
{
afmt = to!string(arg[2]);
}
else
{
xfmt = arg[1..$];
}
}
break;
}
}
else
{
files ~= arg;
}
}
}
import std.file : exists, read;
foreach (f; files)
{
if (f.exists)
{
auto buf = cast(ubyte[]) read(f);
xd(buf, xfmt, afmt, repeated, reversed);
}
}
}
}Работает это все достаточно просто: если количество строк в массиве аргументов командной строки меньше 2 (что означает, что не были переданы минимально необходимые параметры — формат и файл для обработки), то выдается справка по использованию утилиты. Если же аргументов больше 2, то у массива аргументов убирается первый элемент (т.к. самый первый аргумент — это как правило имя программы) и дальше идет поэлементная обработка. Сама обработка проверяет начинается ли элемент с дефиса (что означает, что нам попалась опция командной строки) и если да, то проверяется полученный аргумент на соответствие списку опций xd. Если опция была найдена и соответствует одной из опций, то она кладется в соответствующую переменную, что используется далее функцией-помощником. Также, нетрудно заметить, что отдельно обрабатывается случай, когда есть опция для формата адреса (-a) и для списка файлов (это если не подошла ни одна опций, но аргументы еще остались).
Таким образом, получается полностью функциональная копия xd из Plan 9, но насколько нам удалось понять с меньшим количеством кода: оригинальная версия xd из Plan 9 на C имеет 463 строки, а наша версия — 364 строки! Конечно, ничего особенного это не означает, кроме того, что версия на D чуть проще для понимания, но она может требовать некоторых доработок. Также, нами не были произведены какие-либо бенчмарки обоих версий, так как мы не ставили перед собой целей именно побить в чем-либо оригинальную версию. Стоит заметить, что несмотря на отсутствие проверок производительности, наша версия xd вполне готова к использованию.