Nette Command-Line

Lekka biblioteka do budowania aplikacji wiersza poleceń w PHP. Parsuje przełączniki, opcje i argumenty pozycyjne oraz pomaga tworzyć kolorowe wyjście terminala ze wsparciem ANSI.

Instalacja:

composer require nette/command-line

Wymaga PHP w wersji 8.2 i wspiera PHP do 8.5.

Parsowanie argumentów wiersza poleceń

Każdy skrypt CLI musi obsłużyć argumenty w rodzaju --verbose, -o output.txt albo zwykłe nazwy plików. Klasa Nette\CommandLine\Parser daje najszybszy sposób na start: wystarczy napisać tekst pomocy i pozwolić parserowi wyciągnąć z niego definicje opcji:

use Nette\CommandLine\Parser;

$parser = new Parser;
$parser->addFromHelp('
	-h, --help              Show this help
	-v, --verbose           Enable verbose mode
	-o, --output <file>     Output file
	-f, --format [type]     Output format (default: json)
	-I, --include <path>... Include paths
	--dry-run               Show what would be done
');

$args = $parser->parse();

I to wszystko. Parser rozumie, że --verbose to przełącznik, --output wymaga wartości, a --format ma wartość opcjonalną z json jako wartością zapasową. Twój tekst pomocy pozostaje zsynchronizowany z faktycznymi definicjami opcji.

Metoda parse() zwraca tablicę asocjacyjną. Klucze odpowiadają dokładnie nazwom opcji tak, jak zostały zdefiniowane, wraz z myślnikami:

[
	'--help' => true,         // albo null, jeśli nieużyte
	'--verbose' => null,
	'--output' => 'file.txt', // albo null, jeśli nieużyte
	'--format' => 'json',     // wartość zapasowa z (default: json)
	'--include' => ['src', 'lib'],
	'--dry-run' => null,
]

Domyślnie parse() czyta z $_SERVER['argv']. Możesz przekazać własną tablicę, co przydaje się przy testowaniu:

$args = $parser->parse(['--verbose', '-o', 'out.txt']);

Składnia tekstu pomocy

Parser wyciąga definicje opcji ze sformatowanego tekstu pomocy według tych reguł:

--verbose Przełącznik (bez wartości)
-v, --verbose Przełącznik z krótkim aliasem
--output <file> Opcja z wymaganą wartością
--format [type] Opcja z wartością opcjonalną
(default: json) Ustawia wartość zapasową
<path>... Opcja powtarzalna

Każda linia definiuje jedną opcję. Nazwy opcji muszą być oddzielone od swoich opisów co najmniej dwiema spacjami.

Dodatkowa konfiguracja

Niektórych ustawień nie da się wyrazić w tekście pomocy. Przekaż jako drugi parametr tablicę kluczowaną nazwą opcji:

$parser->addFromHelp('
	-c, --config <file>   Configuration file
	-I, --include <path>  Include path
	-n, --count <num>     Number of iterations
', [
	'--config' => [
		Parser::RealPath => true,
	],
	'--include' => [
		Parser::Repeatable => true,
	],
	'--count' => [
		Parser::Normalizer => fn($v) => (int) $v,
	],
]);

Dostępne klucze:

Parser::Repeatable Zbiera wiele wartości do tablicy
Parser::RealPath Weryfikuje, że plik istnieje, i rozwiązuje go do ścieżki absolutnej
Parser::Normalizer Funkcja przekształcająca fn($value) => ...
Parser::Default Wartość zapasowa (to samo co (default: x) w tekście pomocy)
Parser::Enum Tablica dozwolonych wartości

Interfejs płynny

Gdy potrzebujesz większej kontroli nad definicjami opcji, użyj interfejsu płynnego z metodami addSwitch(), addOption() i addArgument(). To podejście daje Ci dostęp do wszystkich funkcji, wraz z normalizatorami, enumami i precyzyjną kontrolą nad każdym parametrem:

use Nette\CommandLine\Parser;

$parser = new Parser;
$parser
	->addSwitch('--verbose', '-v')
	->addOption('--output', '-o')
	->addArgument('file');

$args = $parser->parse();

Podobnie jak przy addFromHelp() możesz przekazać do parse() własną tablicę na potrzeby testowania:

$args = $parser->parse(['--verbose', '-o', 'out.txt', 'input.txt']);

Przełączniki, opcje i argumenty

Są trzy typy wejść wiersza poleceń:

Przełączniki to flagi bez wartości, jak --verbose czy -v. Parsują się jako true, gdy są obecne, i null, gdy ich nie ma:

$parser->addSwitch('--verbose', '-v');
// --verbose  → true
// -v         → true
// (nieużyte) → null

Opcje przyjmują wartości, jak --output file.txt. Wartość można oddzielić spacją albo znakiem =:

$parser->addOption('--output', '-o');
// --output file.txt    → 'file.txt'
// --output=file.txt    → 'file.txt'
// -o file.txt          → 'file.txt'
// --output             → rzuca wyjątek (wartość wymagana)
// (nieużyte)           → null

Zwróć uwagę, że sama opcja jest zawsze opcjonalna: jej nieużycie zwraca null. Gdy jednak zostanie użyta, wartość jest domyślnie wymagana. Ustaw optionalValue: true, żeby dopuścić opcję bez wartości (parsuje się wtedy jako true):

$parser->addOption('--format', '-f', optionalValue: true);
// --format json        → 'json'
// --format             → true
// (nieużyte)           → null

Gdy ta sama opcja użyta jest wielokrotnie bez repeatable: true, wygrywa ostatnia wartość:

$parser->addOption('--output', '-o');
// -o first.txt -o second.txt  → 'second.txt'

Argumenty to wartości pozycyjne bez myślników. Domyślnie są wymagane. Ustaw optional: true, żeby uczynić je opcjonalnymi:

$parser->addArgument('input');
// script.php file.txt  → 'file.txt'
// (nieużyte)           → rzuca wyjątek

$parser->addArgument('output', optional: true);
// (nieużyte)           → null

$parser->addArgument('output', optional: true, fallback: 'out.txt');
// (nieużyte)           → 'out.txt'

Za pomocą fallback podajesz wartość używaną, gdy opcjonalna opcja albo argument nie zostaną podane. Przy opcjach z optionalValue: true zwróć uwagę, że użycie opcji bez wartości nadal parsuje się jako true, a wartość zapasowa używana jest tylko wtedy, gdy opcji w ogóle nie ma:

$parser->addOption('--format', '-f', optionalValue: true, fallback: 'json');
// --format xml  → 'xml'
// --format      → true (opcja użyta bez wartości)
// (nieużyte)    → 'json' (wartość zapasowa)

Argumenty mogą pojawić się w wierszu poleceń w dowolnym miejscu, nie muszą występować po opcjach:

// wszystkie te warianty są równoważne:
// script.php --verbose input.txt
// script.php input.txt --verbose

Ograniczanie wartości enumem

Ogranicz przyjmowane wartości do konkretnego zbioru:

$parser->addOption('--format', '-f', enum: ['json', 'xml', 'csv']);
// --format yaml  → rzuca "Value of option --format must be json, or xml, or csv."

Opcje powtarzalne

Ustaw repeatable: true, żeby zbierać wiele wartości do tablicy:

$parser->addOption('--include', '-I', repeatable: true);
// -I src -I lib  → ['src', 'lib']
// (nieużyte)     → []

$parser->addArgument('files', optional: true, repeatable: true);
// a.txt b.txt    → ['a.txt', 'b.txt']

Przekształcanie wartości

Użyj normalizer, żeby przekształcić sparsowaną wartość:

$parser->addOption('--count', normalizer: fn($v) => (int) $v);
// --count 42  → 42 (liczba całkowita)

Do walidacji ścieżek plików użyj wbudowanego normalizeRealPath:

$parser->addOption('--config', normalizer: Parser::normalizeRealPath(...));
// --config app.ini     → '/full/path/to/app.ini'
// --config missing.ini → rzuca "File path 'missing.ini' not found."

Łączenie obu podejść

Możesz połączyć addFromHelp() z metodami płynnymi, gdy potrzebujesz normalizatorów tylko dla niektórych opcji:

$parser
	->addFromHelp('
		-v, --verbose  Enable verbose mode
		-q, --quiet    Suppress output
	')
	->addOption('--config', '-c', normalizer: Parser::normalizeRealPath(...))
	->addArgument('input');

Obsługa błędów

Parser rzuca \Exception przy nieprawidłowym wejściu:

use Nette\CommandLine\Parser;

$parser = new Parser;
$parser
	->addOption('--output', '-o')
	->addArgument('file');

try {
	$args = $parser->parse();
} catch (\Exception $e) {
	fwrite(STDERR, "Error: {$e->getMessage()}\n");
	exit(1);
}

Typowe komunikaty o błędach:

Option --output requires argument. Opcja użyta bez wymaganej wartości
Unknown option --foo. Nierozpoznana opcja
Missing required argument <file>. Nie podano wymaganego argumentu
Unexpected parameter foo. Nadmiarowy argument pozycyjny
Value of option --format must be json, or xml. Wartość spoza enuma

Użyj isEmpty(), żeby sprawdzić, czy w ogóle nie podano żadnych argumentów wiersza poleceń (czyli użytkownik uruchomił sam script.php bez niczego dalej):

if ($parser->isEmpty()) {
	$parser->help();
	exit;
}

Obsługa –help i –version

Gdy Twój skrypt ma wymagane argumenty, uruchomienie script.php --help normalnie by zawiodło, bo brakuje wymaganego argumentu. Użyj parseOnly(), żeby najpierw sprawdzić opcje informacyjne:

$parser = new Parser;
$parser
	->addSwitch('--help', '-h')
	->addSwitch('--version', '-V')
	->addArgument('input');  // wymagany

// Najpierw sprawdzamy opcje informacyjne (bez walidacji, bez wyjątków)
$info = $parser->parseOnly(['--help', '--version']);

if ($info['--help']) {
	$parser->help();
	exit;
}

if ($info['--version']) {
	echo "1.0.0\n";
	exit;
}

// Teraz przeprowadzamy pełne parsowanie z walidacją
$args = $parser->parse();

Metoda parseOnly():

  • parsuje tylko podane opcje, ignorując całą resztę,
  • respektuje aliasy (-h--help),
  • nigdy nie rzuca wyjątków,
  • zwraca null dla opcji, które nie zostały użyte.

Kolorowe wyjście

Klasa Nette\CommandLine\Console opakowuje tekst w kody kolorów ANSI, żeby Twoje wyjście wyróżniało się w terminalu:

use Nette\CommandLine\Console;

$console = new Console;
echo $console->color('red', 'Error!') . "\n";
echo $console->color('white/blue', 'White text on blue background') . "\n";

Kolor podaje się jako 'pierwszy plan' albo 'pierwszy plan/tło'. Dostępne kolory to: black, gray, silver, white, navy, blue, green, lime, teal, aqua, maroon, red, purple, fuchsia, olive i yellow.

Kolory włączane są automatycznie tylko wtedy, gdy wyjście je wspiera. Metoda color() zwraca przy wyłączonych kolorach zwykły ciąg, więc jej wywołanie jest zawsze bezpieczne. Zachowanie możesz wymusić ręcznie:

$console->useColors(false); // wyłącza kolory
$console->useColors(true);  // wymusza włączenie kolorów

Wykrywanie terminala

Dwie metody statyczne pomagają Ci zdecydować, czy używać funkcji dostępnych tylko w terminalu. detectColors() zwraca false, gdy ustawiona jest zmienna środowiskowa NO_COLOR albo gdy wyjście nie jest terminalem CLI; zmienna FORCE_COLOR nadpisuje sprawdzenie terminala:

if (Console::detectColors()) {
	// terminal wspiera kolory ANSI
}

detectTerminal() mówi Ci, czy wyjście jest interaktywnym terminalem (TTY). Przydaje się to do automatycznego wyłączania funkcji, które mają sens tylko w terminalu, jak wskaźniki postępu, wyjście przepisujące linie czy interaktywne pytania:

if (Console::detectTerminal()) {
	// wyjście trafia do interaktywnego terminala, a nie do pliku czy potoku
}

Kompletny przykład

Oto rzeczywisty skrypt konwertujący pliki, łączący Parser i Console:

#!/usr/bin/env php
<?php
use Nette\CommandLine\Parser;

require __DIR__ . '/vendor/autoload.php';

$parser = new Parser;
$parser
	->addFromHelp('
		-h, --help           Show this help
		-v, --verbose        Show detailed output
		-n, --dry-run        Show what would be done
		-f, --format [type]  Output format (default: json)
		-o, --output <file>  Output file
	', [
		'--format' => [
			Parser::Enum => ['json', 'xml', 'csv'],
		],
	])
	->addArgument('input', normalizer: Parser::normalizeRealPath(...));

// Obsługujemy --help przed walidacją (unikamy błędu "missing argument")
if ($parser->isEmpty() || $parser->parseOnly(['--help'])['--help']) {
	echo "Usage: convert [options] <input>\n\n";
	$parser->help();
	exit;
}

try {
	$args = $parser->parse();
} catch (\Exception $e) {
	fwrite(STDERR, "Error: {$e->getMessage()}\n");
	exit(1);
}

if ($args['--verbose']) {
	echo "Converting {$args['input']} to {$args['--format']}...\n";
}

if ($args['--dry-run']) {
	echo "Dry run: no changes made.\n";
	exit;
}

// ... tutaj logika konwersji ...

echo "Done!\n";

Skrypt przyjmuje polecenia takie jak:

  • convert input.txt – konwersja z wartościami domyślnymi
  • convert -v --format xml input.txt – tryb verbose, format XML
  • convert -o result.txt input.txt – podanie pliku wyjściowego
  • convert --help – wyświetlenie pomocy (działa nawet bez pliku wejściowego)
wersja: 1.x