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 : |