josh-gmail-backup 0.104

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+