В этой главе:
Инструменты: dub, dmd, rdmd; В коде: module, import, определение и вызов функции, получение параметров командной строки, приведение их к числовому типу.
Интересные особенности языка D:
Утилита rdmd, умная обертка над dmd, позволяет упростить компиляцию и запуск программ. Она может принимать все ключи dmd и, в дополнение - свои собственные. Рассмотрим случай, когда мы хотим написать совсем короткую программу в стиле Perl. Ключ rdmd --loop=... помещает код ... внутрь цикла foreach (line; stdin.byLine()) { ... }. Пример скрипта, построчно читающего STDIN:
echo -e "3.5\n4" | rdmd --loop='import std.conv; writefln("%s + %s = %s", line, 1, parse!real(line)+1 );'
3.5 + 1 = 4.5
4 + 1 = 5
rdmd позволяет выполнить код, указанный в --eval , например, если у вас есть модуль mymath, вы хотите запустить код, использующий функцию из модуля, но вы не хотите создавать файл с программой. Ключом -I вы можете указать, где искать используемый модуль:
rdmd -I=./source --eval='import mymath: add; auto r = add(3,4); writeln(r)'
В отличие от dmd утилита rdmd не требует перечисления всех исходных файлов в аргументах. Опции -I указывающей где искать файлы достаточно.
Удобную утилиту rdmd можно использовать не только для запуска "скриптов", но и для создания исполняемых файлов. Сделать это можно, указав ключ компилятору -of:
По аналогии с прошлым примером:
rdmd -I=./source -ofmyscript1 --eval='import mymath: add; auto r = add(3,4); writeln(r)'
или, если скрипт в файле:
rdmd -I=./source -ofmyscript1 myscript1.d
Простые программы можно писать в одиночных файлах с расширением *.d. Создайте файл app.d, пример, которого представлен ниже. Для создания программы, выглядящей как скрипт, укажите rdmd в "sha-bang" строке, которая должна быть первой в файле. Установите флаг выполнения для файла: chmod +x app.d
После этого программу можно запускать простым вызовом ./app.d. При первом вызове программа будет скомпилирована и скомпилированная версия будет помещена во временный файл. Последующие вызовы будут вызывать скомпилированный файл. Для запуска командойrdmd app.d первая строка и установка chod +x app.d не нужны.
#! /usr/bin/env rdmd
import std.stdio;
void main()
{
auto r = add(3,4);
writefln("Result: %s", r);
}
auto add(real a, real b){
return a+b;
}
Если вы хотите получить скомпилированный файл, то скомпилируйте программу cледующей командой:
dmd app.d
Строка #! /usr/bin/env rdmd в таком случае может быть удалена, но она не мешает. Для компиляции и последующего запуска выполните:
dmd -run app.d
Перекомпиляция будет вызываться при необходимости.
Если вы хотите, чтобы ваш исходный код в единственном файле обладал описанием, которое обычно содержится в проектах в файлах dub.sdl или dub.json, и к нему можно было применить удобную утилиту DUB (менеджер пакетов и инструмент сборки проектов), то можно в начало файла добавить "рецепт" по правилам SDL или JSON ( Подробнее - см. в документации по DUB https://code.dlang.org/package-format?lang=sdl или https://code.dlang.org/package-format?lang=json). Рецепт должен быть помещен в начало файла в многострочный документирующий комментарий /+ ... +/. Минимальный SDL-рецепт с последующим исходным кодом может выглядеть так:
/+
dub.sdl:
name "a7"
+/
import std.stdio;
void main()
{
auto r = add(3,4);
writefln("Result: %s", r);
}
auto add(real a, real b){
return a+b;
}
Аналогичный JSON - вариант рецепта выглядит так:
/+
dub.json:
{
"name": "a7"
}
+/
...
Если плохо помните синтаксис, то пример частично заполненного описания можно получить сгенерировав любой новый проект и взяв за образец содержимое файла dub.json (dub.sdl). Обратите внимание, что нужно добавить строку dub.json: в начале комментария. Она объявляет формат.
Для компиляции такого мини-проекта используйте команду:
dub --single a7.d
# вывод команды:
Performing "debug" build using dmd for x86_64.
a7 ~master: building configuration "application"...
Linking...
Running ./a7
Result: 7
А для запуска без создания исполняемого файла:
dub a7.d
# вывод:
Result: 7
В дальнейшем, как в настоящем проекте, при необходимости, в рецепте можно указать зависимости, которые будут автоматически скачаны при сборке.
В случае, когда нужно выполнять файл как скрипт, после установки признака выполнения chmod +x a7.d в строке "ша-банг" напишите #!/usr/bin/env dub
Если программа начинает увеличиваться, удобно создать структуру проекта. Пусть имя проекта будет a7 , тогда выполняем:
dub init a7
На все вопросы ответить Enter. При необходимости указать зависимости в диалоговом режиме или позже, отредактировав описание проекта в файле. Будет сгенерирована структура проекта: каталог a7 с вложенным подкаталогом source. В нем автоматически будет создан файл app.d. Его можно заменить готовой, написанной ранее программой, переименовав ее в app.d, если имя отличалось. Если до этого в файле существовал рецепт, то нужные данные из него нужно перенести в файл dub.sdl ( или dub.json ).
Как и прежде, весь код можно поместить в одном файле. Пример такого файла (пусть он имеет имя a7/source/app.d ) показан ниже. Программа немного усложнилась и выросла в размерах, что в будущем вызовет у нас желание вынести функции в отдельный модуль. Пока в программе определены вспомогательные функции sub и add. В ней же определена функция main. Программа ожидает два аргумента командной строки, жалуясь, если их нет. Потом она производит с ними нехитрые арифметические действия и печатает результат.
import std.stdio, std.conv, core.stdc.stdlib;
void main( string[] args ){
if ( args.length < 3 ){ stderr.writeln("Must be 2 parameters"); exit(1); } // для краткости в одну строку
long a = parse!long( args[1] ); // std.conv позволяет делать ...
long b = parse!long(args[2] ); // ... преобразование аргумента к нужному типу
auto r1 = sub( a, b);
auto r2 = add( a, b);
writefln("sub( %s, %s ) return %s", a, b, r1);
writefln("add( %s, %s ) return %s", a, b, r2);
}
auto sub(real a, real b){ // определение функции sub
return a-b;
}
auto add(real a, real b){ // определение функции add
return a+b;
}
Перейдя в корневой каталог проекта (~/a7) скомпилировать и запустить проект можно так:
dub
# вывод dub не показан
Чтобы передать параметры запускаемой программе, их, как принято, нужно указать после --:
dub -- 5 3 # указали два параметра командной строки
Не переходя в корневой каталог проекта это можно сделать, указав опцию --root, например, из папки source так:
~/a7/source $ dub --root=.. -- 5 3
# вывод команды:
Performing "debug" build using dmd for x86_64.
a7 ~master: building configuration "application"...
Linking...
Running ../a7 5 3
sub( 5, 3 ) return 2
add( 5, 3 ) return 8
Создадим рядом с ~/a7/source/app.d файл ~/a7/source/mymath.d . Перенесем в него определение функций add и sub.
// module mymath; // <== можно, но не обязательно написать название модуля
// ,если оно по замыслу совпадает с именем файла без пути и расширения
auto sub(real a, real b){
return a-b;
}
auto add(real a, real b){
return a+b;
}
В файле app.d после переноса определений функций в модуль добавляем импорт модуля mymath. Если ни в каких других импортированных модулях нет функций с такими именами и похожими сигнатурами, а это так и есть, то можно явно их не перечислять: компилятор найдет их сам в модуле mymath. Если есть сомнения, можно импортировать функции явно. Но опасений быть не должно: при любых неоднозначностях с импортом компилятор выдаст ошибку компиляции.
import std.stdio, std.conv, core.stdc.stdlib;
import mymath;
// import mymath: add,sub; // <== можно сделать поименный импорт функций
void main( string[] args ){
if ( args.length < 3 ){ stderr.writeln("Must be 2 parameters"); exit(1); }
long a = parse!long( args[1] );
long b = parse!long(args[2] );
auto r1 = sub( a, b); // <== вызов функций остался ...
auto r2 = add( a, b); // <== ... без изменений
writefln("sub( %s, %s ) return %s", a, b, r1);
writefln("add( %s, %s ) return %s", a, b, r2);
}
Скомпилировать проект, не запуская программу можно:
Если из корня проекта: dub build
Если не из корня проекта, то, указав --root:
~/a7/source $ dub --root=.. build
Performing "debug" build using dmd for x86_64.
a7 ~master: building configuration "application"...
Linking...
Либо через dmd, указав -of=объектный_файл :
~/a7/source $ dmd -of=../a7 app.d mymath.d
Как правило, когда вы работаете с проектом, удобнее использовать dub:
dub # из корня проекта
или
dub --root=.. # из каталога source
или
dub --root=.. -- 5 3 # из каталога source с указанием параметров программе
В любом случае всегда можно непосредственно воспользоваться dmd:
~/a7/source $ dmd app.d -run mymath.d 5 3
sub( 5, 3 ) return 2
add( 5, 3 ) return 8
Обратите внимание, все исходные файлы, участвующие в компиляции, должны быть перечислены.
Добавьте в mymath.d в конце или в начале файла блок unittest{...}, в котором используйте утверждения assert():
auto sub(real a, real b){ // определение функции sub
return a-b;
}
auto add(real a, real b){ // определение функции add
return a+b;
}
unittest{
assert( add(4,5)==9 );
assert( sub(1,2)==-1);
}
После добавления тестов скомпилируйте проект, добавив команду test :
dub test # добавить --root=.. , если вызывается из ./source
После запуска тестов и вывода результатов скомпилируется исполняемый файл a7-test-library, содержащий в себе эти тесты, которые будут запускаться до входа в main() при каждом запуске a7-test-library.
С помощью DMD юнит-тесты модуля, ( естественно, не имеющего функции main() ) включаются в исполняемый файл командой (запуск тестов производится отдельно или как в примере ниже в той же команде):
dmd -main -unittest -run mymath.d
# предполагается, что файл mymath.d находится в текущей директории
Ключ-main создает пустую заглушку для main(). Ключ -of задает имя исполняемого файла (он необязательный).
Простейший способ создать документацию - вставить документирующие комментарии в код и сгенерировать файл документации. Простейшим способом вставить документирующий комментарий - использовать три косые черты /// перед определением, которое нужно задокументировать. Добавление документирующего комментария перед юнит-тестом добавляет код теста в раздел Examples документации. Добавим комментарии перед определением функции add и перед блоком unittest в файле mymath.d :
module myutils.mymath;
///
auto add(real a, real b){
return a+b;
}
///
unittest{
assert(2.add(3)==5);
}
DMD-команда для генерации документации будет выглядеть так:
dmd -D mymath.d -main
Одновременно сгенерировать документацию и скомпилировать версию c юнит-тестами (и запустить тесты) можно так:
dmd -unittest -D mymath.d -main && ./mymath
DUB-команда выглядит так (из корня проекта):
dub build --build=docs
Будет создана папка docs, в которой будут лежать файлы документации для каждого исходного файла. Например, для файла mymath.d будет создан файл документации mymath.html
Если программа увеличилась настолько, что вы захотели иметь несколько модулей, сгруппировав их тематически по каталогам, то вы можете сделать это просто реорганизовав файлы модулей.
Создайте в папке source папку myutils и переместите в нее файл модуля mymath.d.
Запустите проект на компиляцию командой dub , как было указано выше.
После создания иерархии каталогов, вы захотеть увеличить вглубь систему именования модулей, чтобы она соответствовала иерархии каталогов. Для этого добавьте в начало модуля ~/a7/source/myutils/mymath.d объявление модуля:
module myutils.mymath;
... остальной код модуля ...
Импорт модуля в файле ~/a7/source/app.d нужно изменить
с
import mymath;
на
import myutils.mymath;
Проект можно компилировать.
Существует способ ( dub add-path ) указать пути поиска зависимостей, которые находятся не в публичном репозитории, а где-то в файловой системе. Указанные таким способом каталоги имеют преимущество перед репозиторием.
Список других возможностей утилит dub, dmd и rdmd можно получить распечатав описание ключей запуска из командной строки для каждой из этих комманд, вызвав их с ключом --help.