Package pybaz :: Module _nameparser
[frames] | no frames]

Source Code for Module pybaz._nameparser

  1  # arch-tag: b9095457-7c52-43eb-9eab-0e7f7fbd87dc 
  2  # Copyright (C) 2003-2005  David Allouche <david@allouche.net> 
  3  #               2005 Canonical Limited 
  4  # 
  5  #    This program is free software; you can redistribute it and/or modify 
  6  #    it under the terms of the GNU General Public License as published by 
  7  #    the Free Software Foundation; either version 2 of the License, or 
  8  #    (at your option) any later version. 
  9  # 
 10  #    This program is distributed in the hope that it will be useful, 
 11  #    but WITHOUT ANY WARRANTY; without even the implied warranty of 
 12  #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 13  #    GNU General Public License for more details. 
 14  # 
 15  #    You should have received a copy of the GNU General Public License 
 16  #    along with this program; if not, write to the Free Software 
 17  #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 18   
 19   
 20  import re 
 21  import errors 
 22   
 23  __all__ = [ 
 24      'NameParser', 
 25      ] 
 26   
 27  from deprecation import deprecated_callable 
 28   
29 -def backend():
30 import pybaz as arch 31 return arch.backend
32
33 -def default_archive():
34 import pybaz as arch 35 return str(arch.default_archive())
36
37 -def factory():
38 import pybaz as arch 39 return arch.factory
40
41 -class ForkNameParser(str):
42 43 """Parse Arch names with with tla. 44 45 All operations run tla, this is generally too expensive for 46 practical use. Use NameParser instead, which is implemented 47 natively. 48 49 This class is retained for testing purposes. 50 """ 51
52 - def __valid_package_name(self, opt, tolerant=True):
53 opts = [ 'valid-package-name' ] 54 if self.startswith('-'): 55 return False 56 if tolerant: 57 opts.append('--tolerant') 58 if opt: 59 opts.append(opt) 60 opts.append(self) 61 # XXX: should be (0,1) but baz 1.3~200503300337 is buggy 62 return 0 == backend().status_cmd(opts, expected=(0,1,2))
63
64 - def __parse_package_name(self, opt):
65 opts = [ 'parse-package-name' ] 66 if self.startswith('-'): 67 return None 68 if opt: 69 opts.append(opt) 70 opts.append(self) 71 # XXX: should be (0,1) but baz 1.3~200503300337 is buggy 72 status, retval = backend().status_one_cmd(opts, expected=(0,1,2)) 73 if status != 0: 74 return None 75 return retval
76
77 - def is_category(self):
78 return self.__valid_package_name('--category', tolerant=False)
79 - def is_package(self):
80 return self.__valid_package_name('--package', tolerant=False)
81 - def is_version(self):
82 return self.__valid_package_name('--vsn', tolerant=False)
83 - def is_patchlevel(self):
84 return self.__valid_package_name('--patch-level', tolerant=False)
85
86 - def has_archive(self):
87 return self.__valid_package_name('--archive')
88 - def has_category(self):
89 return self.__valid_package_name('--category')
90 - def has_package(self):
91 return self.__valid_package_name('--package')
92 - def has_version(self):
93 return self.__valid_package_name('--vsn')
94 - def has_patchlevel(self):
95 return self.__valid_package_name('--patch-level')
96
97 - def get_archive(self):
98 return self.__parse_package_name('--arch')
99 - def get_nonarch(self):
100 return self.__parse_package_name('--non-arch')
101 - def get_category(self):
102 return self.__parse_package_name('--category')
103 - def get_branch(self):
104 return self.__parse_package_name('--branch')
105 - def get_package(self):
106 return self.__parse_package_name('--package')
107 - def get_version(self):
108 if not self.has_version(): return None # work around a tla bug 109 return self.__parse_package_name('--vsn')
110 - def get_package_version(self):
111 return self.__parse_package_name('--package-version')
112 - def get_patchlevel(self):
113 if not self.has_patchlevel(): return None # work around a tla bug 114 return self.__parse_package_name('--patch-level')
115 116
117 -class NameParser(str):
118 119 """Parser for names in Arch archive namespace. 120 121 Implements name parsing natively for performance reasons. It 122 should behave exactly as tla, any discrepancy is to be considered 123 a bug, unless tla is obviously buggy. 124 125 Bare names of archives, category, branch, versions ids, and 126 unqualified patchlevel names are not part of the archive 127 namespace. They can be validated using static methods. 128 129 :group Specificity level: is_category, is_package, is_version 130 131 :group Presence name components: has_archive, has_category, has_package, 132 has_version, has_revision, has_patchlevel 133 134 :group Getting name components: get_archive, get_nonarch, get_category, 135 get_branch, get_package, get_version, get_package_version, 136 get_patchlevel 137 138 :group Validating name components: is_archive_name, is_category_name, 139 is_branch_name, is_version_id, is_patchlevel 140 """ 141 142 __archive_regex = re.compile('^[a-zA-Z0-9][-a-zA-Z0-9]*(\.[-a-zA-Z0-9]+)*@' 143 '[-a-zA-Z0-9.]*$') 144 __name_regex = re.compile('^[a-zA-Z]([a-zA-Z0-9]|-[a-zA-Z0-9])*$') 145 __version_regex = re.compile('^[0-9][A-Za-z0-9+:.~-]*$') 146 # tla accepts --version-12, so mimick that: 147 __level_regex = re.compile('^base-0$|^(version|patch|versionfix)-[0-9]+$') 148
149 - def __init__(self, s):
150 """Create a parser object for the given string. 151 152 :param s: string to parse. 153 :type s: str 154 """ 155 str.__init__(s) 156 parts = self.__parse() 157 if parts: 158 self.__valid = True 159 else: 160 parts = None, None, None, None, None, None 161 self.__valid = False 162 (self.__archive, self.__nonarch, self.__category, 163 self.__branch, self.__version, self.__level) = parts
164
165 - def __parse(self):
166 parts = self.__split() 167 if not parts: 168 return False 169 if not self.__validate(parts): 170 return False 171 return parts
172
173 - def __split(self):
174 parts = self.split('/') 175 if len(parts) == 1: 176 archive, nonarch = None, parts[0] 177 elif len(parts) == 2: 178 archive, nonarch = parts 179 else: 180 return False 181 parts = nonarch.split('--') 182 category, branch, version, level = None, None, None, None 183 if len(parts) == 1: 184 category = parts[0] 185 elif len(parts) == 2: 186 if nonarch.endswith('--'): 187 category, ignored = parts 188 elif self.__name_regex.match(parts[1]): 189 category, branch = parts 190 elif self.__match_name_with_dash(parts[1]): 191 category, branch = parts 192 else: 193 category, version = parts 194 elif len(parts) == 3: 195 if nonarch.endswith('--'): 196 category, branch, ignored = parts 197 elif self.__name_regex.match(parts[1]): 198 category, branch, version = parts 199 else: 200 category, version, level = parts 201 elif len(parts) == 4: 202 category, branch, version, level = parts 203 else: 204 return False 205 return archive, nonarch, category, branch, version, level
206
207 - def __match_name_with_dash(self, string):
208 if not string.endswith('-'): 209 return False 210 if self.__name_regex.match(string[:-1]): 211 return True 212 else: 213 return False
214
215 - def __validate(self, args):
216 archive, nonarch, category, branch, version, level = args 217 if archive is not None and not self.__archive_regex.match(archive): 218 return False 219 if category.endswith('-'): 220 category = category[:-1] 221 if not self.__name_regex.match(category): 222 return False 223 if branch is not None: 224 if branch.endswith('-'): 225 branch = branch[:-1] 226 if not self.__name_regex.match(branch): 227 return False 228 if version is None and level is None: 229 return True 230 if not self.__version_regex.match(version): 231 return False 232 if level is None: 233 return True 234 if not self.__level_regex.match(level): 235 return False 236 else: 237 return True
238
239 - def is_category(self):
240 """Is this a category name? 241 242 :rtype: bool 243 """ 244 return bool(self.__category) and not (self.__branch or self.__version)
245
246 - def is_package(self):
247 """Is this a package name (category or branch name)? 248 249 :rtype: bool 250 """ 251 return bool(self.__category) and not self.__version
252
253 - def is_version(self):
254 """Is this a version name? 255 256 :rtype: bool 257 """ 258 return bool(self.__version) and not self.__level
259
260 - def has_archive(self):
261 """Does this include an archive name? 262 263 :rtype: bool 264 """ 265 return bool(self.__archive)
266
267 - def has_category(self):
268 """Does this include an category name? 269 270 All valid names include a category. 271 272 :rtype: bool 273 """ 274 return bool(self.__category)
275
276 - def has_package(self):
277 """Does this include an package name? 278 279 All valid names include a package. 280 281 :rtype: bool 282 """ 283 return bool(self.__category)
284
285 - def has_version(self):
286 """Does this include a version name? 287 288 :rtype: bool 289 """ 290 return bool(self.__version)
291
292 - def has_patchlevel(self):
293 """Does this include a revision name? 294 295 :rtype: bool 296 """ 297 return bool(self.__level)
298
299 - def get_archive(self):
300 """Get the archive part of the name 301 302 :return: archive part of the name, or the default archive name, or None 303 if the name is invalid. 304 :rtype: str, None 305 """ 306 if not self.__valid: return None 307 if not self.__archive: return default_archive() 308 return self.__archive
309
310 - def get_nonarch(self):
311 """Get Non-archive part of the name 312 313 :return: the name without the archive component, or None if the name is 314 invalid or has no archive component. 315 :rtype: str, None 316 """ 317 return self.__nonarch
318
319 - def get_category(self):
320 """Get the Category name 321 322 :return: part of the name which identifies the category within 323 the archive, or None if the name is invalid or has no category 324 component. 325 :rtype: str, None 326 """ 327 return self.__category
328
329 - def get_branch(self):
330 """Get the branch part of name 331 332 :return: part of the name which identifies the branch within the 333 category, or None if the name is invalid or the empty string if the 334 name has no branch component. 335 :rtype: str, None 336 """ 337 if not self.__valid: return None 338 if not self.__branch: return str() 339 return self.__branch
340
341 - def get_package(self):
342 """Get the package name 343 344 :return: part of the name including the category part and branch part 345 (if present) of the name, or None if the name is not valid. 346 :rtype: str, None 347 """ 348 if not self.__valid: return None 349 if self.__branch is None: return self.__category 350 return self.__category + '--' + self.__branch
351
352 - def get_version(self):
353 """Get the version id part of the name 354 355 :return: part of the name identifying a version in a branch, or None if 356 the name is invalid or does not contain a version id. 357 :rtype: str, None 358 """ 359 return self.__version
360
361 - def get_package_version(self):
362 """Get the unqualified version name 363 364 :return: part of the name identifying a version in an archive, or None 365 if the name does not contain a version id or is invalid. 366 :rtype: str, None 367 """ 368 if not self.__version: return None 369 return self.get_package() + '--' + self.__version
370
371 - def get_patchlevel(self):
372 """Get the patch-level part of the name 373 374 :return: part of the name identifying a patch in a version, or None if 375 the name is not a revision or is invalid. 376 :rtype: str, None 377 """ 378 return self.__level
379
380 - def is_archive_name(klass, s):
381 """Is this string a valid archive name? 382 383 :param s: string to validate. 384 :type s: str 385 :rtype: bool 386 """ 387 return bool(klass.__archive_regex.match(s))
388 is_archive_name = classmethod(is_archive_name) 389
390 - def is_category_name(klass, s):
391 """Is this string a valid category name? 392 393 Currently does the same thing as is_branch_name, but that might 394 change in the future when the namespace evolves and it is more 395 expressive to have different functions. 396 397 :param s: string to validate. 398 :type s: str 399 :rtype: bool 400 """ 401 return bool(klass.__name_regex.match(s))
402 is_category_name = classmethod(is_category_name) 403
404 - def is_branch_name(klass, s):
405 """Is this string a valid category name? 406 407 Currently does the same thing as is_category_name, but that might 408 change in the future when the namespace evolves and it is more 409 expressive to have different functions. 410 411 :param s: string to validate. 412 :type s: str 413 :rtype: bool 414 """ 415 return bool(klass.__name_regex.match(s))
416 is_branch_name = classmethod(is_branch_name) 417
418 - def is_version_id(klass, s):
419 """Is this string a valid version id? 420 421 :param s: string to validate. 422 :type s: str 423 :rtype: bool 424 """ 425 return bool(klass.__version_regex.match(s))
426 is_version_id = classmethod(is_version_id) 427
428 - def is_patchlevel(klass, s):
429 """Is this string a valid unqualified patch-level name? 430 431 :param s: string to validate. 432 :type s: str 433 :rtype: bool 434 """ 435 return bool(klass.__level_regex.match(s))
436 is_patchlevel = classmethod(is_patchlevel) 437 438
439 - def object(self):
440 """Create the Category, Branch, Version or Revision object 441 442 Create the namespace object corresponding to the name. This 443 requires some guessing so, for example, nameless branches will 444 not be recognized. 445 446 This function is unsafe (categories and nameless branches are not 447 distinguished) and is not really useful. Internally, only namespace 448 objects should be used, and external output should be validated in a 449 more specific way. 450 """ 451 if not self.__valid: 452 return None 453 try: 454 # handle default archive 455 fqname = self.get_archive() + '/' + self.get_nonarch() 456 if self.is_category(): 457 archive = factory().Archive(self.get_archive()) 458 obj = archive[self.get_category()] 459 elif self.is_package(): 460 archive = factory().Archive(self.get_archive()) 461 obj = archive[self.get_category()][self.get_branch()] 462 elif self.is_version(): 463 obj = factory().Version(fqname) 464 elif self.has_patchlevel(): 465 obj = factory().Revision(fqname) 466 else: 467 obj = None 468 except errors.NamespaceError: 469 # needed to handle category or branch names with trailing dash 470 obj = None 471 return obj
472