Mercurial > repos > melissacline > ucsc_xena_platform
comparison python-daemon-2.0.5/version.py @ 33:7ceb967147c3
start xena with no gui
add library files
| author | jingchunzhu <jingchunzhu@gmail.com> |
|---|---|
| date | Wed, 22 Jul 2015 13:24:44 -0700 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 32:63b1ba1e3424 | 33:7ceb967147c3 |
|---|---|
| 1 # -*- coding: utf-8 -*- | |
| 2 | |
| 3 # version.py | |
| 4 # Part of ‘python-daemon’, an implementation of PEP 3143. | |
| 5 # | |
| 6 # Copyright © 2008–2015 Ben Finney <ben+python@benfinney.id.au> | |
| 7 # | |
| 8 # This is free software: you may copy, modify, and/or distribute this work | |
| 9 # under the terms of the GNU General Public License as published by the | |
| 10 # Free Software Foundation; version 3 of that license or any later version. | |
| 11 # No warranty expressed or implied. See the file ‘LICENSE.GPL-3’ for details. | |
| 12 | |
| 13 """ Version information unified for human- and machine-readable formats. | |
| 14 | |
| 15 The project ‘ChangeLog’ file is a reStructuredText document, with | |
| 16 each section describing a version of the project. The document is | |
| 17 intended to be readable as-is by end users. | |
| 18 | |
| 19 This module handles transformation from the ‘ChangeLog’ to a | |
| 20 mapping of version information, serialised as JSON. It also | |
| 21 provides functionality for Distutils to use this information. | |
| 22 | |
| 23 Requires: | |
| 24 | |
| 25 * Docutils <http://docutils.sourceforge.net/> | |
| 26 * JSON <https://docs.python.org/3/reference/json.html> | |
| 27 | |
| 28 """ | |
| 29 | |
| 30 from __future__ import (absolute_import, unicode_literals) | |
| 31 | |
| 32 import sys | |
| 33 import os | |
| 34 import io | |
| 35 import errno | |
| 36 import json | |
| 37 import datetime | |
| 38 import textwrap | |
| 39 import re | |
| 40 import functools | |
| 41 import collections | |
| 42 import distutils | |
| 43 import distutils.errors | |
| 44 import distutils.cmd | |
| 45 try: | |
| 46 # Python 2 has both ‘str’ (bytes) and ‘unicode’ (text). | |
| 47 basestring = basestring | |
| 48 unicode = unicode | |
| 49 except NameError: | |
| 50 # Python 3 names the Unicode data type ‘str’. | |
| 51 basestring = str | |
| 52 unicode = str | |
| 53 | |
| 54 import setuptools | |
| 55 import setuptools.command.egg_info | |
| 56 | |
| 57 | |
| 58 def ensure_class_bases_begin_with(namespace, class_name, base_class): | |
| 59 """ Ensure the named class's bases start with the base class. | |
| 60 | |
| 61 :param namespace: The namespace containing the class name. | |
| 62 :param class_name: The name of the class to alter. | |
| 63 :param base_class: The type to be the first base class for the | |
| 64 newly created type. | |
| 65 :return: ``None``. | |
| 66 | |
| 67 This function is a hack to circumvent a circular dependency: | |
| 68 using classes from a module which is not installed at the time | |
| 69 this module is imported. | |
| 70 | |
| 71 Call this function after ensuring `base_class` is available, | |
| 72 before using the class named by `class_name`. | |
| 73 | |
| 74 """ | |
| 75 existing_class = namespace[class_name] | |
| 76 assert isinstance(existing_class, type) | |
| 77 | |
| 78 bases = list(existing_class.__bases__) | |
| 79 if base_class is bases[0]: | |
| 80 # Already bound to a type with the right bases. | |
| 81 return | |
| 82 bases.insert(0, base_class) | |
| 83 | |
| 84 new_class_namespace = existing_class.__dict__.copy() | |
| 85 # Type creation will assign the correct ‘__dict__’ attribute. | |
| 86 del new_class_namespace['__dict__'] | |
| 87 | |
| 88 metaclass = existing_class.__metaclass__ | |
| 89 new_class = metaclass(class_name, tuple(bases), new_class_namespace) | |
| 90 | |
| 91 namespace[class_name] = new_class | |
| 92 | |
| 93 | |
| 94 class VersionInfoWriter(object): | |
| 95 """ Docutils writer to produce a version info JSON data stream. """ | |
| 96 | |
| 97 # This class needs its base class to be a class from `docutils`. | |
| 98 # But that would create a circular dependency: Setuptools cannot | |
| 99 # ensure `docutils` is available before importing this module. | |
| 100 # | |
| 101 # Use `ensure_class_bases_begin_with` after importing `docutils`, to | |
| 102 # re-bind the `VersionInfoWriter` name to a new type that inherits | |
| 103 # from `docutils.writers.Writer`. | |
| 104 | |
| 105 __metaclass__ = type | |
| 106 | |
| 107 supported = ['version_info'] | |
| 108 """ Formats this writer supports. """ | |
| 109 | |
| 110 def __init__(self): | |
| 111 super(VersionInfoWriter, self).__init__() | |
| 112 self.translator_class = VersionInfoTranslator | |
| 113 | |
| 114 def translate(self): | |
| 115 visitor = self.translator_class(self.document) | |
| 116 self.document.walkabout(visitor) | |
| 117 self.output = visitor.astext() | |
| 118 | |
| 119 | |
| 120 rfc822_person_regex = re.compile( | |
| 121 "^(?P<name>[^<]+) <(?P<email>[^>]+)>$") | |
| 122 | |
| 123 class ChangeLogEntry: | |
| 124 """ An individual entry from the ‘ChangeLog’ document. """ | |
| 125 | |
| 126 __metaclass__ = type | |
| 127 | |
| 128 field_names = [ | |
| 129 'release_date', | |
| 130 'version', | |
| 131 'maintainer', | |
| 132 'body', | |
| 133 ] | |
| 134 | |
| 135 date_format = "%Y-%m-%d" | |
| 136 default_version = "UNKNOWN" | |
| 137 default_release_date = "UNKNOWN" | |
| 138 | |
| 139 def __init__( | |
| 140 self, | |
| 141 release_date=default_release_date, version=default_version, | |
| 142 maintainer=None, body=None): | |
| 143 self.validate_release_date(release_date) | |
| 144 self.release_date = release_date | |
| 145 | |
| 146 self.version = version | |
| 147 | |
| 148 self.validate_maintainer(maintainer) | |
| 149 self.maintainer = maintainer | |
| 150 self.body = body | |
| 151 | |
| 152 @classmethod | |
| 153 def validate_release_date(cls, value): | |
| 154 """ Validate the `release_date` value. | |
| 155 | |
| 156 :param value: The prospective `release_date` value. | |
| 157 :return: ``None`` if the value is valid. | |
| 158 :raises ValueError: If the value is invalid. | |
| 159 | |
| 160 """ | |
| 161 if value in ["UNKNOWN", "FUTURE"]: | |
| 162 # A valid non-date value. | |
| 163 return None | |
| 164 | |
| 165 # Raises `ValueError` if parse fails. | |
| 166 datetime.datetime.strptime(value, ChangeLogEntry.date_format) | |
| 167 | |
| 168 @classmethod | |
| 169 def validate_maintainer(cls, value): | |
| 170 """ Validate the `maintainer` value. | |
| 171 | |
| 172 :param value: The prospective `maintainer` value. | |
| 173 :return: ``None`` if the value is valid. | |
| 174 :raises ValueError: If the value is invalid. | |
| 175 | |
| 176 """ | |
| 177 valid = False | |
| 178 | |
| 179 if value is None: | |
| 180 valid = True | |
| 181 elif rfc822_person_regex.search(value): | |
| 182 valid = True | |
| 183 | |
| 184 if not valid: | |
| 185 raise ValueError("Not a valid person specification {value!r}") | |
| 186 else: | |
| 187 return None | |
| 188 | |
| 189 @classmethod | |
| 190 def make_ordered_dict(cls, fields): | |
| 191 """ Make an ordered dict of the fields. """ | |
| 192 result = collections.OrderedDict( | |
| 193 (name, fields[name]) | |
| 194 for name in cls.field_names) | |
| 195 return result | |
| 196 | |
| 197 def as_version_info_entry(self): | |
| 198 """ Format the changelog entry as a version info entry. """ | |
| 199 fields = vars(self) | |
| 200 entry = self.make_ordered_dict(fields) | |
| 201 | |
| 202 return entry | |
| 203 | |
| 204 | |
| 205 class InvalidFormatError(ValueError): | |
| 206 """ Raised when the document is not a valid ‘ChangeLog’ document. """ | |
| 207 | |
| 208 | |
| 209 class VersionInfoTranslator(object): | |
| 210 """ Translator from document nodes to a version info stream. """ | |
| 211 | |
| 212 # This class needs its base class to be a class from `docutils`. | |
| 213 # But that would create a circular dependency: Setuptools cannot | |
| 214 # ensure `docutils` is available before importing this module. | |
| 215 # | |
| 216 # Use `ensure_class_bases_begin_with` after importing `docutils`, | |
| 217 # to re-bind the `VersionInfoTranslator` name to a new type that | |
| 218 # inherits from `docutils.nodes.SparseNodeVisitor`. | |
| 219 | |
| 220 __metaclass__ = type | |
| 221 | |
| 222 wrap_width = 78 | |
| 223 bullet_text = "* " | |
| 224 | |
| 225 attr_convert_funcs_by_attr_name = { | |
| 226 'released': ('release_date', unicode), | |
| 227 'version': ('version', unicode), | |
| 228 'maintainer': ('maintainer', unicode), | |
| 229 } | |
| 230 | |
| 231 def __init__(self, document): | |
| 232 super(VersionInfoTranslator, self).__init__(document) | |
| 233 self.settings = document.settings | |
| 234 self.current_section_level = 0 | |
| 235 self.current_field_name = None | |
| 236 self.content = [] | |
| 237 self.indent_width = 0 | |
| 238 self.initial_indent = "" | |
| 239 self.subsequent_indent = "" | |
| 240 self.current_entry = None | |
| 241 | |
| 242 # Docutils is not available when this class is defined. | |
| 243 # Get the `docutils` module dynamically. | |
| 244 self._docutils = sys.modules['docutils'] | |
| 245 | |
| 246 def astext(self): | |
| 247 """ Return the translated document as text. """ | |
| 248 text = json.dumps(self.content, indent=4) | |
| 249 return text | |
| 250 | |
| 251 def append_to_current_entry(self, text): | |
| 252 if self.current_entry is not None: | |
| 253 if self.current_entry.body is not None: | |
| 254 self.current_entry.body += text | |
| 255 | |
| 256 def visit_Text(self, node): | |
| 257 raw_text = node.astext() | |
| 258 text = textwrap.fill( | |
| 259 raw_text, | |
| 260 width=self.wrap_width, | |
| 261 initial_indent=self.initial_indent, | |
| 262 subsequent_indent=self.subsequent_indent) | |
| 263 self.append_to_current_entry(text) | |
| 264 | |
| 265 def depart_Text(self, node): | |
| 266 pass | |
| 267 | |
| 268 def visit_comment(self, node): | |
| 269 raise self._docutils.nodes.SkipNode | |
| 270 | |
| 271 def visit_field_body(self, node): | |
| 272 field_list_node = node.parent.parent | |
| 273 if not isinstance(field_list_node, self._docutils.nodes.field_list): | |
| 274 raise InvalidFormatError( | |
| 275 "Unexpected field within {node!r}".format( | |
| 276 node=field_list_node)) | |
| 277 (attr_name, convert_func) = self.attr_convert_funcs_by_attr_name[ | |
| 278 self.current_field_name] | |
| 279 attr_value = convert_func(node.astext()) | |
| 280 setattr(self.current_entry, attr_name, attr_value) | |
| 281 | |
| 282 def depart_field_body(self, node): | |
| 283 pass | |
| 284 | |
| 285 def visit_field_list(self, node): | |
| 286 pass | |
| 287 | |
| 288 def depart_field_list(self, node): | |
| 289 self.current_field_name = None | |
| 290 self.current_entry.body = "" | |
| 291 | |
| 292 def visit_field_name(self, node): | |
| 293 field_name = node.astext() | |
| 294 if self.current_section_level == 1: | |
| 295 # At a top-level section. | |
| 296 if field_name.lower() not in ["released", "maintainer"]: | |
| 297 raise InvalidFormatError( | |
| 298 "Unexpected field name {name!r}".format(name=field_name)) | |
| 299 self.current_field_name = field_name.lower() | |
| 300 | |
| 301 def depart_field_name(self, node): | |
| 302 pass | |
| 303 | |
| 304 def visit_bullet_list(self, node): | |
| 305 self.current_context = [] | |
| 306 | |
| 307 def depart_bullet_list(self, node): | |
| 308 self.current_entry.changes = self.current_context | |
| 309 self.current_context = None | |
| 310 | |
| 311 def adjust_indent_width(self, delta): | |
| 312 self.indent_width += delta | |
| 313 self.subsequent_indent = " " * self.indent_width | |
| 314 self.initial_indent = self.subsequent_indent | |
| 315 | |
| 316 def visit_list_item(self, node): | |
| 317 indent_delta = +len(self.bullet_text) | |
| 318 self.adjust_indent_width(indent_delta) | |
| 319 self.initial_indent = self.subsequent_indent[:-indent_delta] | |
| 320 self.append_to_current_entry(self.initial_indent + self.bullet_text) | |
| 321 | |
| 322 def depart_list_item(self, node): | |
| 323 indent_delta = +len(self.bullet_text) | |
| 324 self.adjust_indent_width(-indent_delta) | |
| 325 self.append_to_current_entry("\n") | |
| 326 | |
| 327 def visit_section(self, node): | |
| 328 self.current_section_level += 1 | |
| 329 if self.current_section_level == 1: | |
| 330 # At a top-level section. | |
| 331 self.current_entry = ChangeLogEntry() | |
| 332 else: | |
| 333 raise InvalidFormatError( | |
| 334 "Subsections not implemented for this writer") | |
| 335 | |
| 336 def depart_section(self, node): | |
| 337 self.current_section_level -= 1 | |
| 338 self.content.append( | |
| 339 self.current_entry.as_version_info_entry()) | |
| 340 self.current_entry = None | |
| 341 | |
| 342 _expected_title_word_length = len("Version FOO".split(" ")) | |
| 343 | |
| 344 def depart_title(self, node): | |
| 345 title_text = node.astext() | |
| 346 # At a top-level section. | |
| 347 words = title_text.split(" ") | |
| 348 version = None | |
| 349 if len(words) != self._expected_title_word_length: | |
| 350 raise InvalidFormatError( | |
| 351 "Unexpected title text {text!r}".format(text=title_text)) | |
| 352 if words[0].lower() not in ["version"]: | |
| 353 raise InvalidFormatError( | |
| 354 "Unexpected title text {text!r}".format(text=title_text)) | |
| 355 version = words[-1] | |
| 356 self.current_entry.version = version | |
| 357 | |
| 358 | |
| 359 def changelog_to_version_info_collection(infile): | |
| 360 """ Render the ‘ChangeLog’ document to a version info collection. | |
| 361 | |
| 362 :param infile: A file-like object containing the changelog. | |
| 363 :return: The serialised JSON data of the version info collection. | |
| 364 | |
| 365 """ | |
| 366 | |
| 367 # Docutils is not available when Setuptools needs this module, so | |
| 368 # delay the imports to this function instead. | |
| 369 import docutils.core | |
| 370 import docutils.nodes | |
| 371 import docutils.writers | |
| 372 | |
| 373 ensure_class_bases_begin_with( | |
| 374 globals(), str('VersionInfoWriter'), docutils.writers.Writer) | |
| 375 ensure_class_bases_begin_with( | |
| 376 globals(), str('VersionInfoTranslator'), | |
| 377 docutils.nodes.SparseNodeVisitor) | |
| 378 | |
| 379 writer = VersionInfoWriter() | |
| 380 settings_overrides = { | |
| 381 'doctitle_xform': False, | |
| 382 } | |
| 383 version_info_json = docutils.core.publish_string( | |
| 384 infile.read(), writer=writer, | |
| 385 settings_overrides=settings_overrides) | |
| 386 | |
| 387 return version_info_json | |
| 388 | |
| 389 | |
| 390 try: | |
| 391 lru_cache = functools.lru_cache | |
| 392 except AttributeError: | |
| 393 # Python < 3.2 does not have the `functools.lru_cache` function. | |
| 394 # Not essential, so replace it with a no-op. | |
| 395 lru_cache = lambda maxsize=None, typed=False: lambda func: func | |
| 396 | |
| 397 | |
| 398 @lru_cache(maxsize=128) | |
| 399 def generate_version_info_from_changelog(infile_path): | |
| 400 """ Get the version info for the latest version in the changelog. | |
| 401 | |
| 402 :param infile_path: Filesystem path to the input changelog file. | |
| 403 :return: The generated version info mapping; or ``None`` if the | |
| 404 file cannot be read. | |
| 405 | |
| 406 The document is explicitly opened as UTF-8 encoded text. | |
| 407 | |
| 408 """ | |
| 409 version_info = collections.OrderedDict() | |
| 410 | |
| 411 versions_all_json = None | |
| 412 try: | |
| 413 with io.open(infile_path, 'rt', encoding="utf-8") as infile: | |
| 414 versions_all_json = changelog_to_version_info_collection(infile) | |
| 415 except EnvironmentError: | |
| 416 # If we can't read the input file, leave the collection empty. | |
| 417 pass | |
| 418 | |
| 419 if versions_all_json is not None: | |
| 420 versions_all = json.loads(versions_all_json.decode('utf-8')) | |
| 421 version_info = get_latest_version(versions_all) | |
| 422 | |
| 423 return version_info | |
| 424 | |
| 425 | |
| 426 def get_latest_version(versions): | |
| 427 """ Get the latest version from a collection of changelog entries. | |
| 428 | |
| 429 :param versions: A collection of mappings for changelog entries. | |
| 430 :return: An ordered mapping of fields for the latest version, | |
| 431 if `versions` is non-empty; otherwise, an empty mapping. | |
| 432 | |
| 433 """ | |
| 434 version_info = collections.OrderedDict() | |
| 435 | |
| 436 versions_by_release_date = { | |
| 437 item['release_date']: item | |
| 438 for item in versions} | |
| 439 if versions_by_release_date: | |
| 440 latest_release_date = max(versions_by_release_date.keys()) | |
| 441 version_info = ChangeLogEntry.make_ordered_dict( | |
| 442 versions_by_release_date[latest_release_date]) | |
| 443 | |
| 444 return version_info | |
| 445 | |
| 446 | |
| 447 def serialise_version_info_from_mapping(version_info): | |
| 448 """ Generate the version info serialised data. | |
| 449 | |
| 450 :param version_info: Mapping of version info items. | |
| 451 :return: The version info serialised to JSON. | |
| 452 | |
| 453 """ | |
| 454 content = json.dumps(version_info, indent=4) | |
| 455 | |
| 456 return content | |
| 457 | |
| 458 | |
| 459 changelog_filename = "ChangeLog" | |
| 460 | |
| 461 def get_changelog_path(distribution, filename=changelog_filename): | |
| 462 """ Get the changelog file path for the distribution. | |
| 463 | |
| 464 :param distribution: The distutils.dist.Distribution instance. | |
| 465 :param filename: The base filename of the changelog document. | |
| 466 :return: Filesystem path of the changelog document, or ``None`` | |
| 467 if not discoverable. | |
| 468 | |
| 469 """ | |
| 470 setup_dirname = os.path.dirname(distribution.script_name) | |
| 471 filepath = os.path.join(setup_dirname, filename) | |
| 472 | |
| 473 return filepath | |
| 474 | |
| 475 | |
| 476 def has_changelog(command): | |
| 477 """ Return ``True`` iff the distribution's changelog file exists. """ | |
| 478 result = False | |
| 479 | |
| 480 changelog_path = get_changelog_path(command.distribution) | |
| 481 if changelog_path is not None: | |
| 482 if os.path.exists(changelog_path): | |
| 483 result = True | |
| 484 | |
| 485 return result | |
| 486 | |
| 487 | |
| 488 class EggInfoCommand(setuptools.command.egg_info.egg_info, object): | |
| 489 """ Custom ‘egg_info’ command for this distribution. """ | |
| 490 | |
| 491 sub_commands = ([ | |
| 492 ('write_version_info', has_changelog), | |
| 493 ] + setuptools.command.egg_info.egg_info.sub_commands) | |
| 494 | |
| 495 def run(self): | |
| 496 """ Execute this command. """ | |
| 497 super(EggInfoCommand, self).run() | |
| 498 | |
| 499 for command_name in self.get_sub_commands(): | |
| 500 self.run_command(command_name) | |
| 501 | |
| 502 | |
| 503 version_info_filename = "version_info.json" | |
| 504 | |
| 505 class WriteVersionInfoCommand(EggInfoCommand, object): | |
| 506 """ Setuptools command to serialise version info metadata. """ | |
| 507 | |
| 508 user_options = ([ | |
| 509 ("changelog-path=", None, | |
| 510 "Filesystem path to the changelog document."), | |
| 511 ("outfile-path=", None, | |
| 512 "Filesystem path to the version info file."), | |
| 513 ] + EggInfoCommand.user_options) | |
| 514 | |
| 515 def initialize_options(self): | |
| 516 """ Initialise command options to defaults. """ | |
| 517 super(WriteVersionInfoCommand, self).initialize_options() | |
| 518 self.changelog_path = None | |
| 519 self.outfile_path = None | |
| 520 | |
| 521 def finalize_options(self): | |
| 522 """ Finalise command options before execution. """ | |
| 523 self.set_undefined_options( | |
| 524 'build', | |
| 525 ('force', 'force')) | |
| 526 | |
| 527 super(WriteVersionInfoCommand, self).finalize_options() | |
| 528 | |
| 529 if self.changelog_path is None: | |
| 530 self.changelog_path = get_changelog_path(self.distribution) | |
| 531 | |
| 532 if self.outfile_path is None: | |
| 533 egg_dir = self.egg_info | |
| 534 self.outfile_path = os.path.join(egg_dir, version_info_filename) | |
| 535 | |
| 536 def run(self): | |
| 537 """ Execute this command. """ | |
| 538 version_info = generate_version_info_from_changelog(self.changelog_path) | |
| 539 content = serialise_version_info_from_mapping(version_info) | |
| 540 self.write_file("version info", self.outfile_path, content) | |
| 541 | |
| 542 | |
| 543 # Local variables: | |
| 544 # coding: utf-8 | |
| 545 # mode: python | |
| 546 # End: | |
| 547 # vim: fileencoding=utf-8 filetype=python : |
