Gentoo Archives: gentoo-commits

From: "André Erdmann" <dywi@×××××××.de>
To: gentoo-commits@l.g.o
Subject: [gentoo-commits] proj/R_overlay:master commit in: roverlay/
Date: Fri, 01 Jun 2012 15:47:05
Message-Id: 1338562569.ac0b226fd7d16dad7a4b8d30b4293fe401406c1c.dywi@gentoo
1 commit: ac0b226fd7d16dad7a4b8d30b4293fe401406c1c
2 Author: André Erdmann <dywi <AT> mailerd <DOT> de>
3 AuthorDate: Fri Jun 1 14:56:09 2012 +0000
4 Commit: André Erdmann <dywi <AT> mailerd <DOT> de>
5 CommitDate: Fri Jun 1 14:56:09 2012 +0000
6 URL: http://git.overlays.gentoo.org/gitweb/?p=proj/R_overlay.git;a=commit;h=ac0b226f
7
8 2012-01-06:
9 * description field definition is now configurable
10
11 modified: roverlay/__init__.py
12 modified: roverlay/config.py
13 new file: roverlay/const.py
14 modified: roverlay/descriptionfields.py
15 renamed: roverlay/fileio.py -> roverlay/descriptionreader.py
16 modified: roverlay/ebuild.py
17 modified: roverlay/ebuildjob.py
18 deleted: roverlay/tmpconst.py
19
20 ---
21 roverlay/__init__.py | 5 +
22 roverlay/config.py | 164 ++++++++++++-
23 roverlay/const.py | 51 ++++
24 roverlay/descriptionfields.py | 343 +++++++++++++++++++++++---
25 roverlay/{fileio.py => descriptionreader.py} | 248 ++++++-------------
26 roverlay/ebuild.py | 5 +-
27 roverlay/ebuildjob.py | 15 +-
28 roverlay/tmpconst.py | 108 --------
29 8 files changed, 615 insertions(+), 324 deletions(-)
30
31 diff --git a/roverlay/__init__.py b/roverlay/__init__.py
32 index b1557ef..f50cec9 100644
33 --- a/roverlay/__init__.py
34 +++ b/roverlay/__init__.py
35 @@ -4,6 +4,11 @@
36
37 import logging
38
39 +
40 +from roverlay import config
41 +
42 +config.access().load_field_definition ( 'description_fields.conf' )
43 +
44 logging.basicConfig (
45 level=logging.DEBUG,
46 filename='roverlay.log',
47
48 diff --git a/roverlay/config.py b/roverlay/config.py
49 index 248fc22..b0bf7a3 100644
50 --- a/roverlay/config.py
51 +++ b/roverlay/config.py
52 @@ -3,20 +3,53 @@
53 # Distributed under the terms of the GNU General Public License v2
54
55 import sys
56 -
57 -from roverlay import descriptionfields
58 +import shlex
59
60 try:
61 import configparser
62 -except ImportError:
63 +except ImportError as running_python2:
64 + # configparser is named ConfigParser in python2
65 import ConfigParser as configparser
66
67 +
68 +
69 +
70 +from roverlay import descriptionfields
71 +from roverlay import const
72 +
73 +
74 +
75 +
76 def access():
77 + """Returns the ConfigTree."""
78 return ConfigTree() if ConfigTree.instance is None else ConfigTree.instance
79 +# --- end of access (...) ---
80 +
81 +
82 +def get ( key, fallback_value=None ):
83 + """Searches for key in the ConfigTree and returns its value if possible,
84 + else fallback_value.
85 + 'key' is a config path [<section>[.<subsection>*]]<option name>.
86 +
87 + arguments:
88 + * key --
89 + * fallback_value --
90 + """
91 + if fallback_value:
92 + return access().get ( key, fallback_value )
93 + else:
94 + return access().get ( key )
95 +# --- end of get (...) ---
96 +
97
98 class InitialLogger:
99
100 def __init__ ( self ):
101 + """Initializes an InitialLogger.
102 + It implements the debug/info/warning/error/critical/exception methods
103 + known from the logging module and its output goes directly to sys.stderr.
104 + This can be used until the real logging has been configured.
105 + """
106 self.debug = lambda x : sys.stderr.write ( "DBG " + str ( x ) + "\n" )
107 self.info = lambda x : sys.stderr.write ( "INFO " + str ( x ) + "\n" )
108 self.warning = lambda x : sys.stderr.write ( "WARN " + str ( x ) + "\n" )
109 @@ -24,11 +57,23 @@ class InitialLogger:
110 self.critical = lambda x : sys.stderr.write ( "CRIT " + str ( x ) + "\n" )
111 self.exception = lambda x : sys.stderr.write ( "EXC! " + str ( x ) + "\n" )
112
113 + # --- end of __init__ (...) ---
114 +
115 class ConfigTree:
116 # static access to the first created ConfigTree
117 instance = None
118
119 - def __init__ ( self ):
120 + def __init__ ( self, import_const=True ):
121 + """Initializes an ConfigTree, which is a container for options/config values.
122 + values can be stored directly (such as the field_definitions) or in a
123 + tree-like { section -> subsection[s] -> option = value } structure.
124 + Config keys cannot contain dots because they're used as config path
125 + separator.
126 +
127 + arguments:
128 + * import_const -- whether to deepcopy constants into the config tree or
129 + not. Copying allows faster lookups.
130 + """
131 if ConfigTree.instance is None:
132 ConfigTree.instance = self
133
134 @@ -36,8 +81,52 @@ class ConfigTree:
135
136 self.parser = dict()
137
138 + self._config = const.clone() if import_const else None
139 + self._const_imported = import_const
140 + self._field_definitions = None
141 +
142 + # --- end of __init__ (...) ---
143 +
144 +
145 + def get ( self, key, fallback_value=None ):
146 + """Searches for key in the ConfigTree and returns its value.
147 + Searches in const if ConfigTree does not contain the requested key and
148 + returns the fallback_value if key not found.
149 +
150 + arguments:
151 + * key --
152 + * fallback_value --
153 + """
154 + if self._config:
155 + config_path = key.split ( '.' )
156 + config_path.reverse ()
157 +
158 + config_position = self._config
159 + while len ( config_path ) and config_position:
160 + next_key = config_path.pop ()
161 + if next_key in config_position:
162 + config_position = config_position [next_key]
163 + else:
164 + config_position = None
165 +
166 + if config_position:
167 + return config_position
168 +
169 + if self._const_imported:
170 + return fallback_value
171 + else:
172 + return const.lookup ( key, fallback_value )
173 +
174 + # --- end of get (...) ---
175
176 def load_field_definition ( self, def_file, lenient=False ):
177 + """Loads a field definition file. Please see the example file for format
178 + details.
179 +
180 + arguments:
181 + * def_file -- file (str) to read, this can be a list of str if lenient is True
182 + * lenient -- if True: do not fail if a file cannot be read; defaults to False
183 + """
184 if not 'field_def' in self.parser:
185 self.parser ['field_def'] = configparser.SafeConfigParser ( allow_no_value=True )
186
187 @@ -57,4 +146,71 @@ class ConfigTree:
188 self.logger.exception ( mshe )
189 raise
190
191 + # --- end of load_field_definition (...) ---
192 +
193 +
194 + def get_field_definition ( self, force_update=False ):
195 + """Gets the field definition stored in this ConfigTree.
196 +
197 + arguments:
198 + * force_update -- enforces recreation of the field definition data.
199 + """
200 + if force_update or not self._field_definitions:
201 + self._field_definitions = self._make_field_definition ()
202 +
203 + return self._field_definitions
204 +
205 + # --- end of get_field_definition (...) ---
206 +
207 +
208 + def _make_field_definition ( self ):
209 + """Creates and returns field definition data. Please see the example
210 + field definition config file for details.
211 + """
212 +
213 + def get_list ( value_str ):
214 + if value_str is None:
215 + return []
216 + else:
217 + l = value_str.split ( ', ' )
218 + return [ e for e in l if e.strip() ]
219 +
220 + if not 'field_def' in self.parser:
221 + return None
222 +
223 + fdef = descriptionfields.DescriptionFields ()
224 +
225 + for field_name in self.parser ['field_def'].sections():
226 + field = descriptionfields.DescriptionField ( field_name )
227 + for option, value in self.parser ['field_def'].items ( field_name, 1 ):
228 +
229 + if option == 'alias' or option == 'alias_withcase':
230 + for alias in get_list ( value ):
231 + field.add_simple_alias ( alias, True )
232 +
233 + elif option == 'alias_nocase':
234 + for alias in get_list ( value ):
235 + field.add_simple_alias ( alias, False )
236 +
237 + elif option == 'default_value':
238 + field.set_default_value ( value )
239 +
240 + elif option == 'allowed_value':
241 + field.add_allowed_value ( value )
242 +
243 + elif option == 'allowed_values':
244 + for item in get_list ( value ):
245 + field.add_allowed_value ( item )
246 +
247 + elif option == 'flags':
248 + for flag in get_list ( value ):
249 + field.add_flag ( flag )
250 + else:
251 + # treat option as flag
252 + field.add_flag ( option )
253 +
254 + fdef.add ( field )
255 +
256 + return fdef
257
258 + # --- end of _make_field_definition (...) ---
259
260 diff --git a/roverlay/const.py b/roverlay/const.py
261 new file mode 100644
262 index 0000000..32630cf
263 --- /dev/null
264 +++ b/roverlay/const.py
265 @@ -0,0 +1,51 @@
266 +# R Overlay -- constants
267 +# Copyright 2006-2012 Gentoo Foundation
268 +# Distributed under the terms of the GNU General Public License v2
269 +
270 +import copy
271 +import time
272 +
273 +_CONSTANTS = dict (
274 + DESCRIPTION = dict (
275 + field_separator = ':',
276 + comment_char = '#',
277 + list_split_regex = '\s*[,;]{1}\s*',
278 + file_name = 'DESCRIPTION',
279 + ),
280 + R_PACKAGE = dict (
281 + suffix_regex = '[.](tgz|tbz2|tar|(tar[.](gz|bz2)))',
282 + name_ver_separator = '_',
283 + ),
284 + EBUILD = dict (
285 + indent = '\t',
286 + default_header = [ '# Copyright 1999-' + str ( time.gmtime() [0] ) + ' Gentoo Foundation',
287 + '# Distributed under the terms of the GNU General Public License v2',
288 + '# $Header: $',
289 + '',
290 + 'EAPI=4',
291 + '',
292 + 'inherit R-packages'
293 + ],
294 + )
295 +)
296 +
297 +def lookup ( key, fallback_value=None ):
298 + path = key.split ( '.' )
299 + path.reverse ()
300 +
301 + const_position = _CONSTANTS
302 +
303 + while len ( path ) and const_position:
304 + next_key = path.pop ()
305 + if next_key in const_position:
306 + const_position = const_position [next_key]
307 + else:
308 + const_position = None
309 +
310 + if const_position:
311 + return const_position
312 + else:
313 + return fallback_value
314 +
315 +def clone ( ):
316 + return copy.deepcopy ( _CONSTANTS )
317
318 diff --git a/roverlay/descriptionfields.py b/roverlay/descriptionfields.py
319 index 9c028c2..72175cb 100644
320 --- a/roverlay/descriptionfields.py
321 +++ b/roverlay/descriptionfields.py
322 @@ -2,39 +2,95 @@
323 # Copyright 2006-2012 Gentoo Foundation
324 # Distributed under the terms of the GNU General Public License v2
325
326 -
327 -# split from tmpconst / fileio to make configuration possible, but TODO
328 -
329 class DescriptionField:
330 + """Configuration for a field in the R package description file."""
331
332 def __init__ ( self, name ):
333 + """Initializes a DescriptionField with a valid(!) name.
334 +
335 + arguments:
336 + * name -- name of the field, has to be True (neither empty nor None)
337 +
338 + raises: Exception if name not valid
339 + """
340 +
341 if not name:
342 raise Exception ( "description field name is empty." )
343
344 self.name = name
345
346 + # --- end of __init__ (...) ---
347
348
349 def get_name ( self ):
350 + """Returns the name of this DescriptionField."""
351 return self.name
352
353 - def add_flag ( self, flag, lowercase=True ):
354 - if not hasattr ( self, flags ):
355 + # --- end of get_name (...) ---
356 +
357 +
358 + def add_flag ( self, flag ):
359 + """Adds a flag to this DescriptionField. Flags are always stored in
360 + their lowercase form.
361 +
362 + arguments:
363 + * flag -- name of the flag
364 + """
365 + if not hasattr ( self, 'flags' ):
366 self.flags = set ()
367
368 - self.flags.add ( flag, flag.lower() if lowercase else flag )
369 + self.flags.add ( flag.lower() )
370
371 return None
372
373 + # --- end of add_flag (...) ---
374 +
375 +
376 + def add_allowed_value ( self, value ):
377 + """Adds an allowed value to this DescriptionField, which creates a
378 + value whitelist for it. You can later check if a value is allowed using
379 + value_allowed (<value> [, <case insensitive?>]).
380 +
381 + arguments:
382 + * value -- allowed value
383 + """
384 +
385 + if not hasattr ( self, 'allowed_values' ):
386 + self.allowed_values = set ()
387 +
388 + self.allowed_values.add ( value )
389 +
390 + return None
391 +
392 + # --- end of add_allowed_value (...) ---
393 +
394
395 def del_flag ( self, flag ):
396 - if hasattr ( self, flags ):
397 - self.flags.discard ( flag )
398 + """Removes a flag from this DescriptionField. Does nothing if the flag
399 + does not exist.
400 + """
401 + if hasattr ( self, 'flags' ):
402 + self.flags.discard ( flag.lower() )
403 return None
404
405 + # --- end of del_flag (...) ---
406 +
407
408 def add_alias ( self, alias, alias_type='withcase' ):
409 - if not hasattr ( self, aliases ):
410 + """Adds an alias for this DescriptionField's name. This can also be used
411 + to combine different fields ('Description' and 'Title') or to fix
412 + typos ('Depend' -> 'Depends').
413 +
414 + arguments:
415 + * alias -- alias name
416 + * alias_type -- type of the alias; currently this is limited to
417 + 'withcase' : alias is case sensitive,
418 + 'nocase' : alias is case insensitive
419 + any other type leads to an error
420 +
421 + raises: KeyError if alias_type unknown.
422 + """
423 + if not hasattr ( self, 'aliases' ):
424 self.aliases = dict ()
425
426 to_add = dict (
427 @@ -50,37 +106,83 @@ class DescriptionField:
428
429 return None
430
431 + # --- end of add_alias (...) ---
432
433
434 def add_simple_alias ( self, alias, withcase=True ):
435 - if withcase:
436 - return self.add_alias ( alias, alias_type='withcase' )
437 - else:
438 - return self.add_alias ( alias, alias_type='nocase' )
439 + """Adds an alias to this DescriptionField. Its type is either withcase
440 + or nocase. See add_alias (...) for details.
441 +
442 + arguments:
443 + alias --
444 + withcase -- if True (the default): alias_type is withcase, else nocase
445 +
446 + raises: KeyError (passed from add_alias (...))
447 + """
448 + return self.add_alias ( alias, ( 'withcase' if withcase else 'nocase' ) )
449
450 + # --- end of add_simple_alias (...) ---
451
452
453 def get_default_value ( self ):
454 - if hasattr ( self, 'default_value' ):
455 - return self.default_value
456 - else:
457 - return None
458 + """Returns the default value for this DescriptionField if it exists,
459 + else None.
460 + """
461 + return self.default_value if hasattr ( self, 'default_value' ) else None
462
463 + # --- end of get_default_value (...) ---
464 +
465 +
466 + def set_default_value ( self, value ):
467 + """Sets the default value for this this DescriptionField.
468 +
469 + arguments:
470 + * value -- new default value
471 + """
472 + self.default_value = value
473 +
474 + # --- end of set_default_value (...) ---
475 +
476 +
477 + def get_flags ( self ):
478 + """Returns the flags of this DescriptionField or an empty list (=no flags)."""
479 + return self.flags if hasattr ( self, 'flags' ) else []
480 +
481 + # --- end of get_flags (...) ---
482 +
483 +
484 + def get_allowed_values ( self ):
485 + """Returns the allowed values of this DescriptionField or an empty list,
486 + which should be interpreted as 'no value restriction'.
487 + """
488 + return self.allowed_values if hasattr ( self, 'allowed_values' ) else []
489 +
490 + # --- end of get_allowed_values (...) ---
491
492 - def get ( self, key, fallback_value=None ):
493 - if hasattr ( self, key ):
494 - return self.key
495 - else:
496 - return fallback_value
497
498 def matches ( self, field_identifier ):
499 + """Returns whether field_identifier equals the name of this DescriptionField.
500 +
501 + arguments:
502 + * field_identifier --
503 + """
504 return bool ( self.name == field_identifier ) if field_identifier else False
505
506 + # --- end of matches (...) ---
507 +
508 +
509 def matches_alias ( self, field_identifier ):
510 + """Returns whether field_identifier equals any alias of this DescriptionField.
511 +
512 + arguments:
513 + * field_identifier --
514 + """
515
516 if not field_identifier:
517 + # bad identifier
518 return False
519 - if not hasattr ( self, aliases ):
520 + elif not hasattr ( self, aliases ):
521 + # no aliases
522 return False
523
524 if 'withcase' in self.aliases:
525 @@ -92,30 +194,211 @@ class DescriptionField:
526 if field_id_lower in self.aliases ['nocase']:
527 return True
528
529 - def has_flag ( self, flag, lowercase=True ):
530 - if not hasattr ( self, flags ):
531 + # --- end of matches_alias (...) ---
532 +
533 +
534 + def has_flag ( self, flag ):
535 + """Returns whether this DescriptionField has the given flag.
536 +
537 + arguments:
538 + * flag --
539 + """
540 + if not hasattr ( self, 'flags' ):
541 return False
542
543 - return bool ( (flag.lower() if lowercase else flag) in self.flags )
544 + return bool ( flag.lower() in self.flags )
545 +
546 + def value_allowed ( self, value, nocase=True ):
547 + """Returns whether value is allowed for this DescriptionField.
548 +
549 + arguments:
550 + * value -- value to check
551 + * nocase -- if True (the default): be case insensitive
552 + """
553 + allowed_values = self.get_allowed_values ()
554 +
555 + if not allowed_values:
556 + return True
557 + elif nocase:
558 + lowval = value.lower()
559 + for allowed in allowed_values:
560 + if allowed.lower() == lowval:
561 + return True
562 +
563 + else:
564 + return bool ( value in allowed_values )
565 +
566 + return False
567 +
568 + # --- end of has_flag (...) ---
569 +
570 +# --- end of DescriptionField ---
571 +
572
573 class DescriptionFields:
574 + """DescriptionFields stores several instances of DescriptionField and provides
575 + 'search in all' methods such as get_fields_with_flag (<flag>).
576 + """
577
578 def __init__ ( self ):
579 - fields = dict ()
580 + """Initializes an DescriptionFields object."""
581 + self.fields = dict ()
582 + # result 'caches'
583 + ## flag -> [<fields>]
584 + self._fields_by_flag = None
585 + ## option -> [<fields>]
586 + self._fields_by_option = None
587 +
588 + # --- end of __init__ (...) ---
589 +
590
591 def add ( self, desc_field ):
592 + """Adds an DescriptionField. Returns 1 desc_field was a DescriptionField
593 + and has been added as obj ref, 2 if a new DescriptionField with
594 + name=desc_field has been created and added and 0 if this was not
595 + possible.
596 +
597 + arguments:
598 + * desc_field -- this can either be a DescriptionField or a name.
599 + """
600 if desc_field:
601 if isinstance ( desc_field, DescriptionField ):
602 - fields [desc_field.get_name()] = desc_field
603 + self.fields [desc_field.get_name()] = desc_field
604 return 1
605 elif isinstance ( desc_field, str ):
606 - fields [desc_field] = DescriptionField ( desc_field )
607 + self.fields [desc_field] = DescriptionField ( desc_field )
608 return 2
609
610 return 0
611
612 + # --- end of add (...) ---
613 +
614 +
615 def get ( self, field_name ):
616 + """Returns the DescriptionField to which field_name belongs to.
617 + This method does, unlike others in DescriptionFields, return a
618 + reference to the matching DescriptionField object, not the field name!
619 + Returns None if field_name not found.
620 +
621 + arguments:
622 + * field_name --
623 + """
624 +
625 return self.fields [field_name] if field_name in self.fields else None
626
627 - # ... TODO
628 + # --- end of get (...) ---
629 +
630 +
631 + def find_field ( self, field_name ):
632 + """Determines the name of the DescriptionField to which field_name belongs
633 + to. Returns the name of the matching field or None.
634 +
635 + arguments:
636 + * field_name --
637 + """
638 +
639 + field = get ( field_name )
640 + if field is None:
641 + for field in self.fields:
642 + if field.matches_alias ( field_name ):
643 + return field.get_name ()
644 + else:
645 + return field.get_name ()
646 +
647 + # --- end of find_field (...) ---
648 +
649 +
650 + def _field_search ( self ):
651 + """Scans all stored DescriptionField(s) and creates fast-accessible
652 + data to be used in get_fields_with_<sth> (...).
653 + """
654 + flagmap = dict ()
655 + optionmap = dict (
656 + defaults = dict (),
657 + allowed_values = set ()
658 + )
659 +
660 + for field_name in self.fields.keys():
661 +
662 + d = self.fields [field_name].get_default_value()
663 + if not d is None:
664 + optionmap ['defaults'] [field_name] = d
665 +
666 + if self.fields [field_name].get_allowed_values():
667 + optionmap ['allowed_values'].add ( field_name )
668 +
669 + for flag in self.fields [field_name].get_flags():
670 + if not flag in flagmap:
671 + flagmap [flag] = set ()
672 + flagmap [flag].add ( field_name )
673 +
674 + self._fields_by_flag = flagmap
675 + self._fields_by_option = optionmap
676 + return None
677 +
678 + # --- end of _field_search (...) ---
679 +
680 +
681 + def get_fields_with_flag ( self, flag, force_update=False ):
682 + """Returns the names of the fields that have the given flag.
683 +
684 + arguments:
685 + * flag --
686 + * force_update -- force recreation of data
687 + """
688 + if force_update or self._fields_by_flag is None:
689 + self._field_search ()
690 +
691 + flag = flag.lower()
692 +
693 + if flag in self._fields_by_flag:
694 + return self._fields_by_flag [flag]
695 + else:
696 + return []
697 +
698 + # --- end of get_fields_with_flag (...) ---
699 +
700 +
701 + def get_fields_with_option ( self, option, force_update=False ):
702 + """Returns a struct with fields that have the given option. The actual
703 + data type depends on the requested option.
704 +
705 + arguments:
706 + * option --
707 + * force_update -- force recreation of data
708 + """
709 + if force_update or self._fields_by_option is None:
710 + self._field_search ()
711 +
712 + if option in self._fields_by_option:
713 + return self._fields_by_option [option]
714 + else:
715 + return []
716 +
717 + # --- end of get_field_with_option (...) ---
718 +
719 +
720 + def get_fields_with_default_value ( self, force_update=False ):
721 + """Returns a dict { '<field name>' -> '<default value>' } for all
722 + fields that have a default value.
723 +
724 + arguments:
725 + * force_update -- force recreation of data
726 + """
727 + return self.get_fields_with_option ( 'defaults', force_update )
728 +
729 + # --- end of get_fields_with_default_value (...) ---
730 +
731 +
732 + def get_fields_with_allowed_values ( self, force_update=False ):
733 + """Returns a set { <field name> } for all fields that allow only
734 + certain values.
735 +
736 + arguments:
737 + * force_update -- force recreation of data
738 + """
739 + return self.get_fields_with_option ( 'allowed_values', force_update )
740 +
741 + # --- end of get_fields_with_allowed_values (...) ---
742
743 +# --- end of DescriptionFields ---
744
745 diff --git a/roverlay/fileio.py b/roverlay/descriptionreader.py
746 similarity index 54%
747 rename from roverlay/fileio.py
748 rename to roverlay/descriptionreader.py
749 index 5c01527..2af4372 100644
750 --- a/roverlay/fileio.py
751 +++ b/roverlay/descriptionreader.py
752 @@ -1,4 +1,4 @@
753 -# R Overlay -- file in/out
754 +# R Overlay -- description reader
755 # Copyright 2006-2012 Gentoo Foundation
756 # Distributed under the terms of the GNU General Public License v2
757
758 @@ -7,21 +7,25 @@ import tarfile
759 import logging
760 import os.path
761
762 -
763 -# temporary import until config and real constants are implemented
764 -from roverlay import tmpconst as const
765 +from roverlay import config
766 +from roverlay import descriptionfields
767
768 class DescriptionReader:
769 """Description Reader"""
770
771 LOGGER = logging.getLogger ( 'DescriptionReader' )
772
773 +
774 def __init__ ( self, package_file, read_now=False ):
775 """Initializes a DESCRIPTION file reader."""
776
777 - self.fileinfo = self.make_fileinfo ( package_file )
778 - self.logger = DescriptionReader.LOGGER.getChild ( self.get_log_name() )
779 - self.desc_data = None
780 + if not config.access().get_field_definition():
781 + raise Exception ( "Field definition is missing, cannot initialize DescriptionReader." )
782 +
783 + self.field_definition = config.access().get_field_definition()
784 + self.fileinfo = self.make_fileinfo ( package_file )
785 + self.logger = DescriptionReader.LOGGER.getChild ( self.get_log_name() )
786 + self.desc_data = None
787
788
789 if read_now:
790 @@ -30,10 +34,13 @@ class DescriptionReader:
791 # --- end of __init__ (...) ---
792
793 def get_log_name ( self ):
794 + """Returns a logging name that can be used in other modules."""
795 try:
796 return self.fileinfo ['filename']
797 except Exception as any_exception:
798 return '__undef__'
799 + # --- end of get_log_name (...) ---
800 +
801
802 def get_desc ( self, run_if_unset=True ):
803 if self.desc_data is None:
804 @@ -58,10 +65,11 @@ class DescriptionReader:
805
806 package_file = os.path.basename ( filepath )
807
808 - filename = re.sub ( const.RPACKAGE_SUFFIX_REGEX + '$', '', package_file )
809 + filename = re.sub ( config.get ( 'R_PACKAGE.suffix_regex' ) + '$', '', package_file )
810
811 - # todo move that separator to const
812 - package_name, sepa, package_version = filename.partition ( '_' )
813 + package_name, sepa, package_version = filename.partition (
814 + config.get ( 'R_PACKAGE.name_ver_separator', '_' )
815 + )
816
817 if not sepa:
818 # file name unexpected, tarball extraction will (probably) fail
819 @@ -78,7 +86,6 @@ class DescriptionReader:
820
821 # --- end of make_fileinfo (...) ---
822
823 -
824 def _parse_read_data ( self, read_data ):
825 """Verifies and parses/fixes read data.
826
827 @@ -86,80 +93,42 @@ class DescriptionReader:
828 * read_data -- data from file, will be modified
829 """
830
831 - def get_fields_with_flag ( flag, foce_update=False ):
832 -
833 - matching_fields = []
834 -
835 - field = None
836 - for field in const.DESCRIPTION_FIELD_MAP.keys():
837 - if flag is None:
838 - matching_fields.append ( field )
839 -
840 - elif 'flags' in const.DESCRIPTION_FIELD_MAP [field]:
841 - if flag in const.DESCRIPTION_FIELD_MAP [field] ['flags']:
842 - matching_fields.append ( field )
843 -
844 - del field
845 - return matching_fields
846 -
847 - # --- end of get_fields_with_flag (...) ---
848 -
849 - def value_in_strlist ( _val, _list, case_insensitive=True ):
850 - """Returns true if value is in the given list."""
851 - el = None
852 - if case_insensitive:
853 - lowval = _val.lower()
854 - for el in _list:
855 - if el.lower() == lowval:
856 - return True
857 - del lowval
858 - else:
859 - for el in _list:
860 - if el == _val:
861 - return True
862 -
863 - del el
864 - return False
865 - # --- end of value_in_strlist (...) ---
866 -
867 - field = None
868
869 # insert default values
870 - for field in const.DESCRIPTION_FIELD_MAP.keys():
871 - if not field in read_data and 'default_value' in const.DESCRIPTION_FIELD_MAP [field]:
872 - read_data [field] = const.DESCRIPTION_FIELD_MAP [field] ['default_value']
873 + default_values = self.field_definition.get_fields_with_default_value()
874 + for field_name in default_values.keys():
875 + if not field_name in read_data:
876 + read_data [field_name] = default_values [field_name]
877 +
878
879 # join values to a single string
880 - for field in get_fields_with_flag ( 'joinValues' ):
881 - if field in read_data.keys():
882 - read_data [field] = ' ' . join ( read_data [field] )
883 + for field_name in self.field_definition.get_fields_with_flag ( 'joinValues' ):
884 + print ( "?, ".join ( [ field_name, 'join', str ( read_data ) ] ) )
885 + if field_name in read_data:
886 + read_data [field_name] = ' ' . join ( read_data [field_name] )
887
888 # ensure that all mandatory fields are set
889 - missing_fields = list()
890 + missing_fields = set ()
891
892 - for field in get_fields_with_flag ( 'mandatory' ):
893 - if field in read_data:
894 - if not len (read_data [field]):
895 - missing_fields.append ( field )
896 + for field_name in self.field_definition.get_fields_with_flag ( 'mandatory' ):
897 + if field_name in read_data:
898 + if read_data [field_name] is None or len ( read_data [field_name] ) < 1:
899 + missing_fields.add ( field_name )
900 + #else: ok
901 else:
902 - missing_fields.append ( field )
903 -
904 -
905 + missing_fields.add ( field_name )
906
907
908 # check for fields that allow only certain values
909 - unsuitable_fields = dict()
910 + unsuitable_fields = set()
911
912 - for field in read_data.keys():
913 - if 'allowed_values' in const.DESCRIPTION_FIELD_MAP [field]:
914 - if not value_in_strlist (
915 - read_data [field],
916 - const.DESCRIPTION_FIELD_MAP [field] ['allowed_values']
917 - ):
918 - unsuitable_fields.append [field] = read_data [field]
919 -
920 - del field
921 + restricted_fields = self.field_definition.get_fields_with_allowed_values()
922 + for field_name in restricted_fields:
923 + if field_name in read_data:
924 + if not self.field_definition.get ( field_name ).value_allowed ( read_data [field_name] ):
925 + unsuitable_fields.add ( field_name )
926
927 + # summarize results
928 valid = not bool ( len ( missing_fields ) or len ( unsuitable_fields ) )
929 if not valid:
930 self.logger.info ( "Cannot use R package" ) # name?
931 @@ -200,27 +169,6 @@ class DescriptionReader:
932 multiple values arranged in a list (dep0, dep1 [, depK]*).
933 """
934
935 - def check_fieldflag ( field, flag_to_check=None ):
936 - """Checks if the given field has the specified flag and returns a bool.
937 -
938 - arguments:
939 - * field -- name of the field that should be checked
940 - * flag_to_check -- name of the flag to check; optional, defaults to None
941 -
942 - This method acts as 'field has any flags?' if flag_to_check is None (its default value).
943 - """
944 -
945 - if field in const.DESCRIPTION_FIELD_MAP:
946 - if 'flags' in const.DESCRIPTION_FIELD_MAP [field]:
947 - if flag_to_check in const.DESCRIPTION_FIELD_MAP [field] ['flags']:
948 - return True
949 - elif flag_to_check is None:
950 - # 'flags' exist, return true
951 - return True
952 -
953 - return False
954 - # --- end of check_fieldflag (...) ---
955 -
956 svalue_str = value_str.strip()
957
958 if not svalue_str:
959 @@ -231,16 +179,17 @@ class DescriptionReader:
960 # default return if no context given
961 return [ svalue_str ]
962
963 - elif check_fieldflag ( field_context ):
964 - # value str is not empty and have flags for field_context, check these
965 -
966 - if check_fieldflag ( field_context, 'isList' ):
967 - # split up this list (that is separated by commata and/or semicolons)
968 - return re.split (const.DESCRIPTION_LIST_SPLIT_REGEX, svalue_str, 0)
969 + elif field_context in self.field_definition.get_fields_with_flag ( 'isList' ):
970 + # split up this list (that is separated by commata and/or semicolons)
971 + return re.split (
972 + config.get ( 'DESCRIPTION.list_split_regex' ),
973 + svalue_str,
974 + 0
975 + )
976
977 - elif check_fieldflag ( field_context, 'isWhitespaceList' ):
978 - # split up this list (that is separated whitespace)
979 - return re.split ( '\s+', svalue_str, 0 )
980 + elif field_context in self.field_definition.get_fields_with_flag ( 'isWhitespaceList' ):
981 + # split up this list (that is separated whitespace)
982 + return re.split ( '\s+', svalue_str, 0 )
983
984
985 # default return
986 @@ -275,9 +224,12 @@ class DescriptionReader:
987 # filepath is a tarball, open tar handle + file handle
988 th = tarfile.open ( filepath, 'r' )
989 if pkg_name:
990 - fh = th.extractfile ( os.path.join ( pkg_name, const.DESCRIPTION_FILE_NAME ) )
991 + fh = th.extractfile ( os.path.join (
992 + pkg_name,
993 + config.get ( 'DESCRIPTION.file_name' )
994 + ) )
995 else:
996 - fh = th.extractfile ( const.DESCRIPTION_FILE_NAME )
997 + fh = th.extractfile ( config.get ( 'DESCRIPTION.file_name' ) )
998
999 # have to decode the lines
1000 read = lambda lines : [ line.decode().rstrip() for line in lines ]
1001 @@ -298,60 +250,9 @@ class DescriptionReader:
1002
1003 # --- end of get_desc_from_file (...) ---
1004
1005 - def find_field ( field_identifier ):
1006 - """Determines the real name of a field.
1007 -
1008 - arguments:
1009 - * field_identifier -- name of the field as it appears in the DESCRIPTION file
1010 -
1011 - At first, it is checked whether field_identifier matches the name of
1012 - a field listed in DESCRIPTION_FIELD_MAP (any match results in immediate return).
1013 - Then, a new iteration over the field map compares field_identifier
1014 - with all aliases until the first case-(in)sensitive match (-> immediate return).
1015 - None will be returned if none of the above searches succeed.
1016 -
1017 - In other words: this method decides whether a field_identifier will be used and if so,
1018 - with which name.
1019 - """
1020 -
1021 - # save some time by prevent searching if field_id is empty
1022 - if not field_identifier:
1023 - return None
1024 -
1025 - # search for real field names first
1026 - for field in const.DESCRIPTION_FIELD_MAP.keys():
1027 - if field_identifier == field:
1028 - return field
1029 -
1030 - field_id_lower = field_identifier.lower()
1031 -
1032 - for field in const.DESCRIPTION_FIELD_MAP.keys():
1033 -
1034 - # does extra information (-> alias(es)) for this field exist?
1035 - if 'alias' in const.DESCRIPTION_FIELD_MAP [field]:
1036 -
1037 - if 'withcase' in const.DESCRIPTION_FIELD_MAP [field] ['alias']:
1038 - for alias in const.DESCRIPTION_FIELD_MAP [field] ['alias'] ['withcase']:
1039 - if field_identifier == alias:
1040 - return field
1041 -
1042 - if 'nocase' in const.DESCRIPTION_FIELD_MAP [field] ['alias']:
1043 - for alias in const.DESCRIPTION_FIELD_MAP [field] ['alias'] ['nocase']:
1044 - if field_id_lower == alias.lower():
1045 - return field
1046 -
1047 - #if 'other_alias_type' in const.DESCRIPTION_FIELD_MAP [field] ['alias']:
1048 -
1049 - # returning None if no valid field identifier matches
1050 - return None
1051 -
1052 - # --- end of find_field (...) ---
1053 -
1054 -
1055 self.desc_data = None
1056 read_data = dict ()
1057
1058 -
1059 try:
1060 desc_lines = get_desc_from_file (
1061 self.fileinfo ['filepath'],
1062 @@ -362,14 +263,15 @@ class DescriptionReader:
1063 self.logger.exception ( err )
1064 return self.desc_data
1065
1066 + field_context = None
1067
1068 - field_context = val = line = sline = None
1069 for line in desc_lines:
1070 + field_context_ref = None
1071
1072 # using s(tripped)line whenever whitespace doesn't matter
1073 sline = line.lstrip()
1074
1075 - if (not sline) or (line [0] == const.DESCRIPTION_COMMENT_CHAR):
1076 + if (not sline) or (line [0] == config.get ( 'DESCRIPTION.comment_char' ) ):
1077 # empty line or comment
1078 pass
1079
1080 @@ -389,14 +291,26 @@ class DescriptionReader:
1081 # line introduces a new field context, forget last one
1082 field_context = None
1083
1084 - line_components = sline.partition ( const.DESCRIPTION_FIELD_SEPARATOR )
1085 + line_components = sline.partition ( config.get ( 'DESCRIPTION.field_separator' ) )
1086
1087 if line_components [1]:
1088 # line contains a field separator, set field context
1089 - field_context = find_field ( line_components [0] )
1090 + field_context_ref = self.field_definition.get ( line_components [0] )
1091 +
1092 + if field_context_ref is None:
1093 + # useless line, skip
1094 + self.logger.info ( "Skipped a description field: '%s'.", line_components [0] )
1095 + elif field_context_ref.has_flag ( 'ignore' ):
1096 + # field ignored
1097 + self.logger.debug ( "Ignored field '%s'.", field_context )
1098 +
1099 + else:
1100 + field_context = field_context_ref.get_name()
1101 +
1102 + if not field_context:
1103 + raise Exception ( "Field name is not valid! This should've already been catched in DescriptionField..." )
1104
1105 - if field_context:
1106 - # create a new empty list for field_context
1107 + # create a new empty list for this field_context
1108 read_data [field_context] = []
1109
1110 # add values to read_data
1111 @@ -404,9 +318,7 @@ class DescriptionReader:
1112 for val in make_values ( line_components [2], field_context ):
1113 read_data [field_context] . append ( val )
1114
1115 - else:
1116 - # useless line, skip
1117 - self.logger.info ( "Skipped a description field: '%s'.", line_components [0] )
1118 +
1119
1120 else:
1121 # reaching this branch means that
1122 @@ -415,11 +327,7 @@ class DescriptionReader:
1123 # this should not occur in description files (bad syntax?)
1124 self.logger.warning ( "Unexpected line in description file: '%s'.", line_components [0] )
1125
1126 -
1127 - del line_components
1128 -
1129 - del sline, line, val, field_context
1130 -
1131 + # -- end for --
1132
1133 if self._parse_read_data ( read_data ):
1134 self.logger.debug ( "Successfully read file '%s' with data = %s.",
1135 @@ -430,4 +338,4 @@ class DescriptionReader:
1136 # get_desc() is preferred, but this method returns the desc data, too
1137 return self.desc_data
1138
1139 - # --- end of readfile (...) ---
1140 + # --- end of run (...) ---
1141
1142 diff --git a/roverlay/ebuild.py b/roverlay/ebuild.py
1143 index 1634ef3..4493bb7 100644
1144 --- a/roverlay/ebuild.py
1145 +++ b/roverlay/ebuild.py
1146 @@ -2,9 +2,10 @@
1147 # Copyright 2006-2012 Gentoo Foundation
1148 # Distributed under the terms of the GNU General Public License v2
1149
1150 +import roverlay.config
1151 +
1152 class Ebuild:
1153 - # could move this to const
1154 - EBUILD_INDENT = "\t"
1155 + EBUILD_INDENT = roverlay.config.get ( 'EBUILD.indent', '\t' )
1156
1157 ADD_REMAP = {
1158 # pkg vs package
1159
1160 diff --git a/roverlay/ebuildjob.py b/roverlay/ebuildjob.py
1161 index 0357e77..b6c9456 100644
1162 --- a/roverlay/ebuildjob.py
1163 +++ b/roverlay/ebuildjob.py
1164 @@ -2,16 +2,18 @@
1165 # Copyright 2006-2012 Gentoo Foundation
1166 # Distributed under the terms of the GNU General Public License v2
1167
1168 -import time
1169 import logging
1170 import re
1171
1172 -from roverlay.fileio import DescriptionReader
1173 +from roverlay.descriptionreader import DescriptionReader
1174 from roverlay.ebuild import Ebuild
1175 +from roverlay import config
1176
1177 class EbuildJob:
1178 LOGGER = logging.getLogger ( 'EbuildJob' )
1179
1180 + DEFAULT_EBUILD_HEADER = config.get ( 'EBUILD.default_header' )
1181 +
1182 # move this to const / config
1183 DEPENDENCY_FIELDS = {
1184 'R_SUGGESTS' : [ 'Suggests' ],
1185 @@ -142,14 +144,7 @@ class EbuildJob:
1186
1187 ## default ebuild header, could use some const here (eclass name,..)
1188 ebuild.add ( 'ebuild_header',
1189 - [ '# Copyright 1999-' + str ( time.gmtime() [0] ) + ' Gentoo Foundation',
1190 - '# Distributed under the terms of the GNU General Public License v2',
1191 - '# $Header: $',
1192 - '',
1193 - 'EAPI=4',
1194 - '',
1195 - 'inherit R-packages'
1196 - ],
1197 + EbuildJob.DEFAULT_EBUILD_HEADER,
1198 False
1199 )
1200
1201
1202 diff --git a/roverlay/tmpconst.py b/roverlay/tmpconst.py
1203 deleted file mode 100644
1204 index 8519aad..0000000
1205 --- a/roverlay/tmpconst.py
1206 +++ /dev/null
1207 @@ -1,108 +0,0 @@
1208 -# R overlay -- constants (temporary file)
1209 -# Copyright 2006-2012 Gentoo Foundation
1210 -# Distributed under the terms of the GNU General Public License v2
1211 -
1212 -# matches .tgz .tbz2 .tar .tar.gz .tar.bz2
1213 -RPACKAGE_SUFFIX_REGEX = '[.](tgz|tbz2|tar|(tar[.](gz|bz2)))'
1214 -
1215 -PACKAGE_CATEGORY = 'sci-R'
1216 -
1217 -DESCRIPTION_FIELD_SEPARATOR = ':'
1218 -
1219 -DESCRIPTION_COMMENT_CHAR = '#'
1220 -
1221 -DESCRIPTION_LIST_SPLIT_REGEX = '\s*[,;]{1}\s*'
1222 -
1223 -DESCRIPTION_FILE_NAME = 'DESCRIPTION'
1224 -
1225 -# moved to <field> -> 'allowed_values'
1226 -##DESCRIPTION_VALID_OS_TYPES = [ "unix" ]
1227 -
1228 -
1229 -# note for 2012-05-25: make this struct more organized, assign real values
1230 -"""The map of used fields in the DESCRIPTION file
1231 -
1232 - stores the real field name as well as field flags and aliases
1233 - that can be case-sensitive (withcase) or not (nocase)
1234 -
1235 - access to these values is
1236 - * for aliases
1237 - DESCRIPTION_FIELD_MAP [<field name>] [alias] [case sensitive ? withcase : nocase] [<index>]
1238 -
1239 - * for flags
1240 - DESCRIPTION_FIELD_MAP [<field name>] [flags] [<index>]
1241 -
1242 - * default values
1243 - DESCRIPTION_FIELD_MAP [<field name>] [default_value]
1244 -
1245 - notable flags:
1246 - * isList : indicates that this field has several values that are
1247 - separated by commata/semicolons =:<DESCRIPTION_LIST_SPLIT_REGEX>
1248 - this disables isWhitespaceList
1249 -
1250 - * isWhitespaceList : indicates that this field has several values separated
1251 - by whitespace
1252 -
1253 - * joinValues : indicates that the values of this field should be concatenated
1254 - after reading them (with a ' ' as separator)
1255 - (this implies that the read values are one string)
1256 -
1257 - * mandatory : cannot proceed if a file does not contain this field (implies ignoring default values)
1258 -
1259 -"""
1260 -
1261 -DESCRIPTION_FIELD_MAP = {
1262 - 'Description' : {
1263 - 'flags' : [ 'joinValues' ],
1264 - },
1265 - 'Title' : {
1266 - 'flags' : [ 'joinValues' ],
1267 - },
1268 - 'Package' : {
1269 - 'flags' : [ 'joinValues' ],
1270 - },
1271 - 'License' : {
1272 - 'flags' : [ 'isList' ],
1273 - },
1274 - 'Version' : {
1275 - 'flags' : [ 'mandatory', 'joinValues' ]
1276 - },
1277 - 'Suggests' : {
1278 - 'alias' : {
1279 - 'nocase' : [ 'Suggests', 'Suggest',
1280 - '%Suggests', 'Suggets', 'Recommends' ]
1281 - },
1282 - },
1283 - 'Depends' : {
1284 - 'alias' : {
1285 - 'nocase' : [ 'Depends', 'Dependencies', 'Dependes',
1286 - '%Depends', 'Depents', 'Require', 'Requires' ],
1287 - },
1288 - 'flags' : [ 'isList' ],
1289 - 'default_value' : '',
1290 - },
1291 - 'Imports' : {
1292 - 'alias' : {
1293 - 'nocase' : [ 'Imports', 'Import' ]
1294 - },
1295 - },
1296 - 'LinkingTo' : {
1297 - 'alias' : {
1298 - 'nocase' : [ 'LinkingTo', 'LinkingdTo' ]
1299 - },
1300 - },
1301 - 'SystemRequirements' : {
1302 - 'alias' : {
1303 - 'nocase' : [ 'SystemRequirements', 'SystemRequirement' ]
1304 - },
1305 - },
1306 - 'OS_Type' : {
1307 - 'alias' : {
1308 - 'nocase' : [ 'OS_TYPE' ]
1309 - },
1310 - 'allowed_values' : [ 'unix' ],
1311 - },
1312 - 'test-default' : {
1313 - 'default_value' : 'some default value'
1314 - }
1315 -}