# clap — krok za krokom Každá kapitola: prečítaj, pozri príklady, urob úlohy. Až potom choď ďalej. `Cargo.toml`: ```toml [dependencies] clap = { version = "4", features = ["derive"] } ``` ```rust use clap::Parser; ``` --- ## Kapitola 1: Čo je clap clap je knižnica na spracovanie **argumentov príkazového riadku** (CLI argumentov). Keď spustíš program z terminálu: ```bash ./moj_program --subor data.json --pocet 5 --verbose ``` clap ti tie argumenty automaticky sparsuje do Rust štruktúry. ```rust use clap::Parser; #[derive(Parser)] struct Argumenty { #[arg(short, long)] subor: String, #[arg(short, long)] pocet: u32, #[arg(short, long)] verbose: bool, } fn main() { let args = Argumenty::parse(); println!("Súbor: {}", args.subor); println!("Počet: {}", args.pocet); println!("Verbose: {}", args.verbose); } ``` Spustenie: ```bash ./moj_program --subor data.json --pocet 5 --verbose # Súbor: data.json # Počet: 5 # Verbose: true ./moj_program -s data.json -p 5 -v # To isté — short varianty ./moj_program --help # Automaticky vygenerovaná nápoveda! ``` **Čo clap robí za teba:** - Parsuje argumenty z príkazového riadku - Validuje typy (ak --pocet nie je číslo → chybová hláška) - Generuje `--help` automaticky - Generuje chybové hlášky ak niečo chýba - Podporuje short (`-s`) aj long (`--subor`) verzie ### Úlohy 1 **1a.** Čo je CLI argument? Uveď 3 príklady programov, ktoré berú argumenty (napr. `ls -la`). **1b.** Napíš štruktúru `Args` s jedným argumentom `meno: String` (long + short). Sparsuj a vypíš. **1c.** Čo sa stane keď spustíš program bez argumentov? Čo keď s `--help`? --- ## Kapitola 2: Typy argumentov ### Povinný argument ```rust #[derive(Parser)] struct Args { #[arg(short, long)] subor: String, // POVINNÝ — program spadne ak chýba } ``` ```bash ./program --subor data.json # OK ./program # CHYBA: "required arguments were not provided" ``` ### Voliteľný argument (Option) ```rust #[derive(Parser)] struct Args { #[arg(short, long)] subor: Option, // VOLITEĽNÝ — None ak neuvedený } ``` ```bash ./program --subor data.json # args.subor = Some("data.json") ./program # args.subor = None ``` ### Bool flag ```rust #[derive(Parser)] struct Args { #[arg(short, long)] verbose: bool, // false ak neuvedený, true ak uvedený } ``` ```bash ./program --verbose # args.verbose = true ./program -v # args.verbose = true ./program # args.verbose = false ``` Bool flag neberá hodnotu! Stačí ho uviesť a je true. ### Argument s default hodnotou ```rust #[derive(Parser)] struct Args { #[arg(short, long, default_value = "data.json")] subor: String, #[arg(short, long, default_value_t = 10)] pocet: u32, } ``` ```bash ./program # subor = "data.json", pocet = 10 ./program --subor iny.json # subor = "iny.json", pocet = 10 ./program -p 5 # subor = "data.json", pocet = 5 ``` **Pozor:** `default_value` je pre String (vždy v úvodzovkách). `default_value_t` je pre iné typy (čísla, bool). ### Úlohy 2 **2a.** Napíš Args s: - `subor: String` — povinný - `vystup: Option` — voliteľný - `pocet: u32` — default 5 - `verbose: bool` — flag Vyskúšaj rôzne kombinácie spustenia. **2b.** Čo sa stane ak zadáš `--pocet ahoj` (nie číslo)? **2c.** Čo je rozdiel medzi `Option` a `String` s `default_value`? --- ## Kapitola 3: short a long ### Automatické short a long ```rust #[derive(Parser)] struct Args { #[arg(short, long)] subor: String, // --subor hodnota (long = názov poľa) // -s hodnota (short = prvé písmeno) } ``` ### Len long (bez short) ```rust #[derive(Parser)] struct Args { #[arg(long)] konfig: String, // --konfig hodnota OK // -k hodnota NEFUNGUJE } ``` ### Len short (bez long) ```rust #[derive(Parser)] struct Args { #[arg(short)] verbose: bool, // -v OK // --verbose NEFUNGUJE } ``` ### Vlastné short/long ```rust #[derive(Parser)] struct Args { #[arg(short = 'f', long = "file")] subor: String, // -f hodnota OK // --file hodnota OK // --subor hodnota NEFUNGUJE (premenovali sme) } ``` ### Kolízia short písmen ```rust // PROBLÉM: obe majú short 's' #[derive(Parser)] struct Args { #[arg(short, long)] subor: String, // -s #[arg(short, long)] stav: String, // -s KOLÍZIA! } // Riešenie: jedného premenuj #[derive(Parser)] struct Args { #[arg(short, long)] subor: String, // -s #[arg(short = 't', long)] stav: String, // -t } ``` ### Úlohy 3 **3a.** Napíš Args kde: - `input` má short `-i` a long `--input` - `output` má short `-o` a long `--output` - `debug` má len short `-d` (žiadny long) - `format` má len long `--format` (žiadny short) **3b.** Máš 3 polia: `subor`, `stav`, `slovo`. Všetky začínajú na 's'. Vyrieš kolíziu short písmen. --- ## Kapitola 4: Pozičné argumenty Doteraz sme používali pomenované argumenty (`--subor`, `-s`). Existujú aj pozičné — bez mena, len podľa poradia. ```rust #[derive(Parser)] struct Args { /// Vstupný súbor vstup: String, // PRVÝ pozičný argument /// Výstupný súbor vystup: String, // DRUHÝ pozičný argument #[arg(short, long)] verbose: bool, // Pomenovaný (flag) } ``` ```bash ./program input.json output.json --verbose # ^^^^^^^^^^ ^^^^^^^^^^^ # prvý druhý pozičný ``` **Pravidlo:** Ak pole nemá `#[arg(short, long)]`, je pozičné. ### Voliteľný pozičný ```rust #[derive(Parser)] struct Args { vstup: String, // povinný pozičný vystup: Option, // voliteľný pozičný } ``` ```bash ./program input.json # vystup = None ./program input.json output.json # vystup = Some("output.json") ``` ### Pozičný s default ```rust #[derive(Parser)] struct Args { #[arg(default_value = "data.json")] subor: String, } ``` ```bash ./program # subor = "data.json" ./program iny.json # subor = "iny.json" ``` ### Úlohy 4 **4a.** Napíš Args pre program, ktorý kopíruje súbor: ```bash ./kopiruj zdrojovy.txt cielovy.txt ``` Dva pozičné argumenty. **4b.** Napíš Args pre grep-like program: ```bash ./hladaj "hľadaný text" subor.txt --ignore-case ``` Pozičné: vzor a súbor. Pomenované: ignore_case (bool flag). --- ## Kapitola 5: Popis a help text ### Dokumentačné komentáre → help text ```rust /// Správca knižnice — CLI nástroj na správu kníh #[derive(Parser)] struct Args { /// Cesta k JSON súboru s dátami #[arg(short, long)] subor: String, /// Počet výsledkov na stránku #[arg(short, long, default_value_t = 10)] pocet: u32, /// Zapni podrobný výpis #[arg(short, long)] verbose: bool, } ``` ```bash ./program --help # Správca knižnice — CLI nástroj na správu kníh # # Usage: program [OPTIONS] --subor # # Options: # -s, --subor Cesta k JSON súboru s dátami # -p, --pocet Počet výsledkov na stránku [default: 10] # -v, --verbose Zapni podrobný výpis # -h, --help Print help ``` **Pravidlo:** `///` komentár nad štruktúrou = popis programu. `///` nad poľom = popis argumentu. clap ich automaticky zobrazí v `--help`. ### about a help atribúty ```rust #[derive(Parser)] #[command(about = "Správca knižnice", version = "1.0")] struct Args { #[arg(short, long, help = "Cesta k súboru")] subor: String, } ``` ```bash ./program --version # program 1.0 ``` ### Úlohy 5 **5a.** Pridaj ku každému argumentu z úlohy 2a popis (`///` komentár). Spusti s `--help` a pozri výstup. **5b.** Pridaj k štruktúre `#[command(about, version)]`. Vyskúšaj `--help` a `--version`. --- ## Kapitola 6: Subcommands — podpríkazy Väčšie programy majú podpríkazy: ```bash git add file.txt git commit -m "správa" git push origin main ``` V clap to riešiš cez enum: ```rust use clap::{Parser, Subcommand}; #[derive(Parser)] struct Args { #[command(subcommand)] prikaz: Prikaz, } #[derive(Subcommand)] enum Prikaz { /// Pridaj novú knihu Pridaj { /// Názov knihy nazov: String, /// Autor knihy autor: String, }, /// Odstráň knihu podľa názvu Odstran { /// Názov knihy na odstránenie nazov: String, }, /// Vypíš všetky knihy Vypis, /// Hľadaj knihy Hladaj { /// Hľadaný výraz vyraz: String, }, } fn main() { let args = Args::parse(); match args.prikaz { Prikaz::Pridaj { nazov, autor } => { println!("Pridávam: {} od {}", nazov, autor); } Prikaz::Odstran { nazov } => { println!("Odstraňujem: {}", nazov); } Prikaz::Vypis => { println!("Výpis kníh..."); } Prikaz::Hladaj { vyraz } => { println!("Hľadám: {}", vyraz); } } } ``` ```bash ./program pridaj "Duna" "Frank Herbert" ./program odstran "Duna" ./program vypis ./program hladaj "Herbert" ./program --help # Shows all subcommands ./program pridaj --help # Shows help for "pridaj" subcommand ``` ### Spoločné argumenty + subcommand ```rust #[derive(Parser)] struct Args { /// Cesta k JSON súboru #[arg(short, long, default_value = "data.json")] subor: String, #[command(subcommand)] prikaz: Prikaz, } ``` ```bash ./program --subor moje.json pridaj "Duna" "Herbert" # ^^^^^^^^^^^^^^^^^ ^^^^^^ spoločný arg pred subcommand ``` ### Úlohy 6 **6a.** Napíš CLI pre správcu kontaktov: ```bash ./kontakty pridaj "Anna" "+421900000000" ./kontakty odstran "Anna" ./kontakty vypis ./kontakty hladaj "Ann" ``` **6b.** Pridaj spoločný argument `--subor` (default "kontakty.json") pred subcommand. **6c.** Pridaj do `pridaj` subcomandu voliteľný argument `--email`. --- ## Kapitola 7: ValueEnum — enum ako argument Ak chceš aby argument bol jedna z preddefinovaných hodnôt: ```rust use clap::{Parser, ValueEnum}; #[derive(Clone, ValueEnum)] enum Format { Json, Csv, Text, } #[derive(Parser)] struct Args { #[arg(short, long, default_value = "json")] format: Format, } fn main() { let args = Args::parse(); match args.format { Format::Json => println!("Výstup ako JSON"), Format::Csv => println!("Výstup ako CSV"), Format::Text => println!("Výstup ako text"), } } ``` ```bash ./program --format json ./program --format csv ./program --format text ./program --format xml # CHYBA: "invalid value 'xml'" ./program --help # -f, --format [default: json] [possible values: json, csv, text] ``` clap automaticky: - Ukáže povolené hodnoty v `--help` - Odmietne neplatnú hodnotu - Case-insensitive porovnanie ### Na skúške — enum Stav ako argument ```rust #[derive(Clone, ValueEnum, Serialize, Deserialize, PartialEq, Debug)] enum Stav { Nova, Pouzivana, Poskodena, Vyradena, } #[derive(Subcommand)] enum Prikaz { ZmenStav { nazov: String, #[arg(short, long)] stav: Stav, }, PodlaStavu { #[arg(short, long)] stav: Stav, }, } ``` ```bash ./program zmen-stav "Duna" --stav pouzivana ./program podla-stavu --stav nova ``` ### Úlohy 7 **7a.** Vytvor enum `Priorita { Nizka, Stredna, Vysoka }` s `ValueEnum`. Použi ho ako argument v CLI. **7b.** Pridaj do subcommandu `pridaj` argument `--priorita` typu Priorita s defaultom `Stredna`. --- ## Kapitola 8: Vec argumenty — viacero hodnôt ```rust #[derive(Parser)] struct Args { /// Súbory na spracovanie subory: Vec, // 0 alebo viac pozičných } ``` ```bash ./program a.txt b.txt c.txt # args.subory = ["a.txt", "b.txt", "c.txt"] ./program # args.subory = [] ``` ### Minimálne 1 ```rust #[derive(Parser)] struct Args { /// Súbory na spracovanie (aspoň 1) #[arg(required = true)] subory: Vec, } ``` ```bash ./program a.txt b.txt # OK ./program # CHYBA: required ``` ### Pomenovaný s viacerými hodnotami ```rust #[derive(Parser)] struct Args { #[arg(short, long)] tagy: Vec, } ``` ```bash ./program --tagy rust --tagy cli --tagy serde # args.tagy = ["rust", "cli", "serde"] ./program -t rust -t cli -t serde # to isté ``` ### Úlohy 8 **8a.** Napíš CLI, ktoré berie zoznam čísel a vypočíta ich súčet: ```bash ./sucet 1 2 3 4 5 # Súčet: 15 ``` Hint: pozičný `Vec`. **8b.** Napíš CLI s pomenovaným argumentom `--vyluc` ktorý berie viacero mien. Potom vypíš "Vylúčení: Anna, Boris". --- ## Kapitola 9: Validácia argumentov ### value_parser — kontrola rozsahu ```rust #[derive(Parser)] struct Args { /// Port (1-65535) #[arg(short, long, value_parser = clap::value_parser!(u16).range(1..=65535))] port: u16, /// Počet (1-100) #[arg(short, long, value_parser = clap::value_parser!(u32).range(1..=100))] pocet: u32, } ``` ```bash ./program --port 8080 --pocet 50 # OK ./program --port 0 # CHYBA: "0 is not in 1..=65535" ./program --pocet 200 # CHYBA ``` ### PathBuf argument Na skúške cesta k súboru je vždy `PathBuf`: ```rust use std::path::PathBuf; #[derive(Parser)] struct Args { /// Cesta k dátovému súboru #[arg(short, long, default_value = "data.json")] subor: PathBuf, } fn main() { let args = Args::parse(); // args.subor je PathBuf — môžeš ho priamo použiť let kniznica = Kniznica::nacitaj_zo_suboru(&args.subor) .unwrap_or_default(); } ``` ```bash ./program --subor /cesta/k/suboru.json ./program -s data.json ./program # default: data.json ``` ### Úlohy 9 **9a.** Napíš Args s argumentom `--vek` typu u8, s rozsahom 0 až 150. **9b.** Napíš Args s PathBuf argumentom `--vstup` (povinný) a `--vystup` (voliteľný, default "output.json"). --- ## Kapitola 10: Kompletný vzor zo skúšky ### Vzor: Knižnica CLI ```rust use clap::{Parser, Subcommand, ValueEnum}; use serde::{Serialize, Deserialize}; use std::fs; use std::path::PathBuf; // === Typy === #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ValueEnum)] pub enum Stav { Nova, Pouzivana, Poskodena, Vyradena, } impl Default for Stav { fn default() -> Self { Stav::Nova } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Kniha { pub nazov: String, pub autor: String, pub rok: u16, pub stav: Stav, } #[derive(Debug, Serialize, Deserialize, Default)] pub struct Kniznica { pub knihy: Vec, } // === serde I/O === impl Kniznica { pub fn nacitaj_zo_suboru(cesta: &PathBuf) -> Option { let raw = fs::read_to_string(cesta).ok()?; serde_json::from_str(&raw).ok() } pub fn uloz_do_suboru(&self, cesta: &PathBuf) -> bool { let Ok(json) = serde_json::to_string_pretty(&self) else { return false; }; fs::write(cesta, json).is_ok() } pub fn pridaj(&mut self, kniha: Kniha) -> Result<(), String> { if self.knihy.iter().any(|k| k.nazov == kniha.nazov) { return Err(format!("Kniha '{}' už existuje", kniha.nazov)); } self.knihy.push(kniha); Ok(()) } pub fn odstran(&mut self, nazov: &str) -> Result { let pos = self.knihy.iter() .position(|k| k.nazov == nazov) .ok_or(format!("Kniha '{}' nenájdená", nazov))?; Ok(self.knihy.remove(pos)) } } // === CLI === /// Správca knižnice #[derive(Parser)] #[command(about, version)] struct Args { /// Cesta k JSON súboru #[arg(short, long, default_value = "kniznica.json")] subor: PathBuf, #[command(subcommand)] prikaz: Prikaz, } #[derive(Subcommand)] enum Prikaz { /// Pridaj novú knihu Pridaj { /// Názov knihy nazov: String, /// Autor knihy autor: String, /// Rok vydania #[arg(short, long)] rok: u16, /// Stav knihy #[arg(short, long, default_value = "nova")] stav: Stav, }, /// Odstráň knihu Odstran { /// Názov knihy nazov: String, }, /// Vypíš všetky knihy Vypis { /// Filtrovať podľa stavu #[arg(short, long)] stav: Option, }, /// Hľadaj knihy podľa autora Hladaj { /// Meno autora autor: String, }, } fn main() { let args = Args::parse(); // Načítaj alebo vytvor prázdnu let mut kniznica = Kniznica::nacitaj_zo_suboru(&args.subor) .unwrap_or_default(); match args.prikaz { Prikaz::Pridaj { nazov, autor, rok, stav } => { let kniha = Kniha { nazov, autor, rok, stav }; match kniznica.pridaj(kniha) { Ok(()) => println!("Kniha pridaná."), Err(e) => println!("Chyba: {}", e), } } Prikaz::Odstran { nazov } => { match kniznica.odstran(&nazov) { Ok(kniha) => println!("Odstránená: {} ({})", kniha.nazov, kniha.autor), Err(e) => println!("Chyba: {}", e), } } Prikaz::Vypis { stav } => { let knihy: Vec<&Kniha> = match &stav { Some(s) => kniznica.knihy.iter().filter(|k| &k.stav == s).collect(), None => kniznica.knihy.iter().collect(), }; if knihy.is_empty() { println!("Žiadne knihy."); } else { for kniha in &knihy { println!("{} — {} ({}) [{:?}]", kniha.nazov, kniha.autor, kniha.rok, kniha.stav); } } } Prikaz::Hladaj { autor } => { let najdene: Vec<&Kniha> = kniznica.knihy.iter() .filter(|k| k.autor.to_lowercase().contains(&autor.to_lowercase())) .collect(); if najdene.is_empty() { println!("Nič nenájdené."); } else { for kniha in &najdene { println!("{} — {}", kniha.nazov, kniha.autor); } } } } // Ulož kniznica.uloz_do_suboru(&args.subor); } ``` Spustenie: ```bash ./kniznica pridaj "Duna" "Frank Herbert" --rok 1965 ./kniznica pridaj "Hobbit" "J.R.R. Tolkien" --rok 1937 --stav pouzivana ./kniznica vypis ./kniznica vypis --stav nova ./kniznica hladaj "Tolkien" ./kniznica odstran "Duna" ./kniznica --subor backup.json vypis ``` ### Úlohy 10 **10a.** Implementuj kompletný CLI pre **Správcu úloh** (Task Manager): ``` Štruktúra: Uloha { nazov: String, priorita: Priorita, hotova: bool } Enum: Priorita { Nizka, Stredna, Vysoka } Subcommands: - pridaj --priorita - hotovo - odstran - vypis [--priorita ] - statistiky (počet celkom, hotových, nehotových) Spoločný: --subor (PathBuf, default "ulohy.json") ``` **10b.** Implementuj kompletný CLI pre **Milionár** kvíz: ``` Subcommands: - pridaj-otazku --odpovede <4 odpovede> --spravna - hraj - vypis-otazky Spoločný: --subor (PathBuf, default "otazky.json") ``` **10c.** Implementuj kompletný CLI pre **Adresár**: ``` Štruktúra: Kontakt { meno: String, telefon: String, email: Option } Subcommands: - pridaj [--email ] - odstran - hladaj - vypis [--zoradit] - export --format Spoločný: --subor (PathBuf, default "kontakty.json") ``` --- ## Prehľad: clap atribúty | Atribút | Kde | Čo robí | |---------|-----|---------| | `#[derive(Parser)]` | štruktúra | Hlavná CLI štruktúra | | `#[derive(Subcommand)]` | enum | Podpríkazy | | `#[derive(ValueEnum)]` | enum | Enum ako hodnota argumentu | | `#[command(subcommand)]` | pole | Toto pole je subcommand | | `#[command(about, version)]` | štruktúra | Pridaj popis a verziu | | `#[arg(short, long)]` | pole | Pomenovaný argument | | `#[arg(short = 'x')]` | pole | Vlastné short písmeno | | `#[arg(long = "nazov")]` | pole | Vlastný long názov | | `#[arg(default_value = "...")]` | pole | Default pre String/PathBuf | | `#[arg(default_value_t = N)]` | pole | Default pre čísla/bool | | `#[arg(required = true)]` | pole | Vynúť aj Vec | | `#[arg(value_parser = ...)]` | pole | Validácia | | `/// komentár` | štruktúra/pole | Help text | ## Prehľad: typy polí | Typ poľa | Správanie | |-----------|-----------| | `String` | Povinný argument | | `Option` | Voliteľný argument | | `bool` | Flag (true/false) | | `Vec` | Viacero hodnôt | | `PathBuf` | Cesta k súboru | | `u32, i32, f64...` | Automaticky parsované | | `MojEnum` (ValueEnum) | Povolené hodnoty |