Внешние процессы

auto-rerun

В этой главе: getopt, process, stderr, exit, sleep, colorize, environment, file.SysTime, array, associative array

Код программы (описание см. ниже)

void main(string[] args)
{
  import std.stdio, std.getopt, std.process, std.c.stdlib; // несколько импортов одной строкой
  import core.thread, std.file, std.range.primitives; 
  import colorize : fg, color, cwritef, cwritefln; // импорт конкретных имен из модуля
  import std.datetime;

  // объявление нескольких переменных:
  auto deb=false, interval=1, icolr="cyan", wcolr="yellow", okcolr="green"; 
  auto opts = getopt( args, // анализ опций из args 
    "i|interval", "interval in seconds between checking", &interval, // i|interval - значит или -i или --interval
    "d|deb|debug", "show debug messages to stderr", &deb, 
    "icolor" , "info color", &icolr,
    "wcolor" , "warnings color", &wcolr,
    "okcolor", "ok color", &okcolr,
    ); // ссылается на значение bool, поэтому работает как флаг

  // если пользователь использовал -h|--help:
  // после if может не быть фигурных скобок: 
  if( opts.helpWanted ) defaultGetoptPrinter( "Usage: ", opts.options ), exit(0);

  if ( args.length < 3 ){
      stderr.writeln("Too few parameters"); // синтаксический сахар для: writeln( stderr, "Too few parameters");
      exit(1); // выйти с плохим кодом возврата
  }

  // последний аргумент командной строки - команда, остальное - отслеживаемые файлы:
  auto cmd = args[$-1], files = args[1..$-1]; 
  auto path = environment.get("PATH", "not present"); // пример доступа к переменным окружения

  // Ниже использован цветной вывод на stdout с использованием стороннего модуля colorize
  // Кроме того что colorize импортирован, он указан как зависимость в dub.json такой записью: "colorize": "~>1.0.5" 
  // cwritef из colorize - аналог writef из std.stdio. Правила форматированного вывода те же. 
  deb && cwritef("watch files: %s;\ncmd: \"%s\";\ninterval: %d sec;\nPATH: %s;\n".color(icolr), files, cmd, interval, path);

  auto cnt = 0;
  SysTime[string] file_times;  // создать х-м файл=>время. Тип SysTime подсмотрен в доке по std.file.

  while(true){
    string[] changed; // массив строк

    foreach (f; files){
      auto lm = timeLastModified(f); // функция из std.file
      if ( get( file_times, f, SysTime() ) != lm ){
        changed ~= f; // добавление в массив
        file_times[f] = lm; // присвоение элементу массива
      }    
    }

    if ( !empty(changed) ){ // проверка массива на "не пусто"
      cnt++;
      cwritef("\n %d => ".color(icolr) , cnt);
      deb && cwritef("%s changed\n\"%s\"".color(icolr), changed, cmd);
      auto pid = spawnShell( cmd); // асинхронный запуск shell-процесса 
      auto status = wait(pid); // код возврата
      auto c = okcolr;
      if (status>0) c = wcolr; // одно выражение в блоке может быть без фигурных скобок
      cwritefln( "status: %d\n".color(c), status );
    }
    Thread.sleep( dur!("seconds")( interval ) ); // спать секунду
  }
}

Смысл программы

Когда вы пишете какую-то программу, очень удобно, чтобы происходила автоматическая компиляция/проверка программы после каждого сохранения. Это позволяет сократить количество ручной работы и повышает продуктивность. Этим и будет заниматься программа auto-rerun.

Что делает программа

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

Как только рабочая версия нижеприведенной программы скомпилируется и нормально заработает, ее можно использовать для отслеживания изменения собственных исходников (если продолжать над ней работать). То есть, ее вызов из корня проекта будет таким: ./auto-rerun -d ./source/app.d "dub build"

Создание проекта и указание зависимостей

Создадим проект командой dub init auto-rerun

В интерактивном режиме на все вопросы ответим Enter, за исключением поля dependencyes, куда соответствующем вопросе введем colorize

После создания проекта, перейдем к редактированию файла ./source/app.d и напишем нижеприведенную программу. В другом окне в корне каталога auto-rerun периодически запускаем dub build , чтобы видеть допущенные ошибки, обнаруженные во время компиляции. При первом запуске, будет скачана указанная зависимость.

Лирическое отступление

Написать такую программу можно лишь заглядывая в документацию по библиотекам D и гугля некоторые типичные вопросы. То есть, какой-то, прям, учебник, который я все время до этого пытался найти, не нужен.

Вышеприведенная программа написана по мотивам нижеописанной программы на Perl. За небольшими отличиями, она несет почти тот же смысл и логику работы. Можно убедиться, что, если убрать все комментарии из D-версии, перловая версия не намного короче и читается так же несложно.

#!/usr/bin/env perl
use strict;
use Data::Dumper;
use Term::ANSIColor;
use Getopt::Long;
my ($help, $verb);
my ($badsuffix, $goodsuffix) = ("","");

GetOptions(
 "help" => \$help,
 "verbose" => \$verb,
 "badsuffix=s" => \$badsuffix,
 "goodsuffix=s" => \$goodsuffix,
) or die "Bad opt!";

print "Usage auto-rebuild file1 [file2 ...] 'on-change-command'" and exit if $help;

my $cmd = pop @ARGV or die "Cmd!";
print STDERR "Command: '$cmd', watch files: @ARGV\n" if $verb;
my %files;
my $cnt = 0;
$|=1;
while (1){
    my @changed;
    for my $f ( @ARGV ){
        my @stat = stat($f);
        my $ts = $stat[9];
        if ($files{$f}{ts} != $ts){
            push @changed, $f;
        }
        $files{$f}{ts}=$ts;
    }
    if (@changed){
        $cnt++;
        print STDERR "\n$cnt). Changed: @changed\n";
        print STDERR `$cmd`;
        print STDERR $? ? color('red bold') : color('green bold');
        print STDERR $? ? "<== $cnt - $? $badsuffix" : "<== $cnt - ok $goodsuffix";
        print STDERR color("reset");
    }
    sleep 2;
}

results for ""

    No results matching ""