1
2
3
4
5
6
7
8
9
10
11
12 """
13 Main code template module.
14
15 Overview.
16 =========
17
18 Provide core functionality for code templates.
19
20 Tunning of this module may be prformed by using configuration files.
21 Default file is "I{codetpl_default.cfg}" that must not be changed.
22 User preferences may be added to custom configuration files named
23 "I{codetpl.cfg}" . Search for custom configuration files in all
24 templates folder and combined and override settings from las files.
25
26 Module have next objects:
27 - Exceptions: L{codetplError}, L{codetplArgumentError},
28 L{codetplTemplateException}.
29 - Main class L{CodeTemplateMngr} that provide core manager for templates.
30 - Variable L{ANY} value of which is equivalent any other value in
31 metainformation of templates.
32
33 X{CodeTemplateMngr overview}.
34 =============================
35
36 Use L{CodeTemplateMngr.HasType} to test type for exists.
37 Use L{CodeTemplateMngr.GetTemplates} to get available templates list.
38 Use L{CodeTemplateMngr.MetaNames}, L{CodeTemplateMngr.GetMetaInfo},
39 L{CodeTemplateMngr.GetMetaAllValues} to get metainformation about
40 templates.
41 Use L{CodeTemplateMngr.Types} to get available types of templates.
42 Use L{CodeTemplateMngr.GetConfig} to get preferences from
43 configuration files.
44 Use L{CodeTemplateMngr.ParseTemplate} to parse templates.
45
46 You can reload module for changest in files make effect by calling
47 L{CodeTemplateMngr.Reload}.
48
49 X{Configuration file (CodeTemplateMngr)}.
50 =========================================
51
52 The user configuration file example (it's get from codetpl_default)::
53
54 #!/usr/bin/env python
55 # -*- coding: utf-8 -*-
56 #--------------------------------------------------------------------
57 # Author: Mazhugin Aleksey
58 # Created: 2007/10/24
59 # ID: $Id: XPyTools.codetpl.codetpl-pysrc.html 24 2008-12-01 17:51:12Z alex $
60 # URL: $URL: file:///myfiles/svn_/XPyLIB/trunc/doc/html/XPyTools.codetpl.codetpl-pysrc.html $
61 # Copyright: Copyright (c) 2007, Mazhugin Aleksey
62 # License: BSD
63 #--------------------------------------------------------------------
64
65 # Default settings for Code templates.
66 #
67 # This config file for users folder.
68 # You may copy and rename it to you home
69 # "~/.xpylib/xpytools/codetpl/templates/codetpl.cfg"
70 # and edit configuration.
71
72 [Prefs]
73
74 # If true then will be loaded global templates from
75 # "XPyLIB/XPyTools/codetpl/templates/".
76 # If False when will be used only user templates.
77 UseGlobalTemplates = yes
78
79 #If true then reload templates files every tyme, else only
80 # at startup. Also with templates each time reload configuration
81 # file.
82 ReloadTemplates = yes
83
84 # Templates types.
85 # Preferences for each type see in [type_???] section.
86 # If type is not included then it will be not used.
87 types = act, new, wzd, kwd, tpl, wtp
88
89 # Prefix for types sections.
90 # Types sectins will be search into typesprefix??? where ???-type.
91 typesprefix = type_
92
93 # Meta information fields names.
94 MetaNames = category, language, author
95
96 # Any value equivalent
97 ANY = any
98
99 #If not empty then add to Tools menu with it name.
100 # TODO: Not releazed yet, add.
101 ToolMenuName = Code Template
102
103 [Template_paths]
104
105 ################################################################################
106 #Template paths.
107 ################################################################################
108
109 # Default path - same dir as config file.
110 path1 = .
111
112 # Additional paths.
113 #path2 =
114 #path3 =
115
116
117
118 ################################################################################
119 # Types settings
120 ################################################################################
121 #
122 # Name - Full name for template.
123 #
124 # Desc - Description for given type.
125 #
126 # HotKey - Hot key for call teplates from editor.
127 #
128 # MenuName - If exist and not empty then register in to menu Edit->... .
129 #
130 # Interface - Interface of template type. May be:
131 # - 'gui' - GUI interface (used by default).
132 # - 'list' - Standard editor list popup.
133 #
134
135 [type_act]
136 Name = Action
137 Desc = Action for code manipulation
138 HotKey = Alt+A
139 Interface = list
140 MenuName = Code Template Action
141
142 [type_kwd]
143 Name = Keywords
144 Desc = Any keywords
145 HotKey = Alt+K
146 Interface = list
147 MenuName = Code Template Keyword
148
149 [type_tpl]
150 Name = Code
151 Desc = Code templates
152 HotKey = Alt+T
153 Interface = list
154 MenuName = Code Template Template
155
156 [type_wzd]
157 Name = Wizard
158 Desc = Wizard for code creation.
159 HotKey =
160 Interface = gui
161 MenuName = Code Template Wizard
162
163 [type_new]
164 Name = New
165 Desc = Templates for new files
166 HotKey = Alt+Q
167 Interface = gui
168 MenuName = Code Tpl New
169
170 [type_wtp]
171 Name = XTemplate
172 Desc = Extended code templates.
173 HotKey =
174 Interface = gui
175 MenuName = Code Tpl Tpls wizard
176
177 @var ANY: Value that equivalent any other value (used in metainformation).
178 @type ANY: str
179
180 """
181
182 import sys
183 import os
184 import imp
185 import inspect
186 import glob
187 import re
188 import ConfigParser
189 import XPyLIB
190
191
192
193
194 ANY = 'any'
195
197 """
198 Code templates common exception.
199 """
200 pass
201
203 """
204 Code templates argument exception.
205 """
206 pass
207
209 """
210 Exception then executing template function.
211
212 Argument Err is Exception object.
213 """
214 pass
215
217 """
218 Manager of code templates.
219
220 Provide all functionalities with templates.
221 """
222
224 return self.__errs[:]
225
228
229
230
231 Errors = property(__get_Errors, None, __del_Errors)
232
233 @property
235 """
236 List of available templates types (copy, not original).
237 Type - list.
238 """
239 return self.__types[:]
240
241 @property
248
249 @property
251 """
252 Source file which editing in the editor.
253 Type - str = C{''}
254 """
255 return self.__srcfile
256
257 - def __init__(self, spath = [], usedefault = True):
258 """
259 Initialize class.
260
261 @param spath: List of absolute paths to templates folders. If empty string
262 (or C{False}) then set default path == "... /codetpl/templates/"
263 @type spath: list[string] = C{[]}
264 @param usedefault: If true then load global and user config files and
265 if allow use global templates. Otherwise use only path from L{spath}.
266 @type usedefault: bool = True
267 @raise codetplArgumentError: Template path does not exist. May raise
268 for default path or user specified.
269 @warning: Into initialization folder must be placed configuration
270 file B{C{codetpl.cfg}} else wil be used default values (see
271 module description).
272 """
273
274
275
276 self.__srcfile=''
277
278
279
280 self.__config=None
281
282
283
284 self.__mdls=[]
285
286
287
288
289 self.__kwds={}
290
291
292
293
294 self.__tpls={}
295
296
297
298
299 self.__tplsmeta={}
300
301
302
303
304 self.__errs=[]
305
306
307
308 self.__types=[]
309
310 if type(spath) == str:
311 spath=[spath]
312
313
314
315 self.__path = spath
316
317
318
319
320 self.__usedefault = usedefault
321
322
323
324 self.__metanames=['category', 'language']
325
326
327
328 self.__metare=[ re.compile(r'^#\s*' + n + r'\s*=\s*(?P<' + \
329 n + r'>.*)$', re.M) for n in self.__metanames ]
330
331 self.Reload()
332
333 - def GetConfig(self, section, option=None, default=''):
334 """
335 Return preferences from configuration files.
336
337 You may get option value or section items:
338 - Option.
339 If option not exist then return default value.
340 - Section.
341 For get section items you must leave I{option} parameter
342 empty or set to C{None}.
343 If no such section then return empty list [].
344
345 @param section: Section name.
346 @type section: str
347 @param option: Option name.
348 @type option: str=C{''}
349 @param default: Default value.
350 @type default: str=C{''}
351 @return: Configuration value or list of items for section.
352 @rtype: str or list[(name, value)]
353 """
354 if option == None:
355 return self.__config.has_section(section) and \
356 self.__config.items(section) or []
357 else:
358 return self.__config.has_section(section) and \
359 self.__config.has_option(section, option) and \
360 self.__config.get(section, option) or default
361
363 """
364 Re/Load template and config modules.
365
366 Clear previous loaded modules and reload all noe finded.
367 All loaded template modules into __mdls, naming is as "_codetpl_???_*".
368 Walk throw all loaded modules and fill __kwds and __tpls dictionaries.
369
370 @return: None.
371 @rtype: None
372 """
373
374
375 inspect.linecache.clearcache()
376
377
378 self.__kwds.clear()
379 self.__tpls.clear()
380 self.__errs=[]
381 self.__types=[]
382 self.__tplsmeta.clear()
383
384 self.__config = ConfigParser.SafeConfigParser()
385
386 if self.__usedefault:
387
388 defcfgpath = os.path.join( os.path.dirname(__file__), \
389 'templates', 'codetpl_default.cfg')
390 if os.path.exists(defcfgpath):
391
392 try:
393
394 fp=open(defcfgpath)
395 self.__config.readfp(fp)
396 except:
397
398 pass
399 finally:
400 if fp:
401 fp.close()
402
403
404 defcfgpath = XPyLIB.dir_get('xpytools/codetpl/templates', True, True)
405 defcfgpath = os.path.join( defcfgpath , 'codetpl.cfg')
406 if os.path.exists(defcfgpath):
407
408 try:
409
410 fp=open(defcfgpath)
411 self.__config.readfp(fp)
412 except:
413
414 pass
415 finally:
416 if fp:
417 fp.close()
418
419 if self.GetConfig('Prefs','UseGlobalTemplates','yes') == 'yes':
420 self.__path.append( os.path.join( os.path.dirname(__file__), 'templates') )
421
422
423
424 defcfgpath = [os.path.join( p, 'codetpl.cfg')
425 for p in self.__path]
426
427 self.__config.read(defcfgpath)
428
429
430 availtypes=[n.strip() for n in self.GetConfig('Prefs','types','').split(',')]
431 global ANY
432 ANY=self.GetConfig('Prefs','ANY','any')
433
434 mns=[n.strip() for n in self.GetConfig('Prefs','MetaNames','').split(',')]
435 for mn in mns:
436 if mn not in self.__metanames:
437 self.__metanames.append(mn)
438 self.__metare.append( re.compile(r'^#\s*' + mn + r'\s*=\s*(?P<' + \
439 mn + r'>.*)$', re.M) )
440
441
442
443
444
445 for f in self.__mdls:
446 try:
447
448 m=sys.modules.pop('_codetpl_' + f, 1)
449 except Exception, e:
450
451 pass
452 del m
453
454
455
456 self.__mdls=[]
457
458
459 cfgpath=[v for k,v in self.GetConfig('Template_paths')]
460
461
462 for folder in self.__path + cfgpath:
463
464 if not os.path.exists(folder):
465
466 continue
467
468
469 for f in glob.iglob(os.path.join(folder,'???_*.py')):
470
471 if not os.path.isdir(f):
472
473
474 n=os.path.splitext(os.path.basename(f))[0].lower()
475 pref=n[0:3]
476
477
478 if pref in availtypes:
479 try:
480
481 module = imp.load_source('_codetpl_' + n, f)
482 except Exception, error:
483
484
485 self.__errs.append(str(error) + \
486 '. (' + str(error.__class__) + ').')
487 else:
488
489
490
491
492
493
494 self.__mdls.append(n)
495
496 prefu=pref + '_'
497 for name, val in inspect.getmembers(module, \
498 lambda m: inspect.isfunction(m) and \
499 m.__name__.lower().startswith(prefu) ):
500
501 name=name.lower()
502 npref=name[0:3]
503
504
505 vcmnts=unicode(inspect.getcomments(val),errors='replace')
506
507
508 vdoc=unicode(inspect.getdoc(val),errors='replace')
509 if npref not in self.__types:
510
511 self.__types.append(npref)
512
513 md=[]
514 for i in range(len(self.__metanames)):
515 n=self.__metanames[i]
516
517 m=self.__metare[i].search(vcmnts)
518 if m:
519
520 md.append( (n, str(m.group(n)).strip() or '') )
521
522 self.__tplsmeta[name]=( vdoc, dict(md) )
523
524 if npref=='kwd':
525
526 self.__kwds[name[4:]]=val
527 else:
528
529 self.__tpls[name]=val
530
531
532
533
534
535
536
537 - def GetTemplates(self, type_, filter={}, retIfAbsent=False):
538 """
539 Get list of available templates given type.
540
541 C{type} must be one from L{C{Types()}<Types>}.
542 If no such type then raise L{codetplArgumentError}.
543
544 C{type} are case insensetive (converts to lower).
545
546 Filter contains dictionary {name:listofvalues} where listofvalues
547 may be tuple, list or other iterable or str (converted to single tuple).
548 Filter do only from given metanames other is always done.
549
550 @param type_: Type of templates.
551 @type type_: str
552 @param filter: Filter from metainformation fields.
553 @type filter: dict=C{empty}
554 @param retIfAbsent: If metanames from filter absent in templates then
555 if C{True} return this template, if C{False} then skip.
556 @type retIfAbsent: boolean=C{False}
557 @return: List of templates name for given C{type}.
558 @rtype: list
559 @raise codetplArgumentError: If no such type of template.
560 """
561 type_=type_.lower()
562 if not self.HasType(type_):
563
564
565 if type_ == '':
566 em = 'CodeTemplates: Templates not found.'
567 else:
568 em = 'CodeTemplates: Unknown template type: "' + type_ +'".'
569 sys.stderr.write(em)
570
571 return []
572 pref = type_ + '_'
573 if type_=='kwd':
574 tpls = self.__kwds.keys()
575 else:
576
577 tpls = [name[4:] for name in self.__tpls.keys()
578 if name.startswith(pref)]
579
580 ret=[]
581 for n in tpls:
582 if filter:
583
584 for k,v in filter.items():
585 if type(v) == str:
586 v=(v,)
587 if self.__tplsmeta[pref+n][1].has_key(k):
588
589 vv=self.__tplsmeta[pref+n][1][k]
590 if (ANY in v) or (vv in v) or (vv==ANY):
591
592 ret.append(n)
593 else:
594
595 if retIfAbsent:
596 ret.append(n)
597
598
599 else:
600 ret.append(n)
601
602
603
604
605
606 return ret
607
631
651
652 - def ParseTemplate(self, type, name, fields, srcfile='', params={}):
653 """
654 Parse template used given fields or return preview.
655
656 C{type} and C{name} are case insensetive (converts to lower).
657
658 If you get empty L{fields} then for all finded keywords will be
659 assigned it's name. It's used for preview template. For real parse
660 you may set into fields neded values.
661
662 @param type: Template type. See L{Types}.
663 @type type: str
664 @param name: Template name.
665 @type name: str
666 @param fields: Fields of keywods: {"name":"value"}. Changed by this
667 function to finded keywords.
668 @type fields: dict
669 @param srcfile: Source file, which editing into editor.
670 @type srcfile: str =C{''}
671 @param params: Additional parameters. (Usually for boa constructor its
672 'model' and 'view' objects).
673 @type params: dict = {}
674 @return: Parsed template, and changed fields (added new).
675 @rtype: str
676 """
677
678 if not isinstance(fields, dict):
679 raise codetplArgumentError, 'Argument "fields" must be a dict.'
680 if not self.HasType(type):
681 raise codetplArgumentError, 'Unknown template type.'
682 self.__srcfile=srcfile
683
684 type=type.lower()
685 name=name.lower()
686 if type=='kwd':
687 d=self.__kwds
688 n=name
689 else:
690 d=self.__tpls
691 n=type + '_' + name
692 if not d.has_key(n):
693 raise codetplArgumentError, 'No such template "' + n + '".'
694
695 try:
696 sys.modules[d[n].__module__].codetpl_sourcefile=self.__srcfile
697 sys.modules[d[n].__module__].codetpl_params=params
698
699 if type=='kwd':
700 tpl=d[n]()
701 else:
702 tpl=d[n](fields)
703 except Exception, err:
704 e=codetplTemplateException('Exception in template: ' + str(err))
705 e.Err=err
706 raise e
707
708 if type=='kwd':
709 return str(tpl)
710
711 tpl=self.ReplaceTemplate(tpl, fields, None, '@', False, srcfile, params)
712 return tpl
713
714 - def ReplaceTemplate(self, tpl, fields, kwds=None, brds='@', case=False, \
715 srcfile='', params={}):
716 """
717 Parse template C{tpl}. Change all C{fields} rounded borders
718 C{brd} to value of C{fields}. If fields not find then try
719 search into C{kwds}. If not find then use fields key as value.
720
721 @param tpl: Template string to parse with fields.
722 @type tpl: str
723 @param fields: Fields for search and replace into C{tpl}.
724 @type fields: dict
725 @param kwds: Keywords for search if not find into C{fields}.
726 - If C{None} then use instance L{__kwds}.
727 - If dict then its value may be:
728 - str - use directly.
729 - function - call with no arguments, must return string.
730 @type kwds: B{None}, str or dict{key: str or func()}
731 @param brds: Borders wrapped around keys.
732 Twice brds is parsed as single character 'brds'.
733 @type brds: str = '@'
734 @param case: If True then keys is casesensetive.
735 @type case: boolean = False
736 @param srcfile: Source file, which editing into editor.
737 @type srcfile: str =C{''}
738 @param params: Additional parameters. (Usually for boa constructor its
739 'model' and 'view' objects).
740 @type params: dict = {}
741 @return: Replaced template, and changed fields (added new).
742 @rtype: str
743 """
744
745 self.__srcfile=srcfile
746
747 if case:
748 flags=re.I | re.S
749 else:
750 flags=re.S
751 self.__ro=re.compile(r'(?<!'+brds+r')'+brds+r'\w+?' \
752 +brds+r'(?!'+brds+r')', flags)
753
754 if kwds==None:
755 kwds=self.__kwds
756
757 d=dict( [ (k.lower(), v) for k,v in fields.iteritems() ] )
758 dk=dict( [ (k.lower(), v) for k,v in kwds.iteritems() ] )
759
760
761 tpl=unicode(tpl,errors='replace')
762
763
764
765 F=[ k[1:-1] for k in self.__ro.findall(tpl) ]
766
767
768
769
770
771
772 f={}
773 for kk in F:
774 k=kk.lower()
775
776 if d.has_key(k):
777
778 r=unicode(d[k])
779
780
781 elif dk.has_key(k):
782
783 if callable(dk[k]):
784 sys.modules[dk[k].__module__].codetpl_sourcefile=self.__srcfile
785 sys.modules[dk[k].__module__].codetpl_params=params
786
787
788
789 r=unicode(dk[k]())
790
791 else:
792 r=str(dk[k])
793
794 else:
795 r=kk
796
797 f[k]=r
798 fields[kk]=r
799
800
801
802 r=tpl
803 ro=re.compile(r'@@',re.I | re.S)
804 r=ro.sub('@',r)
805 for k in f:
806 ro=re.compile(r'(?<!@)@'+k+r'@(?!@)',re.I | re.S)
807 r=ro.sub(f[k],r)
808
809 r=r.splitlines(True)
810 if r[0].strip()=='':
811 r.pop(0)
812 if r[-1].strip()=='':
813 r.pop(-1)
814 return ''.join(r)
815
817 """
818 Test - has such type or not.
819
820 It's more perfomance then use I{C{'type' in Types}}, becouse
821 it use inner fields, then return copy.
822
823 C{type} are case insensetive (converts to lower).
824
825 @param type_: Tested template type.
826 @type type_: str
827 @return: C{True} if has, else C{False}.
828 @rtype: boolean.
829 """
830 type_=type_.lower()
831 return type_ in self.__types
832
834 """
835 Get edited surce file.
836
837 @return: Source file path or C{''} if no.
838 @rtype: string
839 """
840 return self.__srcfile
841
842
843
844
845
846
851
852
853
854 if __name__=='__main__':
855 _debug()
856