Захват цветного вывода ANSI с помощью Ruby Open3/Process.spawn()

Я использую пакет sass-lint NPM для проверки стиля .scss файлов из задачи Rake, таким образом:

  sass_lint_cmd = "sass-lint --config #{ui_library_path}/scss/.sass-lint.yml '#{ui_library_path}/scss/*.scss' -v -q --max-warnings=0"
  output, status = Open3.capture2e(sass_lint_cmd)
  raise IOError, output unless status == 0

В основном это работает, поскольку в случае каких-либо предупреждений или ошибок линтера задача Rake прерывается, а вывод sass-lint, включая ошибки, выводится на консоль.

Однако при прямом запуске sass-lint выдает красивый цветной вывод. При захвате capture2e цвета теряются.

Я предполагаю, что проблема в том, что sass-lint (или Node) обнаруживает, что он не работает в TTY, и поэтому выводит обычный текст. Есть ли какая-то опция Process.spawn(), которую я могу передать Open3.capture2e(), или какой-то другой метод, с помощью которого я могу заставить его думать, что он работает в TTY?

(Примечание: я просмотрел Обмануть приложение, заставив его думать, что его стандартный вывод является терминалом, а не каналом, но BSD-версия script, которая поставляется с macOS, похоже, не поддерживает ни --return, ни -c параметры, и я работаю на макОС.)


Обновление: я попробовал script -q /dev/null и PTY.spawn() в соответствии с ответом Piccolo, но безуспешно.

script -q /dev/null … работает из командной строки, но не работает в Open3.capture2e() (он запускается, но выдает монохромный вывод и ложную трассировку стека Bundler::GemNotFound).

Что касается PTY.spawn(), заменив приведенный выше код следующим:

r, _w, pid = PTY.spawn(scss_lint_command)
_, proc_status = Process.wait2(pid)
output, status = [r, proc_status.exitstatus]
(warn(output); raise) unless status == 0

подпроцесс никогда не кажется завершенным; если я ps в другом терминале, он отображается как прерываемый спящий режим. Уничтожение подпроцесса не освобождает родительский процесс.

То же самое происходит с блочной формой.

output, status = nil
PTY.spawn(scss_lint_command) do |r, _w, pid|
  _, proc_status = Process.wait2(pid)
  output, status = [r, proc_status.exitstatus]
end
(warn(output); raise) unless status == 0

person David Moles    schedule 25.07.2018    source источник
comment
Работает ли script -q ... из командной строки, если вы передаете ее в cat?   -  person Piccolo    schedule 26.07.2018


Ответы (1)


Рассматривали ли вы возможность использования превосходной библиотеки Ruby pty? вместо Open3?

Псевдотерминалы для потока, на который вы ссылаетесь, похоже, эмулируют реальный TTY, поэтому скрипт не узнает, что это не так. t в терминале, если он не проверяет такие вещи, как $TERM, но это также можно относительно легко подделать.

Согласно этой блок-схеме, недостатком использования pty вместо Open3 является то, что STDERR не получает свой собственный поток.


В качестве альтернативы, согласно этому ответу, а также из связанного с вами потока, script -q /dev/null $COMMAND, похоже, помогает в Mac OS X.

На компьютерах Mac ls -G раскрашивает вывод ls, и в качестве краткого теста я передал ls -G в cat следующим образом:

script -q /dev/null ls -G | cat

и он отображается с цветами, тогда как просто работает

ls -G | cat

не.

Этот метод также работал в irb, опять же с использованием ls -G:

$ touch regular_file
$ touch executable_file
$ mkdir directory
$ chmod +x executable_file
$ irb
2.4.1 :001 > require 'Open3'
 => true
2.4.1 :002 > output, status = Open3.capture2e("ls -G")
 => ["directory\nexecutable_file\nregular_file\n", #<Process::Status: pid 39299 exit 0>]
2.4.1 :003 > output, status = Open3.capture2e("script -q /dev/null ls -G")
 => ["^D\b\b\e[1m\e[36mdirectory\e[39;49m\e[0m       \e[31mexecutable_file\e[39;49m\e[0m regular_file\r\n", #<Process::Status: pid 39301 exit 0>]
2.4.1 :004 >
person Piccolo    schedule 25.07.2018
comment
Пробовал оба, не повезло. :( См. обновление. Но я, вероятно, неправильно называю PTY.spawn() / Process.wait2(). - person David Moles; 26.07.2018
comment
@DavidMoles Хм, не уверен, что script -q /dev/null ... не работает, но позвольте мне посмотреть, смогу ли я понять, что не так с вашим PTY.spawn(). - person Piccolo; 26.07.2018
comment
@DavidMoles Я попытался запустить следующее: lines = []; PTY.spawn("ls -G && sleep 5") {|reader, writer, pid| reader.each {|line| lines << line }; Process.wait2(pid) } и Process.wait2(pid) заблокированы на нужное время, и результат был правильным. Я действительно не вижу никакой разницы между тем, что вы сделали, и тем; Вы можете попробовать запустить мой в своей системе, чтобы мы могли увидеть, есть ли разница между ls -G и вашей командой, или это системная проблема? - person Piccolo; 26.07.2018