TL;DR: link do repozytorium na GitHub z kodem źródłowym i testami.
Poniżej podaję całkiem spory opis działania parsera. Jeśli komuś się nudzi, proszę o przejrzenie kodu na repo i podpowiedź, co mógłbym zrobić lepiej.
Mam nadzieję, że poniższy opis będzie łatwy do zrozumienia, bo nie umiem tłumaczyć, jak coś działa. Umieściłem trochę przykładów kodu, które powinny trochę pomóc.
Istnieje już trochę parserów argumentów linii komend, w tym używany przeze mnie dotychczas JSAP, ale miały sporo elementów kompletnie mi niepotrzebnych. Napisałem więc własny parser, chcąc możliwie jak najbardziej go zminimalizować. Jednocześnie chciałem zachować najważniejsze dla mnie elementy, czyli wartości i flagi.
Wartości
Klasa CopyFile
przyjmuje dwa argumenty - ścieżki do plików - i kopiuje plik z pierwszej ścieżki do drugiej ścieżki.
import st.jargs.*;
public class CopyFile {
public static void main(String[] args) {
Parser p = new Parser();
Variable sourcePath = new Variable();
Variable destPath = new Variable();
p.insertElements(filePath, destPath); //można umieszczać w środku dowolną ilość elementów
p.parse(args);
if (sourcePath.isUsed() && destPath.isUsed()) {
System.out.println("Source: " + sourcePath.getValue());
System.out.println("Destination: " + sourcePath.getValue());
/* operacja kopiowania tutaj */
}
}
}
Po uruchomieniu:
java CopyFile "katalogA/plik.txt" "katalogB/plik.bak"
W konsoli wyświetli się:
Source: katalogA/plik.txt
Destination: katalogB/plik.bak
Z czasem, wraz z chęcią maksymalnego uproszczenia inicjalizacji parsera i wszystkich elementów, zaczynałem umożliwiać coraz krótsze jego tworzenie. Parsowanie argumentów z efektem identycznym do powyższego można zrealizować też tak:
public static void main(String[] args) {
Variable sourcePath = new Variable();
Variable destPath = new Variable();
Parser p = Parser.createParser(sourcePath, destPath); //można umieszczać w środku dowolną ilość elementów
p.parse(args);
/*
Jeżeli obiekt nie będzie używany wielokrotnie, można to skrócić jeszcze bardziej:
Parser.createParser(sourcePath, destPath).parse(args);
*/
}
Lub w inny sposób, wprowadzając od razu tworzone obiekty klasy Variable
do parsera:
public static void main(String[] args) {
Parser p = new Parser();
Variable sourcePath = p.newVariable();
Variable destPath = p.newVariable();
p.parse(args);
}
**Flagi bez wartości** Rozszerzmy możliwości klasy `CopyFile` o funkcję usunięcia pliku źródłowego. Skorzystamy do tego z flagi `-d` / `--delete`. ```java import st.jargs.*;
public class CopyFile {
public static void main(String[] args) {
Parser p = new Parser();
Variable sourcePath = new Variable();
Variable destPath = new Variable();
Flag removeSource = new Flag('d', "delete", false); //"false" oznacza niżej brak wartości przypisanej do flagi.
p.insertElements(filePath, destPath, removeSource);
p.parse(args);
if (sourcePath.isUsed() && destPath.isUsed()) {
System.out.println("Source: " + sourcePath.getValue());
System.out.println("Destination: " + sourcePath.getValue());
System.out.println("Remove: " + removeSource.isUsed());
/* operacja kopiowania i usuwania tutaj */
}
}
}
Po uruchomieniu:
java CopyFile "katalogA/plik.txt" "katalogB/plik.bak"
W konsoli wyświetli się:
Source: katalogA/plik.txt
Destination: katalogB/plik.bak
Remove: false
Natomiast po użyciu przełącznika `-d`/`--delete` w dowolnym miejscu:
java CopyFile -d "katalogA/plik.txt" "katalogB/plik.bak"
java CopyFile "katalogA/plik.txt" -d "katalogB/plik.bak"
java CopyFile "katalogA/plik.txt" --delete "katalogB/plik.bak"
java CopyFile "katalogA/plik.txt" "katalogB/plik.bak" -d
Otrzymamy:
Source: katalogA/plik.txt
Destination: katalogB/plik.bak
Remove: true
Flagi możemy tworzyć łatwiej klasą `FlagBuilder`:
```java
Flag removeSource = FlagBuilder.createFlag()
.setShortFlag('d')
.setLongFlag("delete").build();
Parser.createParser(removeSource).parse(args);
Używając metody newFlag()
clasy Parser
możemy skorzystać z FlagBuilder
i jednocześnie wstawić flagę do parsera:
Parser p = new Parser();
p.newFlag().setShortFlag('d').setLongFlag("delete").build();
p.parse(args);
Pominięcie metod setShortFlag
i setLongFlag
powoduje, że odpowiednio krótka i długa flaga nie jest możliwa do użycia.
Używając kilku flag, możemy w Linuksowym stylu łączyć krótkie flagi ze sobą:
Flag delete = FlagBuilder.createFlag().setShortFlag('d').setLongFlag("delete").build();
Flag create = FlagBuilder.createFlag().setShortFlag('c').setLongFlag("create").build();
Aby użyć w linii komend obydwóch flag, możemy zapisywać je oddzielnie, np.:
-d -c
--delete --create
-c --delete
Lub połączyć krótkie flagi w jedną:
-cd
Flagi z wartościami
Flagi z wartościami można stworzyć, używając w konstruktorze true
jako ostatniego argumentu:
Flag repeatCount = new Flag('r', "repeat-count", true);
Lub w FlagBuilder
, dodając dodatkową metodę setVariableRequired(true)
:
Flag repeatCount = FlagBuilder.createFlag()
.setShortFlag('r')
.setLongFlag("repeat-count")
.setVariableRequired(true).build();
Wartość true
dla metody setVariableRequired
jest domyślna i może zostać pominięta. Identycznie działa to przy Parser.newFlag()
.
Flagi z wartościami różnią się od poprzedników tym, że bezpośrednio po fladze użytkownik powinien w linii komend wpisać przypisaną fladze wartość. Przykładowo:
public static void main(String[] args) {
Parser p = new Parser();
Variable name = p.newVariable();
Flag repeatCount = p.newFlag()
.setShortFlag('r')
.setLongFlag("repeat-count")
.setVariableRequired().build();
p.parse(args);
System.out.println("Name: " + name.getValue());
System.out.println("Repeat count: " + repeatCount.getValue());
}
Wówczas dla argumentów:
"jakaś nazwa" -r 5
lub
"jakaś nazwa" --repeat-count 5
Otrzymamy:
Name: jakaś nazwa
Repeat count: 5
Flagi z wartościami i bez wartości możemy bez problemu ze sobą łączyć.
public static void main(String[] args) {
Parser p = new Parser();
Flag repeatCount = p.newFlag()
.setShortFlag('r')
.setLongFlag("repeat-count")
.setVariableRequired().build();
p.parse(args);
Flag saveToFile = p.newFlag()
.setShortFlag('s')
.setLongFlag("save")
.setVariableRequired().build();
Flag logger = p.newFlag()
.setShortFlag('l')
.setLongFlag("logger").build();
p.parse(args);
System.out.println("Repeat count: " + repeatCount.getValue());
System.out.println("Save to file: " + saveToFile.getValue());
System.out.println("Use logger: " + logger.isUsed());
}
Wówczas dla wszystkich poniższych przykładów:
--repeat-count 5 --save "plik.txt" --logger
-lr 5 -s "plik.txt"
-s "plik.txt" -r 5 --logger
-lsr "plik.txt" 5
Otrzymamy identyczny efekt:
Repeat count: 5
Save to file: plik.txt
Use logger: true