josh-gmail-backup 0.104

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1461 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright (C) 2008 Jan Svec and Filip Jurcicek
3
+ #
4
+ # YOU USE THIS TOOL ON YOUR OWN RISK!
5
+ #
6
+ # email: info@gmail-backup.com
7
+ #
8
+ #
9
+ # Disclaimer of Warranty
10
+ # ----------------------
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, licensor provides
13
+ # this tool (and each contributor provides its contributions) on an "AS IS"
14
+ # BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
15
+ # implied, including, without limitation, any warranties or conditions of
16
+ # TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR
17
+ # PURPOSE. You are solely responsible for determining the appropriateness of
18
+ # using this work and assume any risks associated with your exercise of
19
+ # permissions under this license.
20
+
21
+ """Framework pro parametrizaci skriptů
22
+
23
+ Úvod
24
+ ====
25
+
26
+ Rychlé prototypování v jazyce Python umožňuje vyvinutí samotného skriptu nebo
27
+ aplikace během několika hodin. Má-li však skript (aplikace) být široce
28
+ použitelnou, je třeba zpracovat kvalitní uživatelské rozhraní. Toto může být
29
+ zpracováno jednak pomocí příkazové řádky, konfiguračních souborů nebo
30
+ grafického uživatelského rozhraní.
31
+
32
+ Každý z těchto přístupů umožňuje (resp. vyžaduje) zadávání parametrů skriptu
33
+ pomocí řetězců. Zpracování a konverze těchto řetězců je úkolem aplikace.
34
+ Poněvadž jde o část aplikační logiky, jež se neustále opakuje, je možné ji
35
+ vyčlenit do samostatného frameworku.
36
+
37
+ Požadavky kladené na tento framework jsou především následující:
38
+
39
+ - Použití řetězců jako vstupních parametrů.
40
+ - Možnost specifikace požadavků na parametry (vícenásobný, vyžadovaný,
41
+ návratový ...).
42
+ - Práce s více zdroji parametrů zároveň (příkazová řádka, konfigurační
43
+ soubory, proměnné prostředí, GUI).
44
+ - Možnost spojit parametry z více zdrojů v jeden parametr.
45
+
46
+ Návrh
47
+ =====
48
+
49
+ Reprezentací parametrizovatelného skriptu ve frameworku `svc.scripting` je
50
+ instance třídy `ParametrizedObject`. Volby předané skriptu jsou funkčnímu
51
+ objektu předány jako hodnoty funkčních argumentů. To umožňuje používat kódovací
52
+ styl, kdy je nejprve napsána určitá funkce sloužící jako hlavní funkce skriptu,
53
+ odladěna pomocí testovacích funkcí a následně "zparametrizována" pomocí
54
+ frameworku ``svc.scripting``.
55
+
56
+ .. image:: ../uml1.png
57
+
58
+ Instance třídy ``OptionManager`` zodpovídá za správu všech parametrů (voleb,
59
+ options) funkčního objektu. Umožňuje získávání jmen voleb, jejich konverzních
60
+ funkcí a požadavků na tyto volby (specifiers, např. `Multiple` - vícenásobná
61
+ volba nebo `Required` - požadovaná volba). Obsahuje též metody pro kontrolování
62
+ (`OptionManager.validate`) a konverzi (`OptionStack.popObjects`) seznamu voleb
63
+ ve tvaru řetězců do tvaru objektů.
64
+
65
+ Při volání parametrizovaného objektu jsou provedeny následující operace
66
+ zůčastněných objektů:
67
+
68
+ .. image:: ../uml2.png
69
+
70
+ V současné době je ve frameworku implementována jediná třída s rozhraním
71
+ ``ExternalAdapter`` - třída `Script`. Pro napsání vlastního skriptu je třeba
72
+ odvodit potomka této třídy a v něm předefinovat následující metody a atributy:
73
+
74
+ - ``main`` - hlavní metoda skriptu, která je po provedení konstruktoru
75
+ převedena na instanci třídy `ParametrizedObject`.
76
+ - ``options`` - atribut typu ``dict`` specifikující popis jednotlivých voleb
77
+ skriptu. Více viz třída `OptionManager`.
78
+ - ``shortOpts``, ``posOpts`` - atributy popisující chování extraktoru
79
+ `extractors.CmdlineExtractor` sloužícího pro získávání voleb z příkazové řádky.
80
+ - ``envPrefix`` - atribut sloužící pro vytvoření extraktoru
81
+ `extractors.EnvironExtractor`.
82
+ - ``pyFiles``, ``pyFilesGlobals`` - atributy pro konstruktor extraktoru
83
+ `extractors.PyFileExtractor`.
84
+ - ``writeReturnValue`` - metoda sloužící pro uložení návratové hodnoty
85
+ skriptu.
86
+
87
+ Pokud odděděná třída nepředefinuje atributy specifikující chování extraktorů,
88
+ použijí se výchozí hodnoty. Konstruktor třídy ``Script`` vytvoří na základě
89
+ metody `Script.main` a atributu `Script.options` instanci třídy
90
+ ``ParametrizedObject``, přičemž nastaví adapter této instance (pomocí
91
+ `ParametrizedObject.setAdapter`) na instanci třídy ``Script``.
92
+
93
+ .. image:: ../uml3.png
94
+
95
+ Třída ``Script`` používá pro získání hodnota jednotlivých voleb z vnějšího
96
+ prostředí tzv. extraktory - třídy implementující rozhraní `Extractor`.
97
+ Extraktory jsou vytvářeny metodou `Script.createExtractors`.
98
+
99
+ Rozhraní ``Extractor`` umožňuje nastavení a získání zdroje extraktoru (metody
100
+ `Extractor.getSource` a `Extractor.setSource`), získání jména zdroje extraktoru
101
+ (`Extractor.getSourceName`), získání seznamu voleb pomocí extraktoru
102
+ (`Extractor.extract`). Extraktory rovněž obdrží odkaz na instanci třídy
103
+ ``OptionManager`` (`Extractor.setManager`). V případě, že objekt ``Script``
104
+ požaduje použití implicitního zdroje, použije ``Extractor.setSource(None)``.
105
+
106
+ Jméno zdroje extraktoru slouží pro jejich centralizovanou správu. Tu zajišťují
107
+ metody `Script.getSources` a `Script.setSources`. Centrální nastavení zdrojů
108
+ používá asociativní pole, přičemž jeho klíče odpovídají jménům zdrojů
109
+ extraktorů podle `Extractor.getSourceName` a hodnoty jsou zdroje následně
110
+ nastavené metodou `Extractor.setSource`.
111
+
112
+ Posloupnost operací vykonaných při vytvoření nové instance třídy `Script`:
113
+
114
+ .. image:: ../uml4.png
115
+
116
+ Při volání metody `Script.run` dojde k nastavení zdrojů pomocí
117
+ `Script.setSources` a následně k zavolání instance třídy `ParametrizedObject`.
118
+ Následuje obvyklý scénář spolupráce instance ``ParametrizedObject`` s rozhraním
119
+ ``ExternalAdapter``:
120
+
121
+ .. image:: ../uml5.png
122
+
123
+ Seznamy voleb ve formě řetězců navrácených metodami `Extractor.extract` jsou
124
+ zřetězeny do jediného seznamu a předány metodě
125
+ `OptionStack.popObjects`. Je-li této metodě předán seznam, v němž jsou
126
+ dvě volby specifikovány v různých zdrojích, pak volba definovaná později
127
+ přepíše původní hodnotu. Je-li však použit specifikátor `JoinSources`, jsou
128
+ tyto hodnoty řazeny za sebou do jediného seznamu. Metoda
129
+ `OptionManager.validate` kontroluje chyby při zadávání voleb, například zadání
130
+ jednoduché volby vícekrát v jednom zdroji, vynechání povinné volby, zadání
131
+ neznámé volby apod.
132
+
133
+ Příklad
134
+ =======
135
+
136
+ Mějme následující funkci pro kopírování souborů::
137
+
138
+ def cp(source, destination, verbose=False):
139
+ if len(source) == 0:
140
+ raise ValueError("You must specify source files")
141
+ if os.path.isdir(destination):
142
+ for fn in source:
143
+ fn_file = os.path.basename(fn)
144
+ if verbose:
145
+ print fn
146
+ fr = file(fn)
147
+ fw = file(os.path.join(destination, fn_file), 'w')
148
+ try:
149
+ fw.write(fr.read())
150
+ finally:
151
+ fr.close()
152
+ fw.close()
153
+ else:
154
+ if len(source) > 1:
155
+ raise ValueError("Destination must be directory")
156
+ if verbose:
157
+ print fn
158
+ fr = file(source[0])
159
+ fw = file(destination, 'w')
160
+ try:
161
+ fw.write(fr.read())
162
+ finally:
163
+ fr.close()
164
+ fw.close()
165
+
166
+ Nyní tuto funkci použijeme pro vytvoření modulu - skriptu, jež bude obdobou
167
+ UNIXového příkazu ``cp`` (následující kód uložme do souboru ``cp.py``)::
168
+
169
+ #!/usr/bin/env python2.4
170
+ import os
171
+ from svc.scripting import *
172
+
173
+ def cp(source, destination, verbose=False):
174
+ ... # See above
175
+
176
+ class CP(Script):
177
+ debug = False
178
+ debugMain = False
179
+
180
+ main = staticmethod(cp)
181
+ options = {
182
+ 'source': (Required, Multiple, String),
183
+ 'destination': (Required, String),
184
+ 'verbose': Flag,
185
+ }
186
+
187
+ shortOpts = {'v': 'verbose'}
188
+ posOpts = ['source', Ellipsis, 'destination']
189
+
190
+ if __name__ == '__main__':
191
+ script = CP()
192
+ script.run()
193
+
194
+ Vytvořili jsme obálkovou třídu ``CP`` odděděnou od třídy `Script`. Dále jsme
195
+ vypnuli její ladicí možnosti, tj. při výjimkách se bude vypisovat pouze
196
+ jednořádkové hlášení o chybě. Jako hlavní metodu ``main`` jsme přiřazením
197
+ určili funkci ``cp``. Pro použití jako metoda musí být funkce ``cp`` převedena
198
+ na statickou metodu.
199
+
200
+ Dále jsme určili typ jednotlivých voleb. Volba ``source`` reprezentující jména
201
+ zdrojových souborů je povinná a může být specifikována vícekrát. Její typ je
202
+ řetězec (``String``). Obdobně jméno cílového souboru, popř. adresáře, musí být
203
+ určeno a je řetězcového typu. Nakonec volba ``verbose`` zapínající tisk
204
+ ladicích informací je typu příznak (``Flag``).
205
+
206
+ Následují dva atributy určující chování třídy `extractors.CmdlineExtractor`.
207
+ Prvním je slovník ``shortOpts``, jde o výčet krátkých argumentů příkazového
208
+ řádku. V našem případě tedy můžeme na příkazovém řádku zapsat ``-v`` namísto
209
+ ``--verbose`` se stejným efektem. Druhý atribut ``posOpts`` je seznam mapující
210
+ jména pozičních argumentů na jména voleb. Hodnota `Ellipsis` v tomto případě
211
+ znamená *maximální množství voleb* a může být použita pouze jednou. Volba
212
+ ``source`` tedy z příkazové řádky obdrží poziční argumenty (nikoli krátké nebo
213
+ dlouhé argumenty) příkazové řádky 1 až (N-1), zatímco volba ``destination``
214
+ obdrží poslední N-tý argument.
215
+
216
+ Poslední tři řádky zajistí vytvoření a spuštění instance třídy ``CP``, pokud je
217
+ modul spuštěn jako hlavní modul jazyka Python. Uložíme-li zdrojový kód do
218
+ souboru ``cp.py``, můžeme ho používat následujícím způsobem ($ značí výzvu
219
+ příkazového řádku)::
220
+
221
+ $ ./cp.py
222
+ Script CP: Option 'destination' is not specified
223
+ $ ./cp.py xyz.txt
224
+ Script CP: Option 'source' is not specified
225
+ $ ./cp.py abc.txt xyz.txt
226
+ $ ./cp.py -v abc.txt xyz.txt
227
+ abc.txt
228
+ $ ./cp.py --verbose abc.txt opq.txt xyz.txt dir
229
+ abc.txt
230
+ opq.txt
231
+ xyz.txt
232
+ $ ./cp.py dir xyz.txt
233
+ Script CP: IOError: [Errno 21] Is a directory
234
+
235
+ :Variables:
236
+ - `Required` - specifikátor *povinné* volby
237
+ - `Multiple` - specifikátor *vícenásobné* volby
238
+ - `JoinSources` - specifikátor *vícenásobné* volby, přičemž je možné
239
+ spojovat volby definované ve více zdrojích
240
+ - `EnvVar` - volba s tímto specifikátorem může být specifikována proměnnou
241
+ prostředí
242
+ - `FullParam` - je-li použito jako specifikátor, pak jméno volby je určeno
243
+ jeho plnou cestou s tečkami '.' nahrazenými podtržítkem '_', jinak je
244
+ jako jméno volby použit poslední element cesty (tj. jméno za poslední
245
+ tečkou).
246
+ - `Prior` - konverze voleb s tímto specifikátorem je provedena před
247
+ konverzí ostatních
248
+ """
249
+ import sys
250
+ import os
251
+ import logging
252
+ import inspect
253
+
254
+ from svc.egg import PythonEgg
255
+ from svc.utils import sym, issequence, isstr, seqIntoDict
256
+ from svc.scripting.conversions import *
257
+ from svc.scripting.help import HelpManager
258
+
259
+ __docformat__ = 'restructuredtext cs'
260
+
261
+ Required = sym('Required')
262
+ Multiple = sym('Multiple')
263
+ JoinSources = sym('JoinSources')
264
+ FullParam = sym('FullParam')
265
+ EnvVar = sym('EnvVar')
266
+ Prior = sym('Prior')
267
+
268
+ # TODO: Other specifiers
269
+ # GlobOption = sym('GlobOption')
270
+ # FlatList = sym('FlatList')
271
+
272
+ OptionAlias = sym('OptionAlias')
273
+
274
+ class ParametrizedObject(PythonEgg):
275
+ """Třída reprezentující parametrizovaný objekt
276
+
277
+ :Ivariables:
278
+ - `_state` - odkaz na objekt `OptionStack` obsahující stav konverze
279
+ objektů. Existuje po dobu od zavolání `createState` do zavolání
280
+ `destroyState`. Po tuto dobu k němu lze přistupovat pomocí metody
281
+ `getState`.
282
+ """
283
+ def createState(self):
284
+ raise TypeError("Abstract method ParametrizedObject.createState()")
285
+
286
+ def getState(self):
287
+ return self._state
288
+
289
+ def setState(self, state):
290
+ self._state = state
291
+
292
+ def destroyState(self):
293
+ del self._state
294
+
295
+ def premain(self):
296
+ self.state.enableAll()
297
+ self.state.disable(self.state.manager.paramsChildren('__premain__'))
298
+ return False
299
+
300
+ def main(self):
301
+ raise TypeError("Abstract method ParametrizedObject.main()")
302
+
303
+ def run(self):
304
+ """Zavolá parametrizovaný objekt
305
+
306
+ Nejprve jsou pomocí `ExternalAdapter.extractOptions` získány volby ve
307
+ formě řetězců, následně jsou metodou `OptionStack.popObjects`
308
+ převedeny na objekty. Následně dojde k rozdělení na *přímé* a
309
+ *návratové* volby. Poté je zavolána hlavní funkce s přímými volbami a
310
+ nakonec je její návratová hodnota uložena pomocí
311
+ `ExternalAdapter.writeReturnValue`.
312
+
313
+ Při vzniklých výjimkách jsou zavolány příslušné handlery chyb.
314
+ """
315
+
316
+ self.createState()
317
+ self.state.disableAll()
318
+ self.state.enable(self.manager.paramsChildren('__premain__'))
319
+
320
+ retval = True
321
+ while retval:
322
+ try:
323
+ objects = self.state.popObjects()
324
+ except OptionError:
325
+ self._validationError(sys.exc_info()[1])
326
+ except:
327
+ self._conversionError(sys.exc_info()[1])
328
+
329
+ premain_opts = objects.get('__premain__', {})
330
+ try:
331
+ retval = self.premain(**premain_opts)
332
+ except SystemExit:
333
+ raise
334
+ except:
335
+ self._mainError(sys.exc_info()[1])
336
+
337
+
338
+ try:
339
+ objects = self.state.getObjects()
340
+ except OptionError:
341
+ self._validationError(sys.exc_info()[1])
342
+ except SystemExit:
343
+ raise
344
+ except:
345
+ self._conversionError(sys.exc_info()[1])
346
+
347
+ try:
348
+ retval = self.main(**objects)
349
+ except:
350
+ self._mainError(sys.exc_info()[1])
351
+
352
+ self.destroyState()
353
+
354
+ return retval
355
+
356
+ def _conversionError(self, e):
357
+ raise
358
+
359
+ def _validationError(self, e):
360
+ raise
361
+
362
+ def _mainError(self, e):
363
+ raise
364
+
365
+ class OptionError(Exception):
366
+ def __init__(self, msg, option=''):
367
+ Exception.__init__(self, msg, option)
368
+ self.option = option
369
+ self.msg = msg
370
+
371
+ def __str__(self):
372
+ return self.msg
373
+
374
+ class OptionManager(PythonEgg):
375
+ """Třída pro správu, kontrolu a konverzi voleb
376
+
377
+ Vstupním údajem pro vytvoření instance je tzv. *specifikace voleb*. Jde o
378
+ slovník, jehož klíče jsou názvy voleb a hodnoty určují jejich konkrétní
379
+ vlastnosti. Hodnoty jsou tuple obsahující *specifikátory*, *konverzní
380
+ funkci* a její *dodatečné argumenty*.
381
+
382
+ Příklad:
383
+ ========
384
+
385
+ ::
386
+
387
+ specification = {
388
+ 'option1' : Integer,
389
+ 'option2' : (Required, Integer),
390
+ 'option3' : (Required, ListOf, Integer),
391
+ }
392
+
393
+ V uvedeném příkladě je volba ``option1`` číslo. Její konverzní funkce je
394
+ funkce `Integer`. Pokud volbu určuje pouze konverzní funkce, lze ji uvést
395
+ jako samostatnou hodnotu a ne jak tuple.
396
+
397
+ Volba ``option2`` je opět celé číslo. Podle specifikátoru před konverzní
398
+ funkci jde však o volby povinnou (`Required`).
399
+
400
+ Konečně volba ``option3`` je seznam celých čísel. Konverzní funkce `ListOf`
401
+ bude volána ve tvaru::
402
+
403
+ ListOf(option_value, Integer)
404
+
405
+ kde ``option_value`` je hodnota volby typu řetězec. Konverzní funkce
406
+ `ListOf` rozdělí řetězec podle znaků čárka a na výslednou posloupnost
407
+ řetězců aplikuje konverzní funkci `Integer`, jež jí byla předána jakou
408
+ druhý argument.
409
+
410
+ Získání jednotlivých položek ze specifikace volby probíhá následovně:
411
+
412
+ 1. Specifikace volby musí obsahovat alespoň jeden objekt, jež je
413
+ funkčním objektem - *konverzní funkcí* (viz metoda `conversion`).
414
+ 2. Všechny objekty před konverzní funkcí jsou *specifikátory* (viz
415
+ `Required`, `Multiple` atd., též metoda `specifiers`).
416
+ 3. Všechny objekty za konverzní funkcí jsou *dodatečné argumenty*
417
+ konverzní funkce a jsou jí předány za hodnotou konvertovaného
418
+ řetězce (viz metoda `conversion`).
419
+
420
+ :Ivariables:
421
+ - `_specification` - specifikace voleb
422
+ - `_rawspec` - undocumented
423
+ """
424
+
425
+ def __init__(self, specification, docs={}):
426
+ self._aliases = set()
427
+ self._optionAliases = {}
428
+ self.specification = specification
429
+ self.helpForOptions = docs
430
+
431
+ def getSpecification(self):
432
+ """Vrátí aktuální specifikaci voleb
433
+ """
434
+ return self._specification
435
+
436
+ def setSpecification(self, specification):
437
+ """Nastaví novou specifikaci voleb
438
+ """
439
+ self.validateSpecification(specification)
440
+ self._specification = specification
441
+ rawspec = self._rawspec = {}
442
+ param2option = self._paramToOptionMap = {}
443
+ option2param = self._optionToParamMap = {}
444
+
445
+ aliases = set()
446
+
447
+ for key, value in specification.iteritems():
448
+ if value == OptionAlias:
449
+ aliases.add(key)
450
+ continue
451
+
452
+ if not issequence(value):
453
+ value = [value]
454
+ specifiers = []
455
+ conversion = None
456
+ args = []
457
+ for i in value:
458
+ if callable(i) and not conversion:
459
+ conversion = i
460
+ elif not conversion:
461
+ specifiers.append(i)
462
+ else:
463
+ args.append(i)
464
+ specifiers = frozenset(specifiers)
465
+
466
+ if FullParam not in specifiers:
467
+ # Get option name from parameter name
468
+ new_key = self._splitOptionName(key)
469
+ else:
470
+ # Convert dotted parameter into underscored parameter
471
+ new_key = key.replace('.', '_')
472
+
473
+ if new_key in option2param:
474
+ raise ValueError("%r and %r both maps to %r" \
475
+ % (key, option2param[new_key], new_key))
476
+ option2param[new_key] = key
477
+ param2option[key] = new_key
478
+ rawspec[key] = (specifiers, conversion, args)
479
+
480
+ self.setAliases(aliases)
481
+
482
+ def getAliases(self):
483
+ return self._aliases
484
+
485
+ def setAliases(self, aliases):
486
+ del self.aliases
487
+ self._aliases = set(aliases)
488
+ self._optionAliases = {}
489
+ for param in self._aliases:
490
+ option = self._splitOptionName(param)
491
+ self._paramToOptionMap[param] = option
492
+ ref_param = self.optionToParam(option)
493
+ self._rawspec[param] = self._rawspec[ref_param]
494
+ if option not in self._optionAliases:
495
+ self._optionAliases[option] = set([ref_param])
496
+ self._optionAliases[option].add(param)
497
+
498
+ def delAliases(self):
499
+ # FIXME: Remove
500
+ for param in self._aliases:
501
+ del self._paramToOptionMap[param]
502
+ del self._rawspec[param]
503
+ self._aliases.clear()
504
+ self._optionAliases.clear()
505
+
506
+ def getHelpForOptions(self):
507
+ return self._helpForOptions
508
+
509
+ def setHelpForOptions(self, help):
510
+ unknown = set(help.keys()) - self.options()
511
+ if unknown:
512
+ raise ValueError("Unknown option: %r" % unknown.pop())
513
+ self._helpForOptions = help
514
+
515
+ def options(self):
516
+ """Vrátí seznam jmen všech voleb
517
+ """
518
+ return set(self.paramToOption(k) for k in self.params())
519
+
520
+ def optionsWithSpecifier(self, specifier):
521
+ """Vrátí seznam jmen všech voleb SE specifikátorem `specifier`
522
+ """
523
+ return set(self.paramToOption(k) for k in self.paramsWithSpecifier(specifier))
524
+
525
+ def optionsWithoutSpecifier(self, specifier):
526
+ """Vrátí seznam jmen všech voleb BEZ specifikátoru `specifier`
527
+ """
528
+ return set(self.paramToOption(k) for k in self.paramsWithoutSpecifier(specifier))
529
+
530
+ def params(self):
531
+ """Vrátí seznam jmen všech parametrů
532
+ """
533
+ return set(self._rawspec)
534
+
535
+ def paramsWithSpecifier(self, specifier):
536
+ """Vrátí seznam jmen všech parametrů SE specifikátorem `specifier`
537
+ """
538
+ return set(key for (key, (s, c, a)) in self._rawspec.iteritems()
539
+ if specifier in s)
540
+
541
+ def paramsWithoutSpecifier(self, specifier):
542
+ """Vrátí seznam jmen všech parametrů BEZ specifikátoru `specifier`
543
+ """
544
+ return set(key for (key, (s, c, a)) in self._rawspec.iteritems()
545
+ if specifier not in s)
546
+
547
+ def paramsAbove(self, level):
548
+ """Vrátí množinu jmen parametrů na úrovni nejvýše `level`
549
+ """
550
+ return set(p for p in self.params() if len(self._splitParam(p)) <= level)
551
+
552
+ def paramsBelow(self, level):
553
+ """Vrátí množinu jmen parametrů na úrovni pod `level`
554
+ """
555
+ return set(p for p in self.params() if len(self._splitParam(p)) > level)
556
+
557
+ def paramsChildren(self, param):
558
+ """Vrátí množinu parametrů, jež jsou dětmi parametru `param`
559
+ """
560
+ param_t = self._splitParam(param)
561
+ m = len(param_t)
562
+ return set(p for p in self.params() if self._splitParam(p)[:m] == param_t)
563
+
564
+ def specifiers(self, paramName):
565
+ """Vrátí všechny specifkátory parametru `paramName`
566
+ """
567
+ return self._rawspec[paramName][0]
568
+
569
+ def conversion(self, paramName):
570
+ """Vrátí dvojici (konverzní_funkce, dodatečné_argumenty) pro parametr `paramName`
571
+ """
572
+ return self._rawspec[paramName][1:]
573
+
574
+ def validateSpecification(self, specification):
575
+ """Provede kontrolu specifikace (nepoužito)
576
+ """
577
+
578
+ def paramToOption(self, param):
579
+ try:
580
+ return self._paramToOptionMap[param]
581
+ except KeyError:
582
+ raise OptionError("Unknown param %r" % param, param)
583
+
584
+ def optionToParam(self, option):
585
+ try:
586
+ return self._optionToParamMap[option]
587
+ except KeyError:
588
+ raise OptionError("Unknown option %r" % option, option)
589
+
590
+ def optionToAliases(self, option):
591
+ aliases = self._optionAliases.get(option, None)
592
+ if aliases is not None:
593
+ return aliases
594
+ else:
595
+ return set([self.optionToParam(option)])
596
+
597
+ def _splitParam(self, param):
598
+ return param.split('.')
599
+
600
+ def _splitOptionName(self, param):
601
+ return self._splitParam(param)[-1]
602
+
603
+ class OptionStack(PythonEgg, list):
604
+ """Třída pro uchovávání aktuální stavu OptionManageru
605
+ """
606
+ def __init__(self, manager):
607
+ self._enabledParams = set()
608
+ self.setManager(manager)
609
+ super(OptionStack, self).__init__()
610
+
611
+ def getManager(self):
612
+ return self._manager
613
+
614
+ def setManager(self, m):
615
+ self._manager = m
616
+ self.clear()
617
+
618
+ def clear(self):
619
+ """Znovupovolí všechny parametry a vymaže stav
620
+ """
621
+ self.enableAll()
622
+ del self[:]
623
+
624
+ def _checkParams(self, params):
625
+ """Provede kontrolu `params` a vrátí ho jako množinu
626
+
627
+ Kontroluje neexistující parametry.
628
+ """
629
+ params = set(params)
630
+ bad_params = params - self.manager.params()
631
+ if bad_params:
632
+ bad_param = bad_params.pop()
633
+ raise OptionError("Unknown parameter in set: %r" % bad_param, bad_param)
634
+ return params
635
+
636
+ def enable(self, params):
637
+ """Povolí pouze parametry `params`
638
+ """
639
+ self._enabledParams |= self._checkParams(params)
640
+
641
+ def enableAll(self):
642
+ """Povolí všechny parametry
643
+ """
644
+ self._enabledParams = self.manager.params()
645
+
646
+ def enableExcept(self, params):
647
+ """Povolí všechny parametry kromě `params`
648
+ """
649
+ self._enabledParams = self.manager.params() - self._checkParams(params)
650
+
651
+ def disable(self, params):
652
+ """Zakáže pouze parametry `params`
653
+ """
654
+ self._enabledParams -= self._checkParams(params)
655
+
656
+ def disableAll(self):
657
+ """Zakáže všechny parametry
658
+ """
659
+ self._enabledParams.clear()
660
+
661
+ def disableExcept(self, params):
662
+ """Zakáže všechny parametry kromě `params`
663
+ """
664
+ self._enabledParams = self._checkParams(params)
665
+
666
+ def getEnabled(self):
667
+ """Vrátí množinu všech povolených parametrů
668
+ """
669
+ return set(self._enabledParams)
670
+
671
+ def getDisabled(self):
672
+ """Vrátí množinu všech zakázaných parametrů
673
+ """
674
+ return self.manager.params() - self._enabledParams
675
+
676
+ def popObjects(self):
677
+ return self.getObjects(_pop=True)
678
+
679
+ def getObjects(self, _pop=False):
680
+ """Převede zásobník voleb na slovník
681
+
682
+ Ze seznamu voleb ve formě řetězců vytvoří slovník, jehož klíče jsou
683
+ rovny jménům voleb a hodnoty objektům vzniklým po konverzi řetězců.
684
+
685
+ Seznam voleb je tvořen uspořádanými čteřicemi ve tvaru::
686
+
687
+ (name, value, source_name, description)
688
+
689
+ kde ``name`` je jméno volby, ``value`` její řetězcová hodnota,
690
+ ``source_name`` je jméno zdroje, ze kterého pochází a ``description``
691
+ je libovolný textový popis volby, který se použije při výpisu chyby.
692
+
693
+ Je možné, aby ``value`` byl i libovolný objekt, pak se nebude provádět
694
+ konverzní funkce a použije se rovnou hodnota tohoto objektu.
695
+
696
+ Před vlastní konverzí je zavolána metoda `validate` pro kontrolu
697
+ správnosti seznamu voleb.
698
+ """
699
+ self.validate()
700
+ objects = _OptionTree(self.manager)
701
+ not_processed = []
702
+ yet_processed = set()
703
+
704
+ def isntPrior(item):
705
+ return Prior not in self.manager.specifiers(self.manager.optionToParam(item[0]))
706
+
707
+ for item in sorted(self, key = isntPrior):
708
+ opt_name, opt_val, source, desc = item
709
+ # Create param name from the short option name
710
+ par_name = self.manager.optionToParam(opt_name)
711
+ targets = self.manager.optionToAliases(opt_name)
712
+
713
+ if not (targets & self.enabled):
714
+ # If parameter is disabled, parameter value will be stored for
715
+ # future processing
716
+ not_processed.append(item)
717
+ # Skip disabled parameters
718
+ continue
719
+
720
+ opt_val = self.convertParameter(par_name, opt_val)
721
+
722
+ for par_name in targets & self.enabled:
723
+ objects.storeValue(par_name, opt_val, source)
724
+
725
+ # Store parameter into an already processed set
726
+ yet_processed.add(par_name)
727
+
728
+ if _pop:
729
+ # Store not processed items in OptionStack for future processing
730
+ self[:] = not_processed
731
+ # Disable already processed parameters
732
+ self.disable(yet_processed)
733
+
734
+ return objects.nested()
735
+
736
+ def convertParameter(self, par_name, str_value):
737
+ if isstr(str_value):
738
+ # If opt_val is string, call conversion function
739
+ conv_func, conv_args = self.manager.conversion(par_name)
740
+ try:
741
+ return conv_func(str_value, *conv_args)
742
+ except:
743
+ e = sys.exc_info()[1]
744
+ e.optionName = par_name
745
+ raise
746
+ else:
747
+ return str_value
748
+
749
+ def validate(self):
750
+ """Zkontroluje zásobník voleb
751
+
752
+ Hledá tři typy chyb:
753
+
754
+ 1. Volby bez specifikace (chybně zapsané volby, překlepy)
755
+ 2. Vícekrát určené jednoduché volby (vícenásobné použití parametru
756
+ na příkazové řádce apod.)
757
+ 3. Chybějící povinné volby (tj. se specifikátorem `Required`)
758
+
759
+ :Raises ValueError:
760
+ Při porušení libovolného pravidla 1. - 3.
761
+ """
762
+ opt_counts = dict((key, 0) for key in self.manager.params())
763
+ par_specs = dict((key, self.manager.specifiers(key)) for key in self.manager.params())
764
+ opt_lastsource = dict((key, None) for key in self.manager.options())
765
+ for opt_name, par_str_val, source_name, desc in self:
766
+ # If option has no enabled alias, skip this option
767
+ aliases = self.manager.optionToAliases(opt_name)
768
+ if not aliases & self.enabled:
769
+ continue
770
+
771
+ par_name = self.manager.optionToParam(opt_name)
772
+
773
+ if opt_lastsource[opt_name] != source_name:
774
+ opt_counts[opt_name] = 0
775
+ opt_counts[opt_name] += 1
776
+ opt_count = opt_counts[opt_name]
777
+ par_spec = par_specs[par_name]
778
+ if opt_count > 1 and not (Multiple in par_spec or JoinSources in par_spec):
779
+ raise OptionError("Option %r specified multiple times (source: %s, %s)" \
780
+ % (opt_name, source_name, desc), opt_name)
781
+
782
+ opt_lastsource[opt_name] = source_name
783
+
784
+ for par_name in self.manager.paramsWithSpecifier(Required) & self.enabled:
785
+ opt_name = self.manager.paramToOption(par_name)
786
+ if opt_name not in opt_counts or opt_counts[opt_name] == 0:
787
+ raise OptionError("Option %r is not specified" % opt_name, opt_name)
788
+
789
+ return True
790
+
791
+ def addObjects(self, dict, source='dict', subsource=None):
792
+ """Přidá na zásobník voleb objekty ze slovníku `dict`
793
+
794
+ Rozšíří zásobník voleb o prvky slovníku. Jeho klíče jsou jména voleb
795
+ (options) a hodnoty odpovídají hodnotě volby. Je-li volba vícenásobná
796
+ (tj. specifikátory Multiple nebo JoinSources), považuje se odpovídající
797
+ hodnota ve slovníku za sekvenční kontejner jenž je procházen a jeho
798
+ prvky jsou postupně přidávány na zásobník.
799
+
800
+ :Parameters:
801
+ - `dict` - slovník, jehož hodnoty rozšíří zásobník
802
+ - `source` - použitá hodnota zdroje, defaultně 'dict'
803
+ - `subsource` - použitá hodnota podzdroje, defaultně `None`
804
+ """
805
+ multi_options = self.manager.optionsWithSpecifier(Multiple)
806
+ multi_options |= self.manager.optionsWithSpecifier(JoinSources)
807
+
808
+ tmp = []
809
+ for opt_name, value in dict.iteritems():
810
+ if opt_name in multi_options:
811
+ if not issequence(value):
812
+ value = [value]
813
+ for subvalue in value:
814
+ tmp.append( (opt_name, subvalue, source, subsource) )
815
+ else:
816
+ tmp.append( (opt_name, value, source, subsource) )
817
+ self.extend(tmp)
818
+
819
+ class _OptionTree(dict):
820
+ pathsep = '.'
821
+
822
+ def __init__(self, manager):
823
+ super(_OptionTree, self).__init__()
824
+ self._manager = manager
825
+ self._optionSources = {}
826
+
827
+ def nested(self, path=None):
828
+ if path is not None:
829
+ req_path = path.split(self.pathsep)
830
+ req_path_len = len(req_path)
831
+ else:
832
+ req_path = []
833
+ req_path_len = 0
834
+ ret = {}
835
+
836
+ def getParentDict(path):
837
+ old_path = []
838
+ parent = ret
839
+ while path:
840
+ parent = parent.setdefault(path[0], {})
841
+ old_path.append(path[0])
842
+ if not isinstance(parent, dict):
843
+ return None
844
+ path = path[1:]
845
+ return parent
846
+
847
+ for key, value in self.iteritems():
848
+ path = key.split(self.pathsep)
849
+ if path[:req_path_len] != req_path:
850
+ continue
851
+ else:
852
+ path = path[req_path_len:]
853
+
854
+ where_key = path[-1]
855
+ path = path[:-1]
856
+ where = getParentDict(path)
857
+ if where is None or where_key in where:
858
+ raise ValueError("Cannot branch tree (node %r)" % key)
859
+ where[where_key] = value
860
+ return ret
861
+
862
+ def storeValue(self, par_name, value, source):
863
+ specifiers = self._manager.specifiers(par_name)
864
+
865
+ # Watch the changes of sources and delete value from previous
866
+ # source
867
+ if self._optionSources.get(par_name, None) != source:
868
+ if par_name in self \
869
+ and JoinSources not in specifiers:
870
+ del self[par_name]
871
+ self._optionSources[par_name] = source
872
+
873
+ if JoinSources in specifiers or Multiple in specifiers:
874
+ # Value can have multiple values, so append it to the list of
875
+ # values
876
+ if par_name not in self:
877
+ self[par_name] = []
878
+ lst = self[par_name]
879
+ lst.append(value)
880
+ else:
881
+ # Single value, store it in option tree
882
+ self[par_name] = value
883
+
884
+ class Extractor(PythonEgg):
885
+ """Rozhraní extraktoru sloužící třídě `Script`
886
+
887
+ Umožňuje třídě `Script` implementující rozhraní `ExternalAdapter` používat
888
+ více zdrojů voleb. Každý zdroj voleb je reprezentován jednou třídou
889
+ implementující rozhraní `Extractor`.
890
+ """
891
+ def getSource(self):
892
+ """Vrátí používaný zdroj voleb
893
+
894
+ Není-li nastaven, vrátí ``None``.
895
+
896
+ :See:
897
+ `Script.getSources`
898
+ """
899
+
900
+ def setSource(self, source):
901
+ """Nastaví používaný zdroj voleb.
902
+
903
+ Pro použití implicitního zdroje, použijte ``source = None``.
904
+
905
+ :See:
906
+ `Script.setSources`
907
+ """
908
+
909
+ def getSourceName(self):
910
+ """Vrátí jméno zdroje
911
+
912
+ Jménem zdroje je každý `Extractor` reprezentován v rámci objektu
913
+ `Script`. Umožňuje hromadné nastavování používaných zdrojů.
914
+
915
+ :See:
916
+ `Script.setSources`
917
+ """
918
+
919
+ def extract(self, state):
920
+ """Naplní seznam voleb
921
+
922
+ :See:
923
+ `OptionStack.popObjects`
924
+ """
925
+
926
+ def setManager(self, manager):
927
+ """Umožní extraktoru získat odkaz na instanci `OptionManager`
928
+ """
929
+
930
+ def getHelpForOptions(self):
931
+ """Vrátí slovník s nápovědou pro parametry od tohoto extractoru
932
+ """
933
+
934
+ def getHelpForExtractor(self):
935
+ """Vrátí řetězec - nápovědu tohoto extractoru
936
+ """
937
+
938
+
939
+ class SimpleScript(ParametrizedObject):
940
+ """Třída reprezentující skript v jazyce Python
941
+
942
+ Pro napsání vlastního skriptu je třeba odvodit potomka této třídy a v něm
943
+ předefinovat minimálně následující atributy a metody: `main` a `options`.
944
+
945
+ Chybí-li některý parametr určující chování extraktoru, je nahrazen
946
+ odpovídající prázdnou hodnotou.
947
+
948
+ :Ivariables:
949
+ - `main` - hlavní metoda skriptu. Více viz `ParametrizedObject`.
950
+ - `options` - atribut typu ``dict`` specifikující popis jednotlivých
951
+ voleb skriptu. Více viz třída `OptionManager`.
952
+ - `shortOpts` -atributy popisující chování extraktoru
953
+ `extractors.CmdlineExtractor`. Atribut `shortOpts` je slovník, jehož
954
+ klíče jsou *jednopísmené zkrácené volby*, hodnoty pak jméno
955
+ odpovídající volby v nezkráceném tvaru.
956
+ - `posOpts` - atributy popisující chování extraktoru
957
+ `extractors.CmdlineExtractor`. `posOpts` pak popisuje *poziční*
958
+ volby předané na příkazové řádce. Jde o seznam jmen jednotlivých
959
+ voleb.
960
+ - `envPrefix` - atribut sloužící pro vytvoření extraktoru
961
+ `extractors.EnvironExtractor`. Seznam určující prefixy, jež lze
962
+ připojit před jméno volby. Takto se získá jméno, jež se hledá mezi
963
+ proměnnými prostředí.
964
+ - `pyFiles` - atributy pro konstruktor extraktoru
965
+ `extractors.PyFileExtractor`. Seznam jmen konfiguračních souborů. Ve
966
+ jménech jsou znaky tildy (``~``) nahrazeny domácím adresářem
967
+ uživatele.
968
+ - `pyFilesGlobals` - atributy pro konstruktor extraktoru
969
+ `extractors.PyFileExtractor`. Slovník definující globální prostor
970
+ jmen pro spouštění skriptů `pyFiles`.
971
+ - `debug` - je-li ``True``, nebudou použity handlery pro obsluhu chyb a
972
+ výjimky budou vypisovány interpretrem v nezkráceném tvaru.
973
+ - `debugMain` - je-li ``True``, nebude použit handler pro obsluhu chyb
974
+ v hlavní funkci a výjimky budou vypisovány interpretrem v nezkráceném
975
+ tvaru.
976
+ - `_extractors` - seznam používaných extraktorů
977
+ - `_manager` - odkaz na používaný `OptionManager`
978
+ """
979
+ debug = False
980
+ debugMain = False
981
+
982
+ options = {}
983
+ optionsDoc = {}
984
+
985
+ shortOpts = {}
986
+ posOpts = []
987
+ envPrefix = None
988
+ pyFiles = []
989
+ pyFilesGlobals = None
990
+
991
+ def __init__(self, sources={}):
992
+ super(SimpleScript, self).__init__()
993
+
994
+ self.createExtractors(**self.extractorsArgs)
995
+ self.setSources(sources)
996
+
997
+ self.manager = OptionManager(**self.managerArgs)
998
+
999
+ def getExtractorsArgs(self):
1000
+ ex_args = {}
1001
+ if hasattr(self, 'short_opts'):
1002
+ raise AttributeError("Please, use shortOpts instead of short_opts")
1003
+ if hasattr(self, 'pos_opts'):
1004
+ raise AttributeError("Please, use posOpts instead of pos_opts")
1005
+ if hasattr(self, 'env_prefix'):
1006
+ raise AttributeError("Please, use envPrefix instead of env_prefix")
1007
+ if hasattr(self, 'pyfiles'):
1008
+ raise AttributeError("Please, use pyFiles instead of pyfiles")
1009
+ if hasattr(self, 'pyfiles_globals'):
1010
+ raise AttributeError("Please, use pyFilesGlobals instead of pyfiles_globals")
1011
+ ex_args['short_opts'] = self.shortOpts.copy()
1012
+ ex_args['pos_opts'] = self.posOpts[:]
1013
+ ex_args['env_prefix'] = self.envPrefix
1014
+ ex_args['pyfiles'] = self.pyFiles
1015
+ ex_args['pyfiles_globals'] = self.pyFilesGlobals
1016
+ return ex_args
1017
+
1018
+ def getManagerArgs(self):
1019
+ oargs = {}
1020
+ oargs['specification'] = self.options.copy()
1021
+ oargs['docs'] = self.optionsDoc.copy()
1022
+ return oargs
1023
+
1024
+ def _extractionError(self, e):
1025
+ """Handler pro výpis chyb při získávání voleb
1026
+ """
1027
+ if self.debug: raise
1028
+ extractorName = ''
1029
+ if hasattr(e, 'extractorName'):
1030
+ extractorName = "%s: " % e.extractorName
1031
+ print >> sys.stderr, 'Script %s: %s%s' % \
1032
+ (self.__class__.__name__, extractorName, str(e))
1033
+ sys.exit(-1)
1034
+
1035
+ def _conversionError(self, e):
1036
+ """Handler pro výpis chyb při konverzi hodnot voleb na objekty
1037
+ """
1038
+ if self.debug: raise
1039
+ optionName = ''
1040
+ if hasattr(e, 'optionName'):
1041
+ optionName = " '%s'" % e.optionName
1042
+ print >> sys.stderr, 'Script %s: Bad option%s: %s: %s' % \
1043
+ (self.__class__.__name__, optionName, e.__class__.__name__, str(e))
1044
+ sys.exit(-1)
1045
+
1046
+ def _mainError(self, e):
1047
+ """Handler pro výpis chyb v hlavní funkci skriptu
1048
+ """
1049
+ if self.debugMain: raise
1050
+ if not isinstance(e, OptionError):
1051
+ print >> sys.stderr, 'Script %s: %s: %s' % \
1052
+ (self.__class__.__name__, e.__class__.__name__, str(e))
1053
+ else:
1054
+ # TODO: Move into self._extractionError()
1055
+ print >> sys.stderr, 'Script %s: %s' % \
1056
+ (self.__class__.__name__, str(e))
1057
+ sys.exit(-1)
1058
+
1059
+ def run(self, sources=None):
1060
+ """Vykoná skript, umožňuje nastavit zdroje
1061
+
1062
+ Pokud je `sources` různé od ``None``, provede se nejprve uchování
1063
+ starých zdrojů, následně se nastaví zdroje na `sources`, vykoná se
1064
+ hlavní funkce `main` a následně se obnoví zdroje na původní hodnotu.
1065
+
1066
+ :See:
1067
+ `getSources`, `setSources`
1068
+ """
1069
+ if sources is not None:
1070
+ old_sources = self.getSources()
1071
+ self.setSources(sources)
1072
+ else:
1073
+ old_sources = None
1074
+
1075
+ retval = super(SimpleScript, self).run()
1076
+
1077
+ if old_sources is not None:
1078
+ self.setSources(old_sources)
1079
+
1080
+ return retval
1081
+
1082
+ def createState(self):
1083
+ self.state = OptionStack(self.manager)
1084
+ self.state.disableAll()
1085
+
1086
+ self.extractOptions(self.state)
1087
+
1088
+ def getExtractors(self):
1089
+ """Vrátí seznam extraktorů
1090
+ """
1091
+ return self._extractors
1092
+
1093
+ def createExtractors(self, short_opts, pos_opts, env_prefix, pyfiles, pyfiles_globals):
1094
+ """Vytvoří extraktory
1095
+
1096
+ Implicitně vytváří následující extraktory (pomocí argumentů v závorce):
1097
+
1098
+ 1. `extractors.EnvironExtractor` (`env_prefix`) - extraktor pro
1099
+ získávání voleb z proměnných prostředí.
1100
+ 2. `extractors.PyFileExtractor` (`pyfiles_globals`, `pyfiles`) -
1101
+ extraktor pro získávání voleb z konfiguračních souborů v jazyce
1102
+ Python.
1103
+ 3. `extractors.CmdlineExtractor` (`short_opts`, `pos_opts`) -
1104
+ extraktor pro získávání voleb z argumentů předávaných na příkazové
1105
+ řádce.
1106
+
1107
+ Extraktory jsou uchovány v atributy `_extractors` a při volání metody
1108
+ `extractOptions` jsou použity v uvedeném pořadí.
1109
+ """
1110
+ from svc.scripting.extractors import CmdlineExtractor, EnvironExtractor, PyFileExtractor
1111
+ env = self._extractor_env = EnvironExtractor(env_prefix)
1112
+ pyfiles = self._extractor_pyfiles = PyFileExtractor(pyfiles_globals, pyfiles)
1113
+ argv = self._extractor_argv = CmdlineExtractor(short_opts, pos_opts)
1114
+ self._extractors = [env, pyfiles, argv]
1115
+
1116
+ def getSources(self):
1117
+ """Vrátí nastavené zdroje
1118
+
1119
+ Zdroje extraktorů s implicitní hodnotou (tj. `Extractor.getSource`
1120
+ vrátí ``None``) nejsou ve výsledku zahrnuty.
1121
+ """
1122
+ ret = {}
1123
+ for e in self.getExtractors():
1124
+ name = e.getSourceName()
1125
+ source = e.getSource()
1126
+ if source is not None:
1127
+ ret[name] = source
1128
+ return ret
1129
+
1130
+ def setSources(self, sources):
1131
+ """Nastaví zdroje používané extraktory
1132
+
1133
+ Nejprve jsou zdroje všech extraktorů nastaveny na implicitní hodotu
1134
+ (tj. ``None``) a poté jsou brány páry ``jméno:hodnota`` slovníku
1135
+ `sources` a extraktoru jehož `Extractor.getSourceName` vrátí ``jméno``
1136
+ je nastaven zdroj ``hodnota``.
1137
+ """
1138
+ extractors = {}
1139
+ for e in self.getExtractors():
1140
+ name = e.getSourceName()
1141
+ extractors[name] = e
1142
+ e.setSource(None)
1143
+ for name, source in sources.iteritems():
1144
+ e = extractors[name]
1145
+ e.setSource(source)
1146
+
1147
+ def getManager(self):
1148
+ """Vrátí používaní `OptionManager`
1149
+
1150
+ :See:
1151
+ `_manager`
1152
+ """
1153
+ return self._manager
1154
+
1155
+ def setManager(self, manager):
1156
+ """Nastaví používaný `OptionManager`
1157
+
1158
+ Tento `manager` ja rovněž nastaven všem extraktorům.
1159
+
1160
+ :See:
1161
+ `_manager`, `Extractor.setManager`
1162
+ """
1163
+ self._manager = manager
1164
+ for e in self.getExtractors():
1165
+ e.setManager(manager)
1166
+
1167
+ def extractOptions(self, state):
1168
+ """Vrátí seznam voleb ve formě řetězců
1169
+
1170
+ Prochází jednotlivé extraktory, volá jejich metody `Extractor.extract`
1171
+ a vrátí sjednocení jejich návratových hodnot.
1172
+
1173
+ :See:
1174
+ `OptionStack.popObjects`
1175
+ """
1176
+ for e in self.getExtractors():
1177
+ try:
1178
+ e.extract(state)
1179
+ except:
1180
+ exc = sys.exc_info()[1]
1181
+ exc.extractorName = e.getSourceName()
1182
+ raise
1183
+
1184
+ def getScriptFile(self):
1185
+ """Vrátí soubor, v němž je skript definován
1186
+ """
1187
+ #return inspect.getmodule(self).__file__
1188
+ return sys.argv[0]
1189
+
1190
+ def getHelpForFunc(self):
1191
+ """Vrátí řetězec s nápovědou pro asociovanou funkci
1192
+ """
1193
+ return self.main.__doc__
1194
+
1195
+ class Script(SimpleScript):
1196
+ _SCREEN_WIDTH = 80
1197
+
1198
+ def __init__(self, sources={}):
1199
+ super(Script, self).__init__(sources)
1200
+ self.createLogger()
1201
+
1202
+ def getExtractorsArgs(self):
1203
+ ex_args = super(Script, self).getExtractorsArgs()
1204
+ ex_args['short_opts'].update({
1205
+ 'v': 'verbose',
1206
+ 'h': 'help',
1207
+ })
1208
+ return ex_args
1209
+
1210
+ def getManagerArgs(self):
1211
+ m_args = super(Script, self).getManagerArgs()
1212
+ m_args['specification'].update({
1213
+ '__premain__.pyfile': (Multiple, String),
1214
+ '__premain__.logging.verbose_level': String,
1215
+ '__premain__.logging.verbose': (Multiple, Bool),
1216
+ '__premain__.help': Flag,
1217
+ '__premain__.debug': Flag,
1218
+ })
1219
+ m_args['docs'].update({
1220
+ 'pyfile': "Additional configuration file to include",
1221
+ 'verbose_level': "Precise verbose level (DEBUG, INFO, WARNING, ERROR)",
1222
+ 'verbose': "Verbose output",
1223
+ 'help': 'Prints help message',
1224
+ 'debug': 'Turn on printing of tracebacks',
1225
+ })
1226
+ return m_args
1227
+
1228
+ def premain(self, help=False, pyfile=[], logging={}, debug=False, **kwargs):
1229
+ # Setup Script logging system
1230
+ self.setupLogger(**logging)
1231
+
1232
+ if help:
1233
+ self.printHelp()
1234
+ sys.exit()
1235
+
1236
+ if debug:
1237
+ self.debug = self.debugMain = True
1238
+
1239
+ # Add extra PyFiles to the PyFileExtractor instance and extend current
1240
+ # state with extracted values
1241
+ orig_source = self._extractor_pyfiles.source
1242
+ if orig_source is None:
1243
+ orig_source = []
1244
+ self._extractor_pyfiles.source = orig_source + pyfile
1245
+ self._extractor_pyfiles.extract(self.state)
1246
+
1247
+ return super(Script, self).premain(**kwargs)
1248
+
1249
+ def createLogger(self):
1250
+ self.logger = logging.getLogger(self.__class__.__name__)
1251
+ self.logger.addHandler(logging.StreamHandler(sys.stderr))
1252
+
1253
+ def setupLogger(self, verbose_level=logging.WARNING, verbose=[]):
1254
+ try:
1255
+ verbose_level = int(verbose_level)
1256
+ except ValueError:
1257
+ verbose_level = verbose_level.upper()
1258
+ try:
1259
+ verbose_level = logging._levelNames[verbose_level]
1260
+ except KeyError:
1261
+ raise ValueError("Unknown verbose_level: %r" % verbose_level)
1262
+ for i in verbose:
1263
+ if i: verbose_level -= 10
1264
+ verbose_level = max(verbose_level, 1)
1265
+ self.logger.setLevel(verbose_level)
1266
+
1267
+ def _getMainOptions(self):
1268
+ script_params = self.manager.params()
1269
+ script_params -= self.manager.paramsChildren('__premain__')
1270
+ script_options = [self.manager.paramToOption(p) for p in script_params]
1271
+ return script_options
1272
+
1273
+ def _getPremainOptions(self):
1274
+ other_params = self.manager.paramsChildren('__premain__')
1275
+ other_options = [self.manager.paramToOption(p) for p in other_params]
1276
+ return other_options
1277
+
1278
+ def printHelp(self):
1279
+ m = HelpManager(self.manager, self.extractors, screenWidth=self._SCREEN_WIDTH)
1280
+
1281
+ script_file = os.path.basename(self.scriptFile)
1282
+
1283
+ m.printUsage(script_file, self.posOpts, self.__class__)
1284
+
1285
+ script_options = self._getMainOptions()
1286
+ m.printHelpDictOptionsHdr(script_options, 'Options', newline=True)
1287
+
1288
+ other_options = self._getPremainOptions()
1289
+ if other_options:
1290
+ m.printHelpDictOptionsHdr(other_options, 'Other options')
1291
+
1292
+ class ExScript(Script):
1293
+ """Třída pro výkoné skripty v jazyce Python
1294
+
1295
+ Automaticky umožňuje práci s logováním a více příkazy.
1296
+
1297
+ :Ivariables:
1298
+ - `defaultCommand` - výchozí příkaz, pokud nejsou specifikovány jiné
1299
+ příkazy
1300
+ """
1301
+ CommandParam = (Multiple, sym('_CommandParam'), String)
1302
+ defaultCommand = None
1303
+
1304
+ def __init__(self, *args, **kwargs):
1305
+ self._cmdPosOpts = {}
1306
+ super(ExScript, self).__init__(*args, **kwargs)
1307
+
1308
+ def createExtractors(self, **kwargs):
1309
+ from svc.scripting.extractors import CmdPosOptsExtractor
1310
+ super(ExScript, self).createExtractors(**kwargs)
1311
+ cmdPos = self._extractor_cmdPos = CmdPosOptsExtractor(self)
1312
+ self._extractors.append(cmdPos)
1313
+
1314
+ def setCmdPosOpts(self, cmdPosOpts):
1315
+ self._cmdPosOpts = cmdPosOpts.copy()
1316
+
1317
+ def getCmdPosOpts(self):
1318
+ return self._cmdPosOpts
1319
+
1320
+ def _modifyPosOpts(self, pos_opts):
1321
+ has_cpo = False
1322
+ ret = []
1323
+ for i in pos_opts:
1324
+ if isinstance(i, dict):
1325
+ if has_cpo:
1326
+ raise ValueError('You can use only one command specific positional option part')
1327
+ has_cpo = True
1328
+ self.cmdPosOpts = i
1329
+ ret.append('_command_pos_opts')
1330
+ ret.append(Ellipsis)
1331
+ elif i is Ellipsis and has_cpo:
1332
+ raise ValueError("You can't use Ellipsis together with command specific positional options")
1333
+ else:
1334
+ ret.append(i)
1335
+ return ret
1336
+
1337
+ def getExtractorsArgs(self):
1338
+ args = super(ExScript, self).getExtractorsArgs()
1339
+ args['pos_opts'] = self._modifyPosOpts(args['pos_opts'])
1340
+ return args
1341
+
1342
+ def getManagerArgs(self):
1343
+ m_args = super(ExScript, self).getManagerArgs()
1344
+ m_args['specification'].update({
1345
+ '__premain__._command_pos_opts': (Multiple, String),
1346
+ })
1347
+ return m_args
1348
+
1349
+ def getCommandOption(self):
1350
+ command_options = self.manager.paramsWithSpecifier('_CommandParam')
1351
+ if len(command_options) != 1:
1352
+ raise ValueError("There must be exactly one ExScript.CommandParam option")
1353
+ return command_options.pop()
1354
+
1355
+ def getCommandValue(self):
1356
+ commandOption = self.commandOption
1357
+ enabled = self.state.enabled
1358
+ try:
1359
+ self.state.disableAll()
1360
+ self.state.enable([commandOption])
1361
+ try:
1362
+ return self.state.getObjects()[commandOption]
1363
+ except KeyError:
1364
+ # TODO: Change type of exception
1365
+ raise ValueError('Command not specified')
1366
+ finally:
1367
+ self.state.disableExcept(enabled)
1368
+
1369
+ def premain(self, _command_pos_opts=[], **kwargs):
1370
+ cont = super(ExScript, self).premain(**kwargs)
1371
+ # Enable all parameters, which will be passed to main() method ...
1372
+ self.state.enableAll()
1373
+ # ... and disable parameters, which belongs to any command
1374
+ for command in self.commands:
1375
+ params = self.state.manager.paramsChildren(command)
1376
+ self.state.disable(params)
1377
+ return cont
1378
+
1379
+ def main(self, **kwargs):
1380
+ commands = []
1381
+ if self.defaultCommand:
1382
+ commands.append(self.defaultCommand)
1383
+ # Get commands and invoke this commands via invokeCommand()
1384
+ command_parameters = self.manager.paramsWithSpecifier('_CommandParam')
1385
+ if command_parameters:
1386
+ new_commands = []
1387
+ for param in command_parameters:
1388
+ if param in kwargs:
1389
+ new_commands.extend(kwargs.pop(param))
1390
+ if new_commands:
1391
+ commands[:] = new_commands
1392
+ if kwargs:
1393
+ raise TypeError("Not supported main() parameters: %s" % ', '.join(kwargs.keys()))
1394
+ if commands:
1395
+ for c in commands:
1396
+ self.invokeCommand(c)
1397
+
1398
+ def _methodForCommand(self, command):
1399
+ method = getattr(self, command, None)
1400
+ if not self.isCommand(method):
1401
+ raise ValueError("Unknown command %r" % command)
1402
+ return method
1403
+
1404
+ def invokeCommand(self, command, **kwargs):
1405
+ method = self._methodForCommand(command)
1406
+ params = self.manager.paramsChildren(command)
1407
+
1408
+ self.state.enable(params)
1409
+ self.state.disable('%s.%s' % (command, arg) for arg in kwargs)
1410
+ params_values = self.state.getObjects()
1411
+ self.state.disable(params)
1412
+
1413
+ method_kwargs = params_values.get(command, {})
1414
+ method_kwargs.update(kwargs)
1415
+
1416
+ return method(**method_kwargs)
1417
+
1418
+ @staticmethod
1419
+ def command(func):
1420
+ func._ExScript_command = True
1421
+ return func
1422
+
1423
+ @staticmethod
1424
+ def isCommand(func):
1425
+ return getattr(func, '_ExScript_command', False)
1426
+
1427
+ def getCommands(self):
1428
+ ret = set()
1429
+ for class_ in self.__class__.__mro__:
1430
+ for name, func in vars(class_).iteritems():
1431
+ if self.isCommand(func):
1432
+ ret.add(name)
1433
+ return ret
1434
+
1435
+ def _getMainOptions(self):
1436
+ script_params = self.manager.params()
1437
+ script_params -= self.manager.paramsChildren('__premain__')
1438
+ for command in self.commands:
1439
+ script_params -= self.manager.paramsChildren(command)
1440
+ script_options = [self.manager.paramToOption(p) for p in script_params]
1441
+ return script_options
1442
+
1443
+ def printHelp(self):
1444
+ m = HelpManager(self.manager, self.extractors, screenWidth=self._SCREEN_WIDTH)
1445
+
1446
+ script_file = os.path.basename(self.scriptFile)
1447
+ m.printUsage(script_file, self.posOpts, self.__class__)
1448
+
1449
+ script_options = self._getMainOptions()
1450
+ m.printHelpDictOptionsHdr(script_options, 'Options', newline=True)
1451
+
1452
+ if self.commands:
1453
+ m.printHeader('Commands')
1454
+ for command in sorted(self.commands):
1455
+ method = self._methodForCommand(command)
1456
+ m.printHelpForCommand(command, method)
1457
+
1458
+ other_options = [o for o in self._getPremainOptions() if not o.startswith('_')]
1459
+ if other_options:
1460
+ m.printHelpDictOptionsHdr(other_options, 'Other options')
1461
+