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

21 KiB

clap — krok za krokom

Každá kapitola: prečítaj, pozri príklady, urob úlohy. Až potom choď ďalej.

Cargo.toml:

[dependencies]
clap = { version = "4", features = ["derive"] }
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:

./moj_program --subor data.json --pocet 5 --verbose

clap ti tie argumenty automaticky sparsuje do Rust štruktúry.

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:

./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

#[derive(Parser)]
struct Args {
    #[arg(short, long)]
    subor: String,       // POVINNÝ — program spadne ak chýba
}
./program --subor data.json   # OK
./program                      # CHYBA: "required arguments were not provided"

Voliteľný argument (Option)

#[derive(Parser)]
struct Args {
    #[arg(short, long)]
    subor: Option<String>,    // VOLITEĽNÝ — None ak neuvedený
}
./program --subor data.json   # args.subor = Some("data.json")
./program                      # args.subor = None

Bool flag

#[derive(Parser)]
struct Args {
    #[arg(short, long)]
    verbose: bool,             // false ak neuvedený, true ak uvedený
}
./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

#[derive(Parser)]
struct Args {
    #[arg(short, long, default_value = "data.json")]
    subor: String,

    #[arg(short, long, default_value_t = 10)]
    pocet: u32,
}
./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

#[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)

#[derive(Parser)]
struct Args {
    #[arg(long)]
    konfig: String,
    // --konfig hodnota   OK
    // -k hodnota         NEFUNGUJE
}

Len short (bez long)

#[derive(Parser)]
struct Args {
    #[arg(short)]
    verbose: bool,
    // -v                 OK
    // --verbose          NEFUNGUJE
}

Vlastné short/long

#[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

// 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.

#[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)
}
./program input.json output.json --verbose
#         ^^^^^^^^^^ ^^^^^^^^^^^
#         prvý       druhý pozičný

Pravidlo: Ak pole nemá #[arg(short, long)], je pozičné.

Voliteľný pozičný

#[derive(Parser)]
struct Args {
    vstup: String,                  // povinný pozičný
    vystup: Option<String>,         // voliteľný pozičný
}
./program input.json                  # vystup = None
./program input.json output.json      # vystup = Some("output.json")

Pozičný s default

#[derive(Parser)]
struct Args {
    #[arg(default_value = "data.json")]
    subor: String,
}
./program              # subor = "data.json"
./program iny.json     # subor = "iny.json"

Úlohy 4

4a. Napíš Args pre program, ktorý kopíruje súbor:

./kopiruj zdrojovy.txt cielovy.txt

Dva pozičné argumenty.

4b. Napíš Args pre grep-like program:

./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

/// 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,
}
./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

#[derive(Parser)]
#[command(about = "Správca knižnice", version = "1.0")]
struct Args {
    #[arg(short, long, help = "Cesta k súboru")]
    subor: String,
}
./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:

git add file.txt
git commit -m "správa"
git push origin main

V clap to riešiš cez enum:

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);
        }
    }
}
./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

#[derive(Parser)]
struct Args {
    /// Cesta k JSON súboru
    #[arg(short, long, default_value = "data.json")]
    subor: String,

    #[command(subcommand)]
    prikaz: Prikaz,
}
./program --subor moje.json pridaj "Duna" "Herbert"
#         ^^^^^^^^^^^^^^^^^ ^^^^^^ spoločný arg pred subcommand

Úlohy 6

6a. Napíš CLI pre správcu kontaktov:

./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:

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"),
    }
}
./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

#[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,
    },
}
./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

#[derive(Parser)]
struct Args {
    /// Súbory na spracovanie
    subory: Vec<String>,              // 0 alebo viac pozičných
}
./program a.txt b.txt c.txt
# args.subory = ["a.txt", "b.txt", "c.txt"]

./program
# args.subory = []

Minimálne 1

#[derive(Parser)]
struct Args {
    /// Súbory na spracovanie (aspoň 1)
    #[arg(required = true)]
    subory: Vec<String>,
}
./program a.txt b.txt   # OK
./program               # CHYBA: required

Pomenovaný s viacerými hodnotami

#[derive(Parser)]
struct Args {
    #[arg(short, long)]
    tagy: Vec<String>,
}
./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:

./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

#[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,
}
./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:

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();
}
./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

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:

./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