diff --git a/priprava/rust_priprava10.md b/priprava/rust_priprava10.md new file mode 100644 index 0000000..a58b39c --- /dev/null +++ b/priprava/rust_priprava10.md @@ -0,0 +1,956 @@ +# 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 |