От скрипта до проекта

От скрипта до проекта

В этой главе:

Инструменты: dub, dmd, rdmd; В коде: module, import, определение и вызов функции, получение параметров командной строки, приведение их к числовому типу.

Интересные особенности языка D:

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

Скрипты на языке D: rdmd

Утилита 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 указывающей где искать файлы достаточно.

Компиляция c rdmd

Удобную утилиту rdmd можно использовать не только для запуска "скриптов", но и для создания исполняемых файлов. Сделать это можно, указав ключ компилятору -of:

По аналогии с прошлым примером:

rdmd -I=./source -ofmyscript1 --eval='import mymath: add; auto r = add(3,4); writeln(r)'

или, если скрипт в файле:

rdmd -I=./source -ofmyscript1 myscript1.d

См. документацию по rdmd: https://dlang.org/rdmd.html

Одиночная программа без генерации проекта

Простые программы можно писать в одиночных файлах с расширением *.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;
}

Компиляция и запуск проекта (код в app.d).

Перейдя в корневой каталог проекта (~/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

Выносим функции в модуль (файл mymath.d)

Создадим рядом с ~/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);
}

Только компиляция (файлы app.d и mymath.d).

Скомпилировать проект, не запуская программу можно:

Если из корня проекта: 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 # из корня проекта

или

dub --root=.. # из каталога source

или

dub --root=.. -- 5 3 # из каталога source с указанием параметров программе

dmd

В любом случае всегда можно непосредственно воспользоваться 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);
}
DUB

После добавления тестов скомпилируйте проект, добавив команду test :

dub test # добавить --root=.. , если вызывается из ./source

После запуска тестов и вывода результатов скомпилируется исполняемый файл a7-test-library, содержащий в себе эти тесты, которые будут запускаться до входа в main() при каждом запуске a7-test-library.

DMD

С помощью 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-команда для генерации документации будет выглядеть так:

dmd -D mymath.d -main

Одновременно сгенерировать документацию и скомпилировать версию c юнит-тестами (и запустить тесты) можно так:

dmd -unittest -D mymath.d -main && ./mymath
DUB

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.

results for ""

    No results matching ""