aboutsummaryrefslogtreecommitdiff
blob: fedd1ccce4c26fe5651e2fc35cfc4c5671a8f8f0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
# R overlay -- rpackage, description fields
# -*- coding: utf-8 -*-
# Copyright (C) 2012 André Erdmann <dywi@mailerd.de>
# Distributed under the terms of the GNU General Public License;
# either version 2 of the License, or (at your option) any later version.

"""field definition objects"""

__all__ = [ 'DescriptionField', 'DescriptionFields', ]

class DescriptionField ( object ):
   """Configuration for a field in the R package description file."""

   def __init__ ( self, name ):
      """Initializes a DescriptionField with a valid(!) name.

      arguments:
      * name -- name of the field, has to be True (neither empty nor None)

      raises: Exception if name not valid
      """

      if not name:
         raise Exception ( "description field name is empty." )

      self.name = name

      self.early_value_validation = False

      self.default_value  = None
      self.flags          = list()
      self.allowed_values = list()
      self.aliases        = dict()

   # --- end of __init__ (...) ---

   def get_name ( self ):
      """Returns the name of this DescriptionField."""
      return self.name

   # --- end of get_name (...) ---

   def add_flag ( self, flag ):
      """Adds a flag to this DescriptionField. Flags are always stored in
      their lowercase form.

      arguments:
      * flag -- name of the flag
      """
      self.flags.append ( flag.lower() )

   # --- end of add_flag (...) ---

   def add_allowed_value ( self, value ):
      """Adds an allowed value to this DescriptionField, which creates a
      value whitelist for it. You can later check if a value is allowed using
      value_allowed (<value> [, <case insensitive?>]).

      arguments:
      * value -- allowed value
      """

      self.allowed_values.append ( value )

   # --- end of add_allowed_value (...) ---

   def del_flag ( self, flag ):
      """Removes a flag from this DescriptionField. Does nothing if the flag
      does not exist.
      """
      self.flags.discard ( flag.lower() )

   # --- end of del_flag (...) ---

   def add_alias ( self, alias, alias_type='withcase' ):
      """Adds an alias for this DescriptionField's name. This can also be used
      to combine different fields ('Description' and 'Title') or to fix
      typos ('Depend' -> 'Depends').

      arguments:
      * alias -- alias name
      * alias_type -- type of the alias
                       'nocase'   : alias is case insensitive
                       else       : alias is case sensitive

      """
      if alias_type == 'nocase':
         to_add = alias.lower()
      else:
         #assert alias_type == 'withcase'
         to_add = alias

      alias_list = self.aliases.get ( alias_type, None )

      if alias_list:
         alias_list.append ( to_add )
      else:
         self.aliases [alias_type] = [ to_add ]
   # --- end of add_alias (...) ---

   def add_simple_alias ( self, alias, withcase=True ):
      """Adds an alias to this DescriptionField. Its type is either withcase
      or nocase. See add_alias (...) for details.

      arguments:
      alias --
      withcase -- if True (the default): alias_type is withcase, else nocase

      raises: KeyError (passed from add_alias (...))
      """
      return self.add_alias ( alias, ( 'withcase' if withcase else 'nocase' ) )

   # --- end of add_simple_alias (...) ---

   def get_default_value ( self ):
      """Returns the default value for this DescriptionField if it exists,
      else None.
      """
      return self.default_value

   # --- end of get_default_value (...) ---

   def set_default_value ( self, value ):
      """Sets the default value for this this DescriptionField.

      arguments:
      * value -- new default value
      """
      self.default_value = value

   # --- end of set_default_value (...) ---

   def get_flags ( self ):
      """Returns the flags of this DescriptionField or
      an empty list (=no flags).
      """
      return self.flags

   # --- end of get_flags (...) ---

   def get_allowed_values ( self ):
      """Returns the allowed values of this DescriptionField or an empty list,
      which should be interpreted as 'no value restriction'.
      """
      return self.allowed_values

   # --- end of get_allowed_values (...) ---

   def matches ( self, field_identifier ):
      """Returns whether field_identifier equals the name of this field.

      arguments:
      * field_identifier --
      """
      if field_indentifier:
         return bool ( self.name == field_identifier )
      else:
         return False
   # --- end of matches (...) ---

   def matches_alias ( self, field_identifier ):
      """Returns whether field_identifier equals any alias of this field.

      arguments:
      * field_identifier --
      """

      if not field_identifier:
         # bad identifier
         return False

      if 'withcase' in self.aliases:
         if field_identifier in self.aliases ['withcase']:
            return True

      if 'nocase' in self.aliases:
         field_id_lower = field_identifier.lower()
         if field_id_lower in self.aliases ['nocase']:
            return True

      return False

   # --- end of matches_alias (...) ---

   def has_flag ( self, flag  ):
      """Returns whether this DescriptionField has the given flag.

      arguments:
      * flag --
      """
      return ( flag.lower() in self.flags )
   # --- end of has_flag (...) ---

   def value_allowed ( self, value, nocase=True ):
      """Returns whether value is allowed for this DescriptionField.

      arguments:
      * value -- value to check
      * nocase -- if True (the default): be case insensitive
      """

      if not self.allowed_values:
         return True
      elif nocase:
         return ( value.lower() in self.allowed_values_nocase )
      else:
         return ( value in self.allowed_values )
   # --- end of value_allowed (...) ---

   def configure ( self ):
      self.allowed_values = frozenset ( self.allowed_values )
      self.flags          = frozenset ( self.flags )

      if self.has_flag ( 'isLicense' ):
         self.early_value_validation = True
      elif self.allowed_values:
         self.allowed_values_nocase = frozenset (
            s.lower() for s in self.allowed_values
         )
   # --- end of update (...) ---

# --- end of DescriptionField ---


class DescriptionFields ( object ):
   """DescriptionFields stores several instances of DescriptionField and
   provides 'search in all' methods such as get_fields_with_flag (<flag>).
   """

   def __init__ ( self ):
      """Initializes an DescriptionFields object."""
      self.fields = dict ()
      # result 'caches'
      ## flag -> [<fields>]
      self._fields_by_flag   = None
      ## option -> [<fields>]
      self._fields_by_option = None

   # --- end of __init__ (...) ---

   def add ( self, desc_field ):
      """Adds an DescriptionField. Returns 1 desc_field was a DescriptionField
      and has been added as obj ref, 2 if a new DescriptionField with
      name=desc_field has been created and added and 0 if this was not
      possible.

      Note:
         update() has to be called after adding one or more fields.

      arguments:
      * desc_field -- this can either be a DescriptionField or a name.
      """
      if desc_field:
         if isinstance ( desc_field, DescriptionField ):
            self.fields [desc_field.get_name()] = desc_field
            return 1
         elif isinstance ( desc_field, str ):
            self.fields [desc_field] = DescriptionField ( desc_field )
            return 2

      return 0

   # --- end of add (...) ---

   def get ( self, field_name ):
      """Returns the DescriptionField to which field_name belongs to.
      This method does, unlike others in DescriptionFields, return a
      reference to the matching DescriptionField object, not the field name!
      Returns None if field_name not found.

      arguments:
      * field_name --
      """
      return self.fields.get ( field_name, None )
   # --- end of get (...) ---

   def find_field ( self, field_name ):
      """Determines the name of the DescriptionField to which field_name
      belongs to. Returns the name of the matching field or None.

      arguments:
      * field_name --
      """

      field = self.get ( field_name )
      if field is None:
         for field in self.fields:
            if field.matches_alias ( field_name ):
               return field.get_name ()
      else:
         return field.get_name ()

   # --- end of find_field (...) ---

   def update ( self ):
      """Scans all stored DescriptionField(s) and creates fast-accessible
      data to be used in get_fields_with_<sth> (...).

      Returns self (this object).
      """
      flagmap   = dict()
      optionmap = dict (
         defaults       = dict(),
         allowed_values = set()
      )

      for field_name, field in self.fields.items():
         field.configure()

         d = field.default_value
         if not d is None:
            optionmap ['defaults'] [field_name] = d

         if not field.early_value_validation and field.allowed_values:
            optionmap ['allowed_values'].add ( field_name )

         for flag in field.flags:
            if not flag in flagmap:
               flagmap [flag] = set()
            flagmap [flag].add ( field_name )

      self._fields_by_flag   = flagmap
      self._fields_by_option = optionmap

      return self
   # --- end of update (...) ---

   def get_fields_with_flag ( self, flag ):
      """Returns the names of the fields that have the given flag.

      arguments:
      * flag --
      """
      return self._fields_by_flag.get ( flag.lower(), () )
   # --- end of get_fields_with_flag (...) ---

   def get_fields_with_option ( self, option ):
      """Returns a struct with fields that have the given option. The actual
      data type depends on the requested option.

      arguments:
      * option --
      """
      return self._fields_by_option.get ( option, () )
   # --- end of get_field_with_option (...) ---

   def get_fields_with_default_value ( self ):
      """Returns a dict { '<field name>' -> '<default value>' } for all
      fields that have a default value.
      """
      return self.get_fields_with_option ( 'defaults' )

   # --- end of get_fields_with_default_value (...) ---

   def get_fields_with_allowed_values ( self ):
      """Returns a set { <field name> } for all fields that allow only
      certain values.
      """
      return self.get_fields_with_option ( 'allowed_values' )

   # --- end of get_fields_with_allowed_values (...) ---

# --- end of DescriptionFields ---