aboutsummaryrefslogtreecommitdiff
blob: dcb0803ce9d8b11e4daa7cdfca0f78b51c30013c (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
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
# R overlay -- description fields
# Copyright 2006-2012 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2

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.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() )

		return None

	# --- 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 )

		return None

	# --- 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() )
		return None

	# --- 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; currently this is limited to
		                 'withcase' : alias is case sensitive,
		                 'nocase'   : alias is case insensitive
		                any other type leads to an error

		raises: KeyError if alias_type unknown.
		"""

		to_add = dict (
			withcase = alias,
			nocase   = alias.lower(),
		) [alias_type]


		if not alias_type in self.aliases:
			self.aliases [alias_type] = list ()

		self.aliases [alias_type] . append ( to_add )

		return None

	# --- 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 bool ( 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:
			lowval = value.lower()
			for allowed in self.allowed_values:
				if allowed.lower() == lowval:
					return True

		else:
			return bool ( value in self.allowed_values )

		return False

	# --- end of value_allowed (...) ---

# --- 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.

		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 [field_name] if field_name in self.fields else 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 _field_search ( self ):
		"""Scans all stored DescriptionField(s) and creates fast-accessible
		data to be used in get_fields_with_<sth> (...).
		"""
		flagmap   = dict ()
		optionmap = dict (
			defaults       = dict (),
			allowed_values = list ()
		)

		for field_name in self.fields.keys():
			d = self.fields [field_name].default_value
			if not d is None:
				optionmap ['defaults'] [field_name] = d

			if self.fields [field_name].allowed_values:
				optionmap ['allowed_values'].append ( field_name )

			for flag in self.fields [field_name].flags:
				if not flag in flagmap:
					flagmap [flag] = list ()
				flagmap [flag].append ( field_name )

		self._fields_by_flag   = flagmap
		self._fields_by_option = optionmap
		return None

	# --- end of _field_search (...) ---

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

		arguments:
		* flag --
		* force_update -- force recreation of data
		"""
		if force_update or self._fields_by_flag is None:
			self._field_search ()

		flag = flag.lower()

		if flag in self._fields_by_flag:
			return self._fields_by_flag [flag]
		else:
			return []

	# --- end of get_fields_with_flag (...) ---

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

		arguments:
		* option --
		* force_update -- force recreation of data
		"""
		if force_update or self._fields_by_option is None:
			self._field_search ()

		if option in self._fields_by_option:
			return self._fields_by_option [option]
		else:
			return []

	# --- end of get_field_with_option (...) ---

	def get_fields_with_default_value ( self, force_update=False ):
		"""Returns a dict { '<field name>' -> '<default value>' } for all
		fields that have a default value.

		arguments:
		* force_update -- force recreation of data
		"""
		return self.get_fields_with_option ( 'defaults', force_update )

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

	def get_fields_with_allowed_values ( self, force_update=False ):
		"""Returns a set { <field name> } for all fields that allow only
		certain values.

		arguments:
		* force_update -- force recreation of data
		"""
		return self.get_fields_with_option ( 'allowed_values', force_update )

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

# --- end of DescriptionFields ---