Protože bychom chtěli, aby aplikace byla multiplatformní, musíme ještě přidat parametr pro kódovou stránku (data aplikace jsou kódována v UTF-8). Ačkoliv to není nezbytně nutné, aplikaci „zabalíme“ do třídy, která bude mít jen jednu instanci (návrhový vzor Jedináček – Singleton). Aplikace by měla také podporovat standardní možnost nápovědy (parametr -h
resp. --help
) a zobrazení verze ( -v
resp. --version
).
1: require 'singleton'
2: require 'optparse'
3: require 'jmeniny'
4: require 'iconv'
5:
6: class App
7: include Singleton
8:
9: ID = 'Jmeniny CLI ver. 1.0.0'
10:
11: def initialize
12: get_options
13: end
14:
15: def get_options
16: codes = %w|cp852 iso-8859-2 windows-1250|
17: code_aliases = { "dos" => "cp852" }
18:
19: ARGV.options do |opts|
20: opts.banner = "Usage: #{File.basename $0} [options]"
21:
22: opts.separator ""
23: opts.separator "Specific options:"
24:
25: code_list = (code_aliases.keys + codes).join(',')
26: opts.on("-c", "--code-page CODE_PAGE", codes, code_aliases,
27: "Select encoding:", "(#{code_list})") { |@code_page| }
28: opts.on("-d", "--den DD.MM.", String, "") { |@den| }
29: opts.on("-j", "--jmeno JMENO", String, "") { |@jmeno| }
30:
31: opts.separator ""
32: opts.separator "General options:"
33:
34: opts.on('-h', '--help', 'Show this message and exit') { puts opts; exit }
35: opts.on('-v', '--version', 'Print version info and exit') { puts ID; exit }
36:
37: opts.parse!
38: end
39: end
40:
41: def run
42: if @den
43: den, mesic = @den.split('.')
44: jmeno = Jmeniny.jmeno(den.to_i, mesic.to_i)
45: jmeno = Iconv.iconv(@code_page, 'utf-8', jmeno) if @code_page and jmeno
46: puts jmeno if jmeno
47: elsif @jmeno
48: jmeno = @jmeno
49: jmeno = Iconv.new('utf-8', @code_page).iconv(jmeno) if @code_page
50: den, mesic = Jmeniny.den(jmeno)
51: puts "#{den}.#{mesic}." if den
52: end
53: end
54:
55: end
56:
57: App.instance.run
Na řádcích 1 až 4 vkládáme postupně prostředky pro vytvoření třídy – jedináčka (řádek 1), zpracování parametrů příkazového řádku (řádek 2), náš modul vytvořený minule (řádek 3) a interface na knihovnu Iconv pro převod mezi kódovými stránkami (řádek 4). Kromě našeho modulu je vše součástí standardní knihovny. Naše třída App
pak na řádku 7 importuje modul Singleton
, a stává se tak jedináčkem. ModulSingleton
znepřístupní metodu new pro vytváření instancí a nahradí ji metodou instance, která vrací vždy tutéž instanci třídy App
(řádek 57).
Metoda get_options
definovaná na řádcích 15 až 39 zajišťuje vše kolem parametrů příkazového řádku. Definuje jednotlivé volby (krátkou i dlouhou verzi), jejich případné parametry, nápovědu k volbě a akci, kterou volba spouští. Například na řádku 35 je definována volba pro výpis verze programu. Volba se zadává jako -v
nebo --version
, nenásledují žádné hodnoty, v nápovědě vypisuje text „Print version info and exit“, a pokud je volba v příkazovém řádku použita, vykoná se blok na konci řádku (výpis obsahu konstanty ID na konzoli a ukončení aplikace). Výstup vypadá takto:
Jmeniny CLI ver. 1.0.0
Nápověda programu (volba -h
nebo --help
) je zkonstruována automaticky a vypadá takto:
Usage: jmeniny-cli.rb [options]
Specific options:
-c, --code-page CODE_PAGE Select encoding:
(dos,cp852,iso-8859-2,windows-1250)
-d, --den DD.MM.
-j, --jmeno JMENO
General options:
-h, --help Show this message and exit
-v, --version Print version info and exit
Konstrukce na řádku 16 začínající %w
je zkratka pro pole řetězců vytvořené ze slov. Pole definuje povolený výčet kódování. Konstrukce %w
je jedním z dědictví Perlu. Ušetříme uvozovky okolo každého slova a čárky mezi nimi. Osobně ji sice docela rád používám (když jsem líný psát hodně uvozovek a čárek), ale standardní zápis by byl čitelnější. Řádek 16 zapsaný standardním způsobem by vypadal takto:
16: codes = [ "cp852", "iso-8859-2", "windows-1250" ]
Na řádku 17 je pak asociativní pole (Hash
), které definuje, že pro označení kódování „cp852“ lze použít i řetězec „dos“.
Na řádku 28 resp. 29 se zpracovává parametr datum resp. jméno. Při použití volby se nastaví proměnná @den
resp. @jmeno
(bloky na koncích řádků). Připomínám, že proměnné začínající zavináčem jsou proměnné instance (vnitřní proměnné objektu), a jsou tak přístupné ze všech metod instance.
Metoda run
(řádky 41 až 53) se spouští jako hlavní vstupní bod aplikace (řádek 57). Je-li zadán datum, řetězec tvaru „DD.MM.“ se rozloží na den a měsíc pomocí metodysplit
(řádek 43). Všimněte si vícenásobného přiřazení do proměnných den
a měsíc
. Ty se po převodu na číslo (metodou to_i
) použijí jako vstup metody Jmeniny.jmeno
(řádek 44). Pokud je jméno nalezeno a byla zadána kódová stránka konzoly, proběhne ještě konverze (řádek 45) a jméno je vypsáno na standardní výstup (řádek 46). Řádky 48 až 51 pak obsahují kód pro opačný směr hledání. Použití vypadá takto:
Ne každému musí vyhovovat zde použitý způsob zpracování parametrů příkazové řádky pomocí optparse
. V základní instalaci Ruby najdete i starší a jednoduššígetoptlong
(obdoba funkce getopt_long známé z GNU knihovny pro jazyk C). Pokročilejší zpracování parametrů příkazového řádku pak naleznete v balíčku cmdparse
, který ale není součástí standardní knihovny. Tento balíček je nadstavbou nadoptparse
ze standardní knihovny a umožňuje konstrukovat například víceúrovňové příkazy (commands with subcommands – např. jako u CLI klientů CVS).
Co by se dalo na naší malé aplikaci ještě zlepšit? Bylo by možné před první řádek našeho skriptu s aplikací uvést „shebang“ (např. #!/usr/bin/ruby
) a souboru přidat právo na spuštění. Pak bychom nemuseli na příkazovém řádku explicitně vypisovat volání interpretu Ruby. Přesněji řečeno, výše uvedené platí pro Linux, Unixy a podobné operační systémy. Šťastní majitelé MS Windows 2000 nebo XP, kteří si nainstalovali Ruby pomocí One-Click Ruby Installeru, se tím nemusejí trápit. Programy a skripty psané v Ruby by jim měly jít spouštět v „shellu“ (cmd) jen uvedením jména bez přípony – v našem případě tedy např.:
jmeno-cli -j petr -c dos
Nepříjemná je také nutnost neustále zadávat kódovou stránku (kromě šťastlivců, kteří již mají pro konzolu nakonfigurovanou kódovou stránku UTF-8). Na možné řešení se ale podíváme příště, kdy si zkusíme vytvořit aplikaci s grafickým rozhraním.
Objekty a metody podruhé
Ve druhém dílu jsme si ukázali několik způsobů, jak zajistit, aby objekt nebo všechny objekty dané třídy rozuměly určité zprávě. Nebo, jinak řečeno, aby se na instanci (nebo všech instancích dané třídy) dala zavolat určitá metoda. Velmi zajímavý způsob „správy“ metod nabízí také metoda method_missing
. Pokud se snažíme vyvolat metodu, která není definována, zavolá se právě metoda method_missing
. Implicitní implementace zařídí vyvolání výjimky NoMethodError
. My ale můžememethod_missing
přepsat a předepsat jiné chování než vyvolání výjimky. V dokumentaci je například načrtnuta implementace třídy, která umožňuje zadávat čísla pomocí římských číslic takto:
r = Roman.new
r.iv #=> 4
r.xxiii #=> 23
r.mm #=> 2000
V následujícím příkladu přepišme tuto metodu standardní tříděHash
( return
lze případně vynechat):
class Hash
def method_missing(sym_name)
return self[sym_name.to_s]
end
end
Pak můžeme získavat hodnoty z heše, i když zadáme klíč jako metodu, např.:
a = { 'key1' => 'val_a', 'key_2' => 3 }
# normalni zpusob:
a['key1'] #=> vysledek je 'val_a'
a['key_2'] #=> vysledek je 3
# pres metodu:
a.key1 #=> vysledek je 'val_a'
a.key_2 #=> vysledek je 3
Ačkoliv se může zdát, že jde jen o hříčky, tento způsob umožňuje například velmi efektivní implementaci návrhového vzoru Proxy (nejen) pro distribuované aplikace. Pokud dojde k volání neznámé metody, v přepsané method_missing
se serializují parametry, vyvolá vzdálená metoda a deserializují výsledky, které jsou vráceny volajícímu. Tomu se to pak jeví jako normální volání (lokální) metody.
Jinou možností aplikace method_missing
je, pokud potřebujeme třídu s větším množstvím metod se stejnou (nebo podobnou) implementací. Typicky by se mohlo jednat o generátor well-formed XML:
class XmlGen
def method_missing(sym_name)
print "<#{sym_name}>"
print yield if block_given?
print "</#{sym_name}>"
return ""
end
end
Použití by pak bylo:
x = XmlGen.new
x.a #=> vytiskne: <a></a>
x.a { x.b } #=> vytiskne: <a><b></b></a>
x.a { x.b { "muj text" } } #=> vytiskne: <a><b>muj text</b></a>
x.html {
[
x.head {
x.title { "Nadpis" }
},
x.body {
[
x.h1 { "Taky nadpis" },
x.p { "Prvni odstavec" }
]
}
]
}
Poslední použití XML generátoru vytvoří jednoduchou HTML stránku. Nakonec je potřeba varovat, že method_missing
je velmi silný nástroj, který je ale nutno používat s rozmyslem.