Files
JR-priprava-na-skusku/priprava/rust_priprava10.md
2026-03-04 17:47:14 +01:00

957 lines
21 KiB
Markdown

# 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<String>, // 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<String>` — 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<String>` 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<String>, // 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 <SUBOR>
#
# Options:
# -s, --subor <SUBOR> Cesta k JSON súboru s dátami
# -p, --pocet <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 <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<String>, // 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<String>,
}
```
```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<String>,
}
```
```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<i32>`.
**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<Kniha>,
}
// === serde I/O ===
impl Kniznica {
pub fn nacitaj_zo_suboru(cesta: &PathBuf) -> Option<Kniznica> {
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<Kniha, String> {
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<Stav>,
},
/// 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 <nazov> --priorita <priorita>
- hotovo <nazov>
- odstran <nazov>
- vypis [--priorita <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 <text> --odpovede <4 odpovede> --spravna <index>
- 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<String> }
Subcommands:
- pridaj <meno> <telefon> [--email <email>]
- odstran <meno>
- hladaj <vyraz>
- vypis [--zoradit]
- export --format <json|csv>
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<String>` | Voliteľný argument |
| `bool` | Flag (true/false) |
| `Vec<String>` | Viacero hodnôt |
| `PathBuf` | Cesta k súboru |
| `u32, i32, f64...` | Automaticky parsované |
| `MojEnum` (ValueEnum) | Povolené hodnoty |