├── .gitattributes ├── .gitignore ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── README.adoc ├── pionic ├── __init__.py ├── _version.py ├── antlr │ ├── IonText.g4 │ ├── IonText.tokens │ ├── IonTextLexer.py │ ├── IonTextLexer.tokens │ ├── IonTextListener.py │ └── IonTextParser.py └── core.py ├── setup.cfg ├── setup.py ├── tests └── test_pionic.py ├── tox.ini └── versioneer.py /.gitattributes: -------------------------------------------------------------------------------- 1 | pion/_version.py export-subst 2 | pionic/_version.py export-subst 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | *.swp 104 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | dist: trusty 3 | language: python 4 | python: 5 | - "3.5" 6 | 7 | install: 8 | - pip install tox 9 | 10 | script: tox 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Tony Locke 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include versioneer.py 2 | include pion/_version.py 3 | include pionic/_version.py 4 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = Pionic 2 | 3 | A Python 3 library for the http://amzn.github.io/ion-docs/[Ion format], 4 | released under the 5 | https://github.com/tlocke/pionic/blob/master/LICENSE[MIT Licence]. 6 | 7 | image:https://travis-ci.org/tlocke/pionic.svg?branch=master["Build Status", 8 | link="https://travis-ci.org/tlocke/pionic"] 9 | 10 | The Ion format is a superset of JSON, adding (among other things) the 11 | much-needed timestamp, decimal and binary data types. 12 | 13 | 14 | == Installation 15 | 16 | It's a good idea to set up a virtualenv: 17 | 18 | virtualenv venv 19 | source venv/bin/activate 20 | 21 | then install Pionic with pip: 22 | 23 | pip install pionic 24 | 25 | 26 | == Quickstart 27 | 28 | To go from a Python object to an Ion string use `pionic.dumps`. To go from an 29 | Ion string to a Python object use `pionic.loads`. Eg. 30 | 31 | .... 32 | >>> from pionic import loads, dumps 33 | >>> from datetime import datetime, timezone 34 | >>> from decimal import Decimal 35 | >>> 36 | >>> # Take a Python object 37 | >>> book = { 38 | ... 'title': 'A Hero of Our Time', 39 | ... 'read_date': datetime(2017, 7, 16, 14, 5, tzinfo=timezone.utc), 40 | ... 'would_recommend': True, 41 | ... 'description': None, 42 | ... 'number_of_novellas': 5, 43 | ... 'price': Decimal('7.99'), 44 | ... 'weight': 6.88, 45 | ... 'key': bytearray(b'kshhgrl'), 46 | ... 'tags': ['russian', 'novel', '19th century']} 47 | >>> 48 | >>> # Output it as an Ion string 49 | >>> ion_str = dumps(book) 50 | >>> print(ion_str) 51 | { 52 | 'description': null, 53 | 'key': {{ a3NoaGdybA== }}, 54 | 'number_of_novellas': 5, 55 | 'price': 7.99, 56 | 'read_date': 2017-07-16T14:05:00Z, 57 | 'tags': [ 58 | "russian", 59 | "novel", 60 | "19th century"], 61 | 'title': "A Hero of Our Time", 62 | 'weight': 6.88, 63 | 'would_recommend': true} 64 | >>> 65 | >>> # Load the Ion string, to give us back the Python object 66 | >>> reloaded_book = loads(ion_str) 67 | >>> 68 | >>> # Print the title 69 | >>> print(reloaded_book['title']) 70 | A Hero of Our Time 71 | 72 | .... 73 | 74 | 75 | == Contributing 76 | 77 | Useful links: 78 | 79 | * https://amzn.github.io/ion-docs/spec.html[Ion Specification] 80 | * http://www.antlr.org/api/Java/index.html?overview-summary.html[ANTLR JavaDocs] 81 | 82 | To run the tests: 83 | 84 | * Change to the `pionic` directory: `cd pionic` 85 | * Create a virtual environment: `virtualenv --python=python3 venv` 86 | * Active the virtual environment: `source venv/bin/activate` 87 | * Install tox: `pip install tox` 88 | * Run tox: `tox` 89 | 90 | The core parser is created using https://github.com/antlr/antlr4[ANTLR] from 91 | the http://amzn.github.io/ion-docs/grammar/IonText.g4.txt[Ion grammar]. To 92 | create the parser files, go to the `antlr` directory and download the ANTLR jar 93 | and then run the following command: 94 | 95 | java -jar antlr-4.7-complete.jar -Dlanguage=Python3 IonText.g4 96 | 97 | 98 | === Making A New Release 99 | 100 | Run `tox` to make sure all tests pass, then update the `Release Notes` section 101 | then do: 102 | 103 | .... 104 | git tag -a x.y.z -m "version x.y.z" 105 | python setup.py register sdist bdist_wheel upload --sign 106 | .... 107 | -------------------------------------------------------------------------------- /pionic/__init__.py: -------------------------------------------------------------------------------- 1 | from pionic.core import load, loads, PionException, dumps, dump 2 | from ._version import get_versions 3 | __version__ = get_versions()['version'] 4 | del get_versions 5 | 6 | __all__ = [load, loads, PionException, dumps, dump] 7 | -------------------------------------------------------------------------------- /pionic/_version.py: -------------------------------------------------------------------------------- 1 | 2 | # This file helps to compute a version number in source trees obtained from 3 | # git-archive tarball (such as those provided by githubs download-from-tag 4 | # feature). Distribution tarballs (built by setup.py sdist) and build 5 | # directories (produced by setup.py build) will contain a much shorter file 6 | # that just contains the computed version number. 7 | 8 | # This file is released into the public domain. Generated by 9 | # versioneer-0.18 (https://github.com/warner/python-versioneer) 10 | 11 | """Git implementation of _version.py.""" 12 | 13 | import errno 14 | import os 15 | import re 16 | import subprocess 17 | import sys 18 | 19 | 20 | def get_keywords(): 21 | """Get the keywords needed to look up the version information.""" 22 | # these strings will be replaced by git during git-archive. 23 | # setup.py/versioneer.py will grep for the variable names, so they must 24 | # each be defined on a line of their own. _version.py will just call 25 | # get_keywords(). 26 | git_refnames = " (HEAD -> master, tag: 0.0.1)" 27 | git_full = "c89f2200727ef5647889e94b185406bc503c1924" 28 | git_date = "2017-07-23 18:36:47 +0100" 29 | keywords = {"refnames": git_refnames, "full": git_full, "date": git_date} 30 | return keywords 31 | 32 | 33 | class VersioneerConfig: 34 | """Container for Versioneer configuration parameters.""" 35 | 36 | 37 | def get_config(): 38 | """Create, populate and return the VersioneerConfig() object.""" 39 | # these strings are filled in when 'setup.py versioneer' creates 40 | # _version.py 41 | cfg = VersioneerConfig() 42 | cfg.VCS = "git" 43 | cfg.style = "pep440" 44 | cfg.tag_prefix = "" 45 | cfg.parentdir_prefix = "pionic-" 46 | cfg.versionfile_source = "pionic/_version.py" 47 | cfg.verbose = False 48 | return cfg 49 | 50 | 51 | class NotThisMethod(Exception): 52 | """Exception raised if a method is not valid for the current scenario.""" 53 | 54 | 55 | LONG_VERSION_PY = {} 56 | HANDLERS = {} 57 | 58 | 59 | def register_vcs_handler(vcs, method): # decorator 60 | """Decorator to mark a method as the handler for a particular VCS.""" 61 | def decorate(f): 62 | """Store f in HANDLERS[vcs][method].""" 63 | if vcs not in HANDLERS: 64 | HANDLERS[vcs] = {} 65 | HANDLERS[vcs][method] = f 66 | return f 67 | return decorate 68 | 69 | 70 | def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, 71 | env=None): 72 | """Call the given command(s).""" 73 | assert isinstance(commands, list) 74 | p = None 75 | for c in commands: 76 | try: 77 | dispcmd = str([c] + args) 78 | # remember shell=False, so use git.cmd on windows, not just git 79 | p = subprocess.Popen([c] + args, cwd=cwd, env=env, 80 | stdout=subprocess.PIPE, 81 | stderr=(subprocess.PIPE if hide_stderr 82 | else None)) 83 | break 84 | except EnvironmentError: 85 | e = sys.exc_info()[1] 86 | if e.errno == errno.ENOENT: 87 | continue 88 | if verbose: 89 | print("unable to run %s" % dispcmd) 90 | print(e) 91 | return None, None 92 | else: 93 | if verbose: 94 | print("unable to find command, tried %s" % (commands,)) 95 | return None, None 96 | stdout = p.communicate()[0].strip() 97 | if sys.version_info[0] >= 3: 98 | stdout = stdout.decode() 99 | if p.returncode != 0: 100 | if verbose: 101 | print("unable to run %s (error)" % dispcmd) 102 | print("stdout was %s" % stdout) 103 | return None, p.returncode 104 | return stdout, p.returncode 105 | 106 | 107 | def versions_from_parentdir(parentdir_prefix, root, verbose): 108 | """Try to determine the version from the parent directory name. 109 | 110 | Source tarballs conventionally unpack into a directory that includes both 111 | the project name and a version string. We will also support searching up 112 | two directory levels for an appropriately named parent directory 113 | """ 114 | rootdirs = [] 115 | 116 | for i in range(3): 117 | dirname = os.path.basename(root) 118 | if dirname.startswith(parentdir_prefix): 119 | return {"version": dirname[len(parentdir_prefix):], 120 | "full-revisionid": None, 121 | "dirty": False, "error": None, "date": None} 122 | else: 123 | rootdirs.append(root) 124 | root = os.path.dirname(root) # up a level 125 | 126 | if verbose: 127 | print("Tried directories %s but none started with prefix %s" % 128 | (str(rootdirs), parentdir_prefix)) 129 | raise NotThisMethod("rootdir doesn't start with parentdir_prefix") 130 | 131 | 132 | @register_vcs_handler("git", "get_keywords") 133 | def git_get_keywords(versionfile_abs): 134 | """Extract version information from the given file.""" 135 | # the code embedded in _version.py can just fetch the value of these 136 | # keywords. When used from setup.py, we don't want to import _version.py, 137 | # so we do it with a regexp instead. This function is not used from 138 | # _version.py. 139 | keywords = {} 140 | try: 141 | f = open(versionfile_abs, "r") 142 | for line in f.readlines(): 143 | if line.strip().startswith("git_refnames ="): 144 | mo = re.search(r'=\s*"(.*)"', line) 145 | if mo: 146 | keywords["refnames"] = mo.group(1) 147 | if line.strip().startswith("git_full ="): 148 | mo = re.search(r'=\s*"(.*)"', line) 149 | if mo: 150 | keywords["full"] = mo.group(1) 151 | if line.strip().startswith("git_date ="): 152 | mo = re.search(r'=\s*"(.*)"', line) 153 | if mo: 154 | keywords["date"] = mo.group(1) 155 | f.close() 156 | except EnvironmentError: 157 | pass 158 | return keywords 159 | 160 | 161 | @register_vcs_handler("git", "keywords") 162 | def git_versions_from_keywords(keywords, tag_prefix, verbose): 163 | """Get version information from git keywords.""" 164 | if not keywords: 165 | raise NotThisMethod("no keywords at all, weird") 166 | date = keywords.get("date") 167 | if date is not None: 168 | # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant 169 | # datestamp. However we prefer "%ci" (which expands to an "ISO-8601 170 | # -like" string, which we must then edit to make compliant), because 171 | # it's been around since git-1.5.3, and it's too difficult to 172 | # discover which version we're using, or to work around using an 173 | # older one. 174 | date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) 175 | refnames = keywords["refnames"].strip() 176 | if refnames.startswith("$Format"): 177 | if verbose: 178 | print("keywords are unexpanded, not using") 179 | raise NotThisMethod("unexpanded keywords, not a git-archive tarball") 180 | refs = set([r.strip() for r in refnames.strip("()").split(",")]) 181 | # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of 182 | # just "foo-1.0". If we see a "tag: " prefix, prefer those. 183 | TAG = "tag: " 184 | tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) 185 | if not tags: 186 | # Either we're using git < 1.8.3, or there really are no tags. We use 187 | # a heuristic: assume all version tags have a digit. The old git %d 188 | # expansion behaves like git log --decorate=short and strips out the 189 | # refs/heads/ and refs/tags/ prefixes that would let us distinguish 190 | # between branches and tags. By ignoring refnames without digits, we 191 | # filter out many common branch names like "release" and 192 | # "stabilization", as well as "HEAD" and "master". 193 | tags = set([r for r in refs if re.search(r'\d', r)]) 194 | if verbose: 195 | print("discarding '%s', no digits" % ",".join(refs - tags)) 196 | if verbose: 197 | print("likely tags: %s" % ",".join(sorted(tags))) 198 | for ref in sorted(tags): 199 | # sorting will prefer e.g. "2.0" over "2.0rc1" 200 | if ref.startswith(tag_prefix): 201 | r = ref[len(tag_prefix):] 202 | if verbose: 203 | print("picking %s" % r) 204 | return {"version": r, 205 | "full-revisionid": keywords["full"].strip(), 206 | "dirty": False, "error": None, 207 | "date": date} 208 | # no suitable tags, so version is "0+unknown", but full hex is still there 209 | if verbose: 210 | print("no suitable tags, using unknown + full revision id") 211 | return {"version": "0+unknown", 212 | "full-revisionid": keywords["full"].strip(), 213 | "dirty": False, "error": "no suitable tags", "date": None} 214 | 215 | 216 | @register_vcs_handler("git", "pieces_from_vcs") 217 | def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): 218 | """Get version from 'git describe' in the root of the source tree. 219 | 220 | This only gets called if the git-archive 'subst' keywords were *not* 221 | expanded, and _version.py hasn't already been rewritten with a short 222 | version string, meaning we're inside a checked out source tree. 223 | """ 224 | GITS = ["git"] 225 | if sys.platform == "win32": 226 | GITS = ["git.cmd", "git.exe"] 227 | 228 | out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, 229 | hide_stderr=True) 230 | if rc != 0: 231 | if verbose: 232 | print("Directory %s not under git control" % root) 233 | raise NotThisMethod("'git rev-parse --git-dir' returned error") 234 | 235 | # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] 236 | # if there isn't one, this yields HEX[-dirty] (no NUM) 237 | describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty", 238 | "--always", "--long", 239 | "--match", "%s*" % tag_prefix], 240 | cwd=root) 241 | # --long was added in git-1.5.5 242 | if describe_out is None: 243 | raise NotThisMethod("'git describe' failed") 244 | describe_out = describe_out.strip() 245 | full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) 246 | if full_out is None: 247 | raise NotThisMethod("'git rev-parse' failed") 248 | full_out = full_out.strip() 249 | 250 | pieces = {} 251 | pieces["long"] = full_out 252 | pieces["short"] = full_out[:7] # maybe improved later 253 | pieces["error"] = None 254 | 255 | # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] 256 | # TAG might have hyphens. 257 | git_describe = describe_out 258 | 259 | # look for -dirty suffix 260 | dirty = git_describe.endswith("-dirty") 261 | pieces["dirty"] = dirty 262 | if dirty: 263 | git_describe = git_describe[:git_describe.rindex("-dirty")] 264 | 265 | # now we have TAG-NUM-gHEX or HEX 266 | 267 | if "-" in git_describe: 268 | # TAG-NUM-gHEX 269 | mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) 270 | if not mo: 271 | # unparseable. Maybe git-describe is misbehaving? 272 | pieces["error"] = ("unable to parse git-describe output: '%s'" 273 | % describe_out) 274 | return pieces 275 | 276 | # tag 277 | full_tag = mo.group(1) 278 | if not full_tag.startswith(tag_prefix): 279 | if verbose: 280 | fmt = "tag '%s' doesn't start with prefix '%s'" 281 | print(fmt % (full_tag, tag_prefix)) 282 | pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" 283 | % (full_tag, tag_prefix)) 284 | return pieces 285 | pieces["closest-tag"] = full_tag[len(tag_prefix):] 286 | 287 | # distance: number of commits since tag 288 | pieces["distance"] = int(mo.group(2)) 289 | 290 | # commit: short hex revision ID 291 | pieces["short"] = mo.group(3) 292 | 293 | else: 294 | # HEX: no tags 295 | pieces["closest-tag"] = None 296 | count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], 297 | cwd=root) 298 | pieces["distance"] = int(count_out) # total number of commits 299 | 300 | # commit date: see ISO-8601 comment in git_versions_from_keywords() 301 | date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"], 302 | cwd=root)[0].strip() 303 | pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) 304 | 305 | return pieces 306 | 307 | 308 | def plus_or_dot(pieces): 309 | """Return a + if we don't already have one, else return a .""" 310 | if "+" in pieces.get("closest-tag", ""): 311 | return "." 312 | return "+" 313 | 314 | 315 | def render_pep440(pieces): 316 | """Build up version string, with post-release "local version identifier". 317 | 318 | Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you 319 | get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty 320 | 321 | Exceptions: 322 | 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] 323 | """ 324 | if pieces["closest-tag"]: 325 | rendered = pieces["closest-tag"] 326 | if pieces["distance"] or pieces["dirty"]: 327 | rendered += plus_or_dot(pieces) 328 | rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) 329 | if pieces["dirty"]: 330 | rendered += ".dirty" 331 | else: 332 | # exception #1 333 | rendered = "0+untagged.%d.g%s" % (pieces["distance"], 334 | pieces["short"]) 335 | if pieces["dirty"]: 336 | rendered += ".dirty" 337 | return rendered 338 | 339 | 340 | def render_pep440_pre(pieces): 341 | """TAG[.post.devDISTANCE] -- No -dirty. 342 | 343 | Exceptions: 344 | 1: no tags. 0.post.devDISTANCE 345 | """ 346 | if pieces["closest-tag"]: 347 | rendered = pieces["closest-tag"] 348 | if pieces["distance"]: 349 | rendered += ".post.dev%d" % pieces["distance"] 350 | else: 351 | # exception #1 352 | rendered = "0.post.dev%d" % pieces["distance"] 353 | return rendered 354 | 355 | 356 | def render_pep440_post(pieces): 357 | """TAG[.postDISTANCE[.dev0]+gHEX] . 358 | 359 | The ".dev0" means dirty. Note that .dev0 sorts backwards 360 | (a dirty tree will appear "older" than the corresponding clean one), 361 | but you shouldn't be releasing software with -dirty anyways. 362 | 363 | Exceptions: 364 | 1: no tags. 0.postDISTANCE[.dev0] 365 | """ 366 | if pieces["closest-tag"]: 367 | rendered = pieces["closest-tag"] 368 | if pieces["distance"] or pieces["dirty"]: 369 | rendered += ".post%d" % pieces["distance"] 370 | if pieces["dirty"]: 371 | rendered += ".dev0" 372 | rendered += plus_or_dot(pieces) 373 | rendered += "g%s" % pieces["short"] 374 | else: 375 | # exception #1 376 | rendered = "0.post%d" % pieces["distance"] 377 | if pieces["dirty"]: 378 | rendered += ".dev0" 379 | rendered += "+g%s" % pieces["short"] 380 | return rendered 381 | 382 | 383 | def render_pep440_old(pieces): 384 | """TAG[.postDISTANCE[.dev0]] . 385 | 386 | The ".dev0" means dirty. 387 | 388 | Eexceptions: 389 | 1: no tags. 0.postDISTANCE[.dev0] 390 | """ 391 | if pieces["closest-tag"]: 392 | rendered = pieces["closest-tag"] 393 | if pieces["distance"] or pieces["dirty"]: 394 | rendered += ".post%d" % pieces["distance"] 395 | if pieces["dirty"]: 396 | rendered += ".dev0" 397 | else: 398 | # exception #1 399 | rendered = "0.post%d" % pieces["distance"] 400 | if pieces["dirty"]: 401 | rendered += ".dev0" 402 | return rendered 403 | 404 | 405 | def render_git_describe(pieces): 406 | """TAG[-DISTANCE-gHEX][-dirty]. 407 | 408 | Like 'git describe --tags --dirty --always'. 409 | 410 | Exceptions: 411 | 1: no tags. HEX[-dirty] (note: no 'g' prefix) 412 | """ 413 | if pieces["closest-tag"]: 414 | rendered = pieces["closest-tag"] 415 | if pieces["distance"]: 416 | rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) 417 | else: 418 | # exception #1 419 | rendered = pieces["short"] 420 | if pieces["dirty"]: 421 | rendered += "-dirty" 422 | return rendered 423 | 424 | 425 | def render_git_describe_long(pieces): 426 | """TAG-DISTANCE-gHEX[-dirty]. 427 | 428 | Like 'git describe --tags --dirty --always -long'. 429 | The distance/hash is unconditional. 430 | 431 | Exceptions: 432 | 1: no tags. HEX[-dirty] (note: no 'g' prefix) 433 | """ 434 | if pieces["closest-tag"]: 435 | rendered = pieces["closest-tag"] 436 | rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) 437 | else: 438 | # exception #1 439 | rendered = pieces["short"] 440 | if pieces["dirty"]: 441 | rendered += "-dirty" 442 | return rendered 443 | 444 | 445 | def render(pieces, style): 446 | """Render the given version pieces into the requested style.""" 447 | if pieces["error"]: 448 | return {"version": "unknown", 449 | "full-revisionid": pieces.get("long"), 450 | "dirty": None, 451 | "error": pieces["error"], 452 | "date": None} 453 | 454 | if not style or style == "default": 455 | style = "pep440" # the default 456 | 457 | if style == "pep440": 458 | rendered = render_pep440(pieces) 459 | elif style == "pep440-pre": 460 | rendered = render_pep440_pre(pieces) 461 | elif style == "pep440-post": 462 | rendered = render_pep440_post(pieces) 463 | elif style == "pep440-old": 464 | rendered = render_pep440_old(pieces) 465 | elif style == "git-describe": 466 | rendered = render_git_describe(pieces) 467 | elif style == "git-describe-long": 468 | rendered = render_git_describe_long(pieces) 469 | else: 470 | raise ValueError("unknown style '%s'" % style) 471 | 472 | return {"version": rendered, "full-revisionid": pieces["long"], 473 | "dirty": pieces["dirty"], "error": None, 474 | "date": pieces.get("date")} 475 | 476 | 477 | def get_versions(): 478 | """Get version information or return default if unable to do so.""" 479 | # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have 480 | # __file__, we can work backwards from there to the root. Some 481 | # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which 482 | # case we can only use expanded keywords. 483 | 484 | cfg = get_config() 485 | verbose = cfg.verbose 486 | 487 | try: 488 | return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, 489 | verbose) 490 | except NotThisMethod: 491 | pass 492 | 493 | try: 494 | root = os.path.realpath(__file__) 495 | # versionfile_source is the relative path from the top of the source 496 | # tree (where the .git directory might live) to this file. Invert 497 | # this to find the root from __file__. 498 | for i in cfg.versionfile_source.split('/'): 499 | root = os.path.dirname(root) 500 | except NameError: 501 | return {"version": "0+unknown", "full-revisionid": None, 502 | "dirty": None, 503 | "error": "unable to find root of source tree", 504 | "date": None} 505 | 506 | try: 507 | pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) 508 | return render(pieces, cfg.style) 509 | except NotThisMethod: 510 | pass 511 | 512 | try: 513 | if cfg.parentdir_prefix: 514 | return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) 515 | except NotThisMethod: 516 | pass 517 | 518 | return {"version": "0+unknown", "full-revisionid": None, 519 | "dirty": None, 520 | "error": "unable to compute version", "date": None} 521 | -------------------------------------------------------------------------------- /pionic/antlr/IonText.g4: -------------------------------------------------------------------------------- 1 | // Ion Text 1.0 ANTLR v4 Grammar 2 | // 3 | // The following grammar does not encode all of the Ion semantics, in particular: 4 | // 5 | // * Timestamps are syntactically defined but the rules of ISO 8601 need to be 6 | // applied (especially regarding day rules with months and leap years). 7 | // * Non $ion_1_0 version markers are not trapped (e.g. $ion_1_1, $ion_2_0) 8 | // * Edge cases around Unicode semantics: 9 | // - ANTLR specifies only four hex digit unicode escapes and on Java operates 10 | // on UTF-16 code units (this is a flaw in ANTLR). 11 | // - The grammar doesn't validate unpaired surrogate escapes in symbols or strings 12 | // (e.g. "\udc00") 13 | 14 | grammar IonText; 15 | 16 | // note that EOF is a concept for the grammar, technically Ion streams 17 | // are infinite 18 | top_level 19 | : (ws* top_level_value)* ws* value? EOF 20 | ; 21 | 22 | top_level_value 23 | : annotation+ top_level_value 24 | | delimiting_entity 25 | // numeric literals (if followed by something), need to be followed by 26 | // whitespace or a token that is either quoted (e.g. string) or 27 | // starts with punctuation (e.g. clob, struct, list) 28 | | numeric_entity ws 29 | | numeric_entity quoted_annotation value 30 | | numeric_entity delimiting_entity 31 | // literals that are unquoted symbols or keywords have a similar requirement 32 | // as the numerics above, they have different productions because the 33 | // rules for numerics are the same in s-expressions, but keywords 34 | // have different rules between top-level and s-expressions. 35 | | keyword_entity ws 36 | | keyword_entity quoted_annotation value 37 | | keyword_entity keyword_delimiting_entity 38 | ; 39 | 40 | // TODO let's make sure this terminology 41 | // is consistent with our specification documents 42 | value 43 | : annotation* entity 44 | ; 45 | 46 | entity 47 | : numeric_entity 48 | | delimiting_entity 49 | | keyword_entity 50 | ; 51 | 52 | delimiting_entity 53 | : quoted_text 54 | | SHORT_QUOTED_CLOB 55 | | LONG_QUOTED_CLOB 56 | | BLOB 57 | | list_type 58 | | sexp 59 | | struct 60 | ; 61 | 62 | keyword_delimiting_entity 63 | : delimiting_entity 64 | | numeric_entity 65 | ; 66 | 67 | keyword_entity 68 | : any_null 69 | | BOOL 70 | | SPECIAL_FLOAT 71 | | IDENTIFIER_SYMBOL 72 | // note that this is because we recognize the type names for null 73 | // they are ordinary symbols on their own 74 | | TYPE 75 | ; 76 | 77 | numeric_entity 78 | : BIN_INTEGER 79 | | DEC_INTEGER 80 | | HEX_INTEGER 81 | | TIMESTAMP 82 | | FLOAT 83 | | DECIMAL 84 | ; 85 | 86 | annotation 87 | : symbol ws* COLON COLON ws* 88 | ; 89 | 90 | quoted_annotation 91 | : QUOTED_SYMBOL ws* COLON COLON ws* 92 | ; 93 | 94 | list_type 95 | : L_BRACKET ws* value ws* (COMMA ws* value)* ws* (COMMA ws*)? R_BRACKET 96 | | L_BRACKET ws* R_BRACKET 97 | ; 98 | 99 | sexp 100 | : L_PAREN (ws* sexp_value)* ws* value? R_PAREN 101 | ; 102 | 103 | sexp_value 104 | : annotation+ sexp_value 105 | | sexp_delimiting_entity 106 | | operator 107 | // much like at the top level, numeric/identifiers/keywords 108 | // have similar delimiting rules 109 | | numeric_entity ws 110 | | numeric_entity quoted_annotation value 111 | | numeric_entity sexp_delimiting_entity 112 | | sexp_keyword_entity ws 113 | | sexp_keyword_entity quoted_annotation value 114 | | sexp_keyword_entity sexp_keyword_delimiting_entity 115 | | NULL ws 116 | | NULL quoted_annotation value 117 | | NULL sexp_null_delimiting_entity 118 | ; 119 | 120 | sexp_delimiting_entity 121 | : delimiting_entity 122 | ; 123 | 124 | sexp_keyword_delimiting_entity 125 | : sexp_delimiting_entity 126 | | numeric_entity 127 | | operator 128 | ; 129 | 130 | sexp_null_delimiting_entity 131 | : delimiting_entity 132 | | NON_DOT_OPERATOR+ 133 | ; 134 | 135 | sexp_keyword_entity 136 | : typed_null 137 | | BOOL 138 | | SPECIAL_FLOAT 139 | | IDENTIFIER_SYMBOL 140 | // note that this is because we recognize the type names for null 141 | // they are ordinary symbols on their own 142 | | TYPE 143 | ; 144 | 145 | operator 146 | : (DOT | NON_DOT_OPERATOR)+ 147 | ; 148 | 149 | struct 150 | : L_CURLY ws* field (ws* COMMA ws* field)* ws* (COMMA ws*)? R_CURLY 151 | | L_CURLY ws* R_CURLY 152 | ; 153 | 154 | field 155 | : field_name ws* COLON ws* annotation* entity 156 | ; 157 | 158 | any_null 159 | : NULL 160 | | typed_null 161 | ; 162 | 163 | typed_null 164 | : NULL DOT NULL 165 | | NULL DOT TYPE 166 | ; 167 | 168 | field_name 169 | : symbol 170 | | SHORT_QUOTED_STRING 171 | | (ws* LONG_QUOTED_STRING)+ 172 | ; 173 | 174 | quoted_text 175 | : QUOTED_SYMBOL 176 | | SHORT_QUOTED_STRING 177 | | (ws* LONG_QUOTED_STRING)+ 178 | ; 179 | 180 | symbol 181 | : IDENTIFIER_SYMBOL 182 | // note that this is because we recognize the type names for null 183 | // they are ordinary symbols on their own 184 | | TYPE 185 | | QUOTED_SYMBOL 186 | ; 187 | 188 | ws 189 | : WHITESPACE 190 | | INLINE_COMMENT 191 | | BLOCK_COMMENT 192 | ; 193 | 194 | ////////////////////////////////////////////////////////////////////////////// 195 | // Ion Punctuation 196 | ////////////////////////////////////////////////////////////////////////////// 197 | 198 | L_BRACKET : '['; 199 | R_BRACKET : ']'; 200 | L_PAREN : '('; 201 | R_PAREN : ')'; 202 | L_CURLY : '{'; 203 | R_CURLY : '}'; 204 | COMMA : ','; 205 | COLON : ':'; 206 | DOT : '.'; 207 | 208 | NON_DOT_OPERATOR 209 | : [!#%&*+\-/;<=>?@^`|~] 210 | ; 211 | 212 | ////////////////////////////////////////////////////////////////////////////// 213 | // Ion Whitespace / Comments 214 | ////////////////////////////////////////////////////////////////////////////// 215 | 216 | WHITESPACE 217 | : WS+ 218 | ; 219 | 220 | INLINE_COMMENT 221 | : '//' .*? (NL | EOF) 222 | ; 223 | 224 | BLOCK_COMMENT 225 | : '/*' .*? '*/' 226 | ; 227 | 228 | ////////////////////////////////////////////////////////////////////////////// 229 | // Ion Null 230 | ////////////////////////////////////////////////////////////////////////////// 231 | 232 | NULL 233 | : 'null' 234 | ; 235 | 236 | TYPE 237 | : 'bool' 238 | | 'int' 239 | | 'float' 240 | | 'decimal' 241 | | 'timestamp' 242 | | 'symbol' 243 | | 'string' 244 | | 'clob' 245 | | 'blob' 246 | | 'list' 247 | | 'sexp' 248 | | 'struct' 249 | ; 250 | 251 | ////////////////////////////////////////////////////////////////////////////// 252 | // Ion Bool 253 | ////////////////////////////////////////////////////////////////////////////// 254 | 255 | BOOL 256 | : 'true' 257 | | 'false' 258 | ; 259 | 260 | ////////////////////////////////////////////////////////////////////////////// 261 | // Ion Timestamp 262 | ////////////////////////////////////////////////////////////////////////////// 263 | 264 | TIMESTAMP 265 | : DATE ('T' TIME?)? 266 | | YEAR '-' MONTH 'T' 267 | | YEAR 'T' 268 | ; 269 | 270 | fragment 271 | DATE 272 | : YEAR '-' MONTH '-' DAY 273 | ; 274 | 275 | fragment 276 | YEAR 277 | : '000' [1-9] 278 | | '00' [1-9] DEC_DIGIT 279 | | '0' [1-9] DEC_DIGIT DEC_DIGIT 280 | | [1-9] DEC_DIGIT DEC_DIGIT DEC_DIGIT 281 | ; 282 | 283 | fragment 284 | MONTH 285 | : '0' [1-9] 286 | | '1' [0-2] 287 | ; 288 | 289 | fragment 290 | DAY 291 | : '0' [1-9] 292 | | [1-2] DEC_DIGIT 293 | | '3' [0-1] 294 | ; 295 | 296 | fragment 297 | TIME 298 | : HOUR ':' MINUTE (':' SECOND)? OFFSET 299 | ; 300 | 301 | fragment 302 | OFFSET 303 | : 'Z' 304 | | PLUS_OR_MINUS HOUR ':' MINUTE 305 | ; 306 | 307 | fragment 308 | HOUR 309 | : [01] DEC_DIGIT 310 | | '2' [0-3] 311 | ; 312 | 313 | fragment 314 | MINUTE 315 | : [0-5] DEC_DIGIT 316 | ; 317 | 318 | // note that W3C spec requires a digit after the '.' 319 | fragment 320 | SECOND 321 | : [0-5] DEC_DIGIT ('.' DEC_DIGIT+)? 322 | ; 323 | 324 | ////////////////////////////////////////////////////////////////////////////// 325 | // Ion Int 326 | ////////////////////////////////////////////////////////////////////////////// 327 | 328 | BIN_INTEGER 329 | : '-'? '0' [bB] BINARY_DIGIT (UNDERSCORE? BINARY_DIGIT)* 330 | ; 331 | 332 | DEC_INTEGER 333 | : '-'? DEC_UNSIGNED_INTEGER 334 | ; 335 | 336 | HEX_INTEGER 337 | : '-'? '0' [xX] HEX_DIGIT (UNDERSCORE? HEX_DIGIT)* 338 | ; 339 | 340 | ////////////////////////////////////////////////////////////////////////////// 341 | // Ion Float 342 | ////////////////////////////////////////////////////////////////////////////// 343 | 344 | SPECIAL_FLOAT 345 | : PLUS_OR_MINUS 'inf' 346 | | 'nan' 347 | ; 348 | 349 | FLOAT 350 | : DEC_INTEGER DEC_FRAC? FLOAT_EXP 351 | ; 352 | 353 | fragment 354 | FLOAT_EXP 355 | : [Ee] PLUS_OR_MINUS? DEC_DIGIT+ 356 | ; 357 | 358 | ////////////////////////////////////////////////////////////////////////////// 359 | // Ion Decimal 360 | ////////////////////////////////////////////////////////////////////////////// 361 | 362 | DECIMAL 363 | : DEC_INTEGER DEC_FRAC? DECIMAL_EXP? 364 | ; 365 | 366 | fragment 367 | DECIMAL_EXP 368 | : [Dd] PLUS_OR_MINUS? DEC_DIGIT+ 369 | ; 370 | 371 | ////////////////////////////////////////////////////////////////////////////// 372 | // Ion Symbol 373 | ////////////////////////////////////////////////////////////////////////////// 374 | 375 | QUOTED_SYMBOL 376 | : SYMBOL_QUOTE SYMBOL_TEXT SYMBOL_QUOTE 377 | ; 378 | 379 | fragment 380 | SYMBOL_TEXT 381 | : (TEXT_ESCAPE | SYMBOL_TEXT_ALLOWED)* 382 | ; 383 | 384 | // non-control Unicode and not single quote or backslash 385 | fragment 386 | SYMBOL_TEXT_ALLOWED 387 | : ~[\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u000A\u000D\u000E\u000F\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F\u0027\u005C] 388 | // : '\u0020'..'\u0026' // no C1 control characters and no U+0027 single quote 389 | // | '\u0028'..'\u005B' // no U+005C backslash 390 | // | '\u005D'..'\uFFFF' // should be up to U+10FFFF 391 | // | WS_NOT_NL 392 | ; 393 | 394 | IDENTIFIER_SYMBOL 395 | : [$_a-zA-Z] ([$_a-zA-Z] | DEC_DIGIT)* 396 | ; 397 | 398 | ////////////////////////////////////////////////////////////////////////////// 399 | // Ion String 400 | ////////////////////////////////////////////////////////////////////////////// 401 | 402 | SHORT_QUOTED_STRING 403 | : SHORT_QUOTE STRING_SHORT_TEXT SHORT_QUOTE 404 | ; 405 | 406 | LONG_QUOTED_STRING 407 | : LONG_QUOTE STRING_LONG_TEXT LONG_QUOTE 408 | ; 409 | 410 | 411 | fragment 412 | STRING_SHORT_TEXT 413 | : (TEXT_ESCAPE | STRING_SHORT_TEXT_ALLOWED)* 414 | ; 415 | 416 | fragment 417 | STRING_LONG_TEXT 418 | : (TEXT_ESCAPE | STRING_LONG_TEXT_ALLOWED)*? 419 | ; 420 | 421 | // non-control Unicode and not double quote or backslash 422 | fragment 423 | STRING_SHORT_TEXT_ALLOWED 424 | : ~["\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u000A\u000D\u000E\u000F\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F\u005C] 425 | // : '\u0020'..'\u0021' // no C1 control characters and no U+0022 double quote 426 | // | '\u0023'..'\u005B' // no U+005C backslash 427 | // | '\u005D'..'\uFFFF' // FIXME should be up to U+10FFFF 428 | // | WS_NOT_NL 429 | ; 430 | 431 | // non-control Unicode (newlines are OK) 432 | fragment 433 | STRING_LONG_TEXT_ALLOWED 434 | : ~[\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u000E\u000F\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F\u005C] 435 | // : '\u0020'..'\u005B' // no C1 control characters and no U+005C blackslash 436 | // | '\u005D'..'\uFFFF' // FIXME should be up to U+10FFFF 437 | // | WS 438 | ; 439 | 440 | fragment 441 | TEXT_ESCAPE 442 | : COMMON_ESCAPE | HEX_ESCAPE | UNICODE_ESCAPE 443 | ; 444 | 445 | ////////////////////////////////////////////////////////////////////////////// 446 | // Ion CLOB 447 | ////////////////////////////////////////////////////////////////////////////// 448 | 449 | SHORT_QUOTED_CLOB 450 | : LOB_START WS* SHORT_QUOTE CLOB_SHORT_TEXT SHORT_QUOTE WS* LOB_END 451 | ; 452 | 453 | LONG_QUOTED_CLOB 454 | : LOB_START (WS* LONG_QUOTE CLOB_LONG_TEXT*? LONG_QUOTE)+ WS* LOB_END 455 | ; 456 | 457 | fragment 458 | CLOB_SHORT_TEXT 459 | : (CLOB_ESCAPE | CLOB_SHORT_TEXT_ALLOWED)* 460 | ; 461 | 462 | fragment 463 | CLOB_LONG_TEXT 464 | : CLOB_LONG_TEXT_NO_QUOTE 465 | | '\'' CLOB_LONG_TEXT_NO_QUOTE 466 | | '\'\'' CLOB_LONG_TEXT_NO_QUOTE 467 | ; 468 | 469 | fragment 470 | CLOB_LONG_TEXT_NO_QUOTE 471 | : (CLOB_ESCAPE | CLOB_LONG_TEXT_ALLOWED) 472 | ; 473 | 474 | // non-control ASCII and not double quote or backslash 475 | fragment 476 | CLOB_SHORT_TEXT_ALLOWED 477 | : '\u0020'..'\u0021' // no U+0022 double quote 478 | | '\u0023'..'\u005B' // no U+005C backslash 479 | | '\u005D'..'\u007F' 480 | | WS_NOT_NL 481 | ; 482 | 483 | // non-control ASCII (newlines are OK) 484 | fragment 485 | CLOB_LONG_TEXT_ALLOWED 486 | : '\u0020'..'\u0026' // no U+0027 single quote 487 | | '\u0028'..'\u005B' // no U+005C blackslash 488 | | '\u005D'..'\u007F' 489 | | WS 490 | ; 491 | 492 | fragment 493 | CLOB_ESCAPE 494 | : COMMON_ESCAPE | HEX_ESCAPE 495 | ; 496 | 497 | ////////////////////////////////////////////////////////////////////////////// 498 | // Ion BLOB 499 | ////////////////////////////////////////////////////////////////////////////// 500 | 501 | BLOB 502 | : LOB_START (BASE_64_QUARTET | WS)* BASE_64_PAD? WS* LOB_END 503 | ; 504 | 505 | fragment 506 | BASE_64_PAD 507 | : BASE_64_PAD1 508 | | BASE_64_PAD2 509 | ; 510 | 511 | fragment 512 | BASE_64_QUARTET 513 | : BASE_64_CHAR WS* BASE_64_CHAR WS* BASE_64_CHAR WS* BASE_64_CHAR 514 | ; 515 | 516 | fragment 517 | BASE_64_PAD1 518 | : BASE_64_CHAR WS* BASE_64_CHAR WS* BASE_64_CHAR WS* '=' 519 | ; 520 | 521 | fragment 522 | BASE_64_PAD2 523 | : BASE_64_CHAR WS* BASE_64_CHAR WS* '=' WS* '=' 524 | ; 525 | 526 | fragment 527 | BASE_64_CHAR 528 | : [0-9a-zA-Z+/] 529 | ; 530 | 531 | ////////////////////////////////////////////////////////////////////////////// 532 | // Common Lexer Primitives 533 | ////////////////////////////////////////////////////////////////////////////// 534 | 535 | fragment LOB_START : '{{'; 536 | fragment LOB_END : '}}'; 537 | fragment SYMBOL_QUOTE : '\''; 538 | fragment SHORT_QUOTE : '"'; 539 | fragment LONG_QUOTE : '\'\'\''; 540 | 541 | // Ion does not allow leading zeros for base-10 numbers 542 | fragment 543 | DEC_UNSIGNED_INTEGER 544 | : '0' 545 | | [1-9] (UNDERSCORE? DEC_DIGIT)* 546 | ; 547 | 548 | fragment 549 | DEC_FRAC 550 | : '.' 551 | | '.' DEC_DIGIT (UNDERSCORE? DEC_DIGIT)* 552 | ; 553 | 554 | fragment 555 | DEC_DIGIT 556 | : [0-9] 557 | ; 558 | 559 | fragment 560 | HEX_DIGIT 561 | : [0-9a-fA-F] 562 | ; 563 | 564 | fragment 565 | BINARY_DIGIT 566 | : [01] 567 | ; 568 | 569 | fragment 570 | PLUS_OR_MINUS 571 | : [+\-] 572 | ; 573 | 574 | fragment 575 | COMMON_ESCAPE 576 | : '\\' COMMON_ESCAPE_CODE 577 | ; 578 | 579 | fragment 580 | COMMON_ESCAPE_CODE 581 | : 'a' 582 | | 'b' 583 | | 't' 584 | | 'n' 585 | | 'f' 586 | | 'r' 587 | | 'v' 588 | | '?' 589 | | '0' 590 | | '\'' 591 | | '"' 592 | | '/' 593 | | '\\' 594 | | NL 595 | ; 596 | 597 | fragment 598 | HEX_ESCAPE 599 | : '\\x' HEX_DIGIT HEX_DIGIT 600 | ; 601 | 602 | fragment 603 | UNICODE_ESCAPE 604 | : '\\u' HEX_DIGIT_QUARTET 605 | | '\\U000' HEX_DIGIT_QUARTET HEX_DIGIT 606 | | '\\U0010' HEX_DIGIT_QUARTET 607 | ; 608 | 609 | fragment 610 | HEX_DIGIT_QUARTET 611 | : HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT 612 | ; 613 | 614 | fragment 615 | WS 616 | : WS_NOT_NL 617 | | '\u000A' // line feed 618 | | '\u000D' // carriage return 619 | ; 620 | 621 | fragment 622 | NL 623 | : '\u000D\u000A' // carriage return + line feed 624 | | '\u000D' // carriage return 625 | | '\u000A' // line feed 626 | ; 627 | 628 | fragment 629 | WS_NOT_NL 630 | : '\u0009' // tab 631 | | '\u000B' // vertical tab 632 | | '\u000C' // form feed 633 | | '\u0020' // space 634 | ; 635 | 636 | fragment 637 | UNDERSCORE 638 | : '_' 639 | ; 640 | -------------------------------------------------------------------------------- /pionic/antlr/IonText.tokens: -------------------------------------------------------------------------------- 1 | L_BRACKET=1 2 | R_BRACKET=2 3 | L_PAREN=3 4 | R_PAREN=4 5 | L_CURLY=5 6 | R_CURLY=6 7 | COMMA=7 8 | COLON=8 9 | DOT=9 10 | NON_DOT_OPERATOR=10 11 | WHITESPACE=11 12 | INLINE_COMMENT=12 13 | BLOCK_COMMENT=13 14 | NULL=14 15 | TYPE=15 16 | BOOL=16 17 | TIMESTAMP=17 18 | BIN_INTEGER=18 19 | DEC_INTEGER=19 20 | HEX_INTEGER=20 21 | SPECIAL_FLOAT=21 22 | FLOAT=22 23 | DECIMAL=23 24 | QUOTED_SYMBOL=24 25 | IDENTIFIER_SYMBOL=25 26 | SHORT_QUOTED_STRING=26 27 | LONG_QUOTED_STRING=27 28 | SHORT_QUOTED_CLOB=28 29 | LONG_QUOTED_CLOB=29 30 | BLOB=30 31 | '['=1 32 | ']'=2 33 | '('=3 34 | ')'=4 35 | '{'=5 36 | '}'=6 37 | ','=7 38 | ':'=8 39 | '.'=9 40 | 'null'=14 41 | -------------------------------------------------------------------------------- /pionic/antlr/IonTextLexer.py: -------------------------------------------------------------------------------- 1 | # Generated from IonText.g4 by ANTLR 4.7 2 | from antlr4 import * 3 | from io import StringIO 4 | from typing.io import TextIO 5 | import sys 6 | 7 | 8 | def serializedATN(): 9 | with StringIO() as buf: 10 | buf.write("\3\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964\2 ") 11 | buf.write("\u0315\b\1\4\2\t\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7") 12 | buf.write("\t\7\4\b\t\b\4\t\t\t\4\n\t\n\4\13\t\13\4\f\t\f\4\r\t\r") 13 | buf.write("\4\16\t\16\4\17\t\17\4\20\t\20\4\21\t\21\4\22\t\22\4\23") 14 | buf.write("\t\23\4\24\t\24\4\25\t\25\4\26\t\26\4\27\t\27\4\30\t\30") 15 | buf.write("\4\31\t\31\4\32\t\32\4\33\t\33\4\34\t\34\4\35\t\35\4\36") 16 | buf.write("\t\36\4\37\t\37\4 \t \4!\t!\4\"\t\"\4#\t#\4$\t$\4%\t%") 17 | buf.write("\4&\t&\4\'\t\'\4(\t(\4)\t)\4*\t*\4+\t+\4,\t,\4-\t-\4.") 18 | buf.write("\t.\4/\t/\4\60\t\60\4\61\t\61\4\62\t\62\4\63\t\63\4\64") 19 | buf.write("\t\64\4\65\t\65\4\66\t\66\4\67\t\67\48\t8\49\t9\4:\t:") 20 | buf.write("\4;\t;\4<\t<\4=\t=\4>\t>\4?\t?\4@\t@\4A\tA\4B\tB\4C\t") 21 | buf.write("C\4D\tD\4E\tE\4F\tF\4G\tG\4H\tH\4I\tI\4J\tJ\4K\tK\4L\t") 22 | buf.write("L\4M\tM\4N\tN\4O\tO\4P\tP\3\2\3\2\3\3\3\3\3\4\3\4\3\5") 23 | buf.write("\3\5\3\6\3\6\3\7\3\7\3\b\3\b\3\t\3\t\3\n\3\n\3\13\3\13") 24 | buf.write("\3\f\6\f\u00b7\n\f\r\f\16\f\u00b8\3\r\3\r\3\r\3\r\7\r") 25 | buf.write("\u00bf\n\r\f\r\16\r\u00c2\13\r\3\r\3\r\5\r\u00c6\n\r\3") 26 | buf.write("\16\3\16\3\16\3\16\7\16\u00cc\n\16\f\16\16\16\u00cf\13") 27 | buf.write("\16\3\16\3\16\3\16\3\17\3\17\3\17\3\17\3\17\3\20\3\20") 28 | buf.write("\3\20\3\20\3\20\3\20\3\20\3\20\3\20\3\20\3\20\3\20\3\20") 29 | buf.write("\3\20\3\20\3\20\3\20\3\20\3\20\3\20\3\20\3\20\3\20\3\20") 30 | buf.write("\3\20\3\20\3\20\3\20\3\20\3\20\3\20\3\20\3\20\3\20\3\20") 31 | buf.write("\3\20\3\20\3\20\3\20\3\20\3\20\3\20\3\20\3\20\3\20\3\20") 32 | buf.write("\3\20\3\20\3\20\3\20\3\20\3\20\3\20\3\20\3\20\3\20\3\20") 33 | buf.write("\3\20\3\20\3\20\3\20\3\20\5\20\u0117\n\20\3\21\3\21\3") 34 | buf.write("\21\3\21\3\21\3\21\3\21\3\21\3\21\5\21\u0122\n\21\3\22") 35 | buf.write("\3\22\3\22\5\22\u0127\n\22\5\22\u0129\n\22\3\22\3\22\3") 36 | buf.write("\22\3\22\3\22\3\22\3\22\3\22\5\22\u0133\n\22\3\23\3\23") 37 | buf.write("\3\23\3\23\3\23\3\23\3\24\3\24\3\24\3\24\3\24\3\24\3\24") 38 | buf.write("\3\24\3\24\3\24\3\24\3\24\3\24\3\24\3\24\3\24\3\24\3\24") 39 | buf.write("\3\24\3\24\5\24\u014f\n\24\3\25\3\25\3\25\3\25\5\25\u0155") 40 | buf.write("\n\25\3\26\3\26\3\26\3\26\3\26\3\26\5\26\u015d\n\26\3") 41 | buf.write("\27\3\27\3\27\3\27\3\27\5\27\u0164\n\27\3\27\3\27\3\30") 42 | buf.write("\3\30\3\30\3\30\3\30\3\30\5\30\u016e\n\30\3\31\3\31\3") 43 | buf.write("\31\3\31\5\31\u0174\n\31\3\32\3\32\3\32\3\33\3\33\3\33") 44 | buf.write("\3\33\6\33\u017d\n\33\r\33\16\33\u017e\5\33\u0181\n\33") 45 | buf.write("\3\34\5\34\u0184\n\34\3\34\3\34\3\34\3\34\5\34\u018a\n") 46 | buf.write("\34\3\34\7\34\u018d\n\34\f\34\16\34\u0190\13\34\3\35\5") 47 | buf.write("\35\u0193\n\35\3\35\3\35\3\36\5\36\u0198\n\36\3\36\3\36") 48 | buf.write("\3\36\3\36\5\36\u019e\n\36\3\36\7\36\u01a1\n\36\f\36\16") 49 | buf.write("\36\u01a4\13\36\3\37\3\37\3\37\3\37\3\37\3\37\3\37\3\37") 50 | buf.write("\5\37\u01ae\n\37\3 \3 \5 \u01b2\n \3 \3 \3!\3!\5!\u01b8") 51 | buf.write("\n!\3!\6!\u01bb\n!\r!\16!\u01bc\3\"\3\"\5\"\u01c1\n\"") 52 | buf.write("\3\"\5\"\u01c4\n\"\3#\3#\5#\u01c8\n#\3#\6#\u01cb\n#\r") 53 | buf.write("#\16#\u01cc\3$\3$\3$\3$\3%\3%\7%\u01d5\n%\f%\16%\u01d8") 54 | buf.write("\13%\3&\3&\3\'\3\'\3\'\7\'\u01df\n\'\f\'\16\'\u01e2\13") 55 | buf.write("\'\3(\3(\3(\3(\3)\3)\3)\3)\3*\3*\7*\u01ee\n*\f*\16*\u01f1") 56 | buf.write("\13*\3+\3+\7+\u01f5\n+\f+\16+\u01f8\13+\3,\3,\3-\3-\3") 57 | buf.write(".\3.\3.\5.\u0201\n.\3/\3/\7/\u0205\n/\f/\16/\u0208\13") 58 | buf.write("/\3/\3/\3/\3/\7/\u020e\n/\f/\16/\u0211\13/\3/\3/\3\60") 59 | buf.write("\3\60\7\60\u0217\n\60\f\60\16\60\u021a\13\60\3\60\3\60") 60 | buf.write("\7\60\u021e\n\60\f\60\16\60\u0221\13\60\3\60\3\60\6\60") 61 | buf.write("\u0225\n\60\r\60\16\60\u0226\3\60\7\60\u022a\n\60\f\60") 62 | buf.write("\16\60\u022d\13\60\3\60\3\60\3\61\3\61\7\61\u0233\n\61") 63 | buf.write("\f\61\16\61\u0236\13\61\3\62\3\62\3\62\3\62\3\62\3\62") 64 | buf.write("\3\62\5\62\u023f\n\62\3\63\3\63\5\63\u0243\n\63\3\64\3") 65 | buf.write("\64\5\64\u0247\n\64\3\65\3\65\5\65\u024b\n\65\3\66\3\66") 66 | buf.write("\5\66\u024f\n\66\3\67\3\67\3\67\7\67\u0254\n\67\f\67\16") 67 | buf.write("\67\u0257\13\67\3\67\5\67\u025a\n\67\3\67\7\67\u025d\n") 68 | buf.write("\67\f\67\16\67\u0260\13\67\3\67\3\67\38\38\58\u0266\n") 69 | buf.write("8\39\39\79\u026a\n9\f9\169\u026d\139\39\39\79\u0271\n") 70 | buf.write("9\f9\169\u0274\139\39\39\79\u0278\n9\f9\169\u027b\139") 71 | buf.write("\39\39\3:\3:\7:\u0281\n:\f:\16:\u0284\13:\3:\3:\7:\u0288") 72 | buf.write("\n:\f:\16:\u028b\13:\3:\3:\7:\u028f\n:\f:\16:\u0292\13") 73 | buf.write(":\3:\3:\3;\3;\7;\u0298\n;\f;\16;\u029b\13;\3;\3;\7;\u029f") 74 | buf.write("\n;\f;\16;\u02a2\13;\3;\3;\7;\u02a6\n;\f;\16;\u02a9\13") 75 | buf.write(";\3;\3;\3<\3<\3=\3=\3=\3>\3>\3>\3?\3?\3@\3@\3A\3A\3A\3") 76 | buf.write("A\3B\3B\3B\5B\u02c0\nB\3B\7B\u02c3\nB\fB\16B\u02c6\13") 77 | buf.write("B\5B\u02c8\nB\3C\3C\3C\3C\5C\u02ce\nC\3C\7C\u02d1\nC\f") 78 | buf.write("C\16C\u02d4\13C\5C\u02d6\nC\3D\3D\3E\3E\3F\3F\3G\3G\3") 79 | buf.write("H\3H\3H\3I\3I\5I\u02e5\nI\3J\3J\3J\3J\3J\3J\3K\3K\3K\3") 80 | buf.write("K\3K\3K\3K\3K\3K\3K\3K\3K\3K\3K\3K\3K\3K\3K\3K\3K\3K\5") 81 | buf.write("K\u0302\nK\3L\3L\3L\3L\3L\3M\3M\5M\u030b\nM\3N\3N\3N\5") 82 | buf.write("N\u0310\nN\3O\3O\3P\3P\6\u00c0\u00cd\u01f6\u021f\2Q\3") 83 | buf.write("\3\5\4\7\5\t\6\13\7\r\b\17\t\21\n\23\13\25\f\27\r\31\16") 84 | buf.write("\33\17\35\20\37\21!\22#\23%\2\'\2)\2+\2-\2/\2\61\2\63") 85 | buf.write("\2\65\2\67\249\25;\26=\27?\30A\2C\31E\2G\32I\2K\2M\33") 86 | buf.write("O\34Q\35S\2U\2W\2Y\2[\2]\36_\37a\2c\2e\2g\2i\2k\2m o\2") 87 | buf.write("q\2s\2u\2w\2y\2{\2}\2\177\2\u0081\2\u0083\2\u0085\2\u0087") 88 | buf.write("\2\u0089\2\u008b\2\u008d\2\u008f\2\u0091\2\u0093\2\u0095") 89 | buf.write("\2\u0097\2\u0099\2\u009b\2\u009d\2\u009f\2\3\2\32\r\2") 90 | buf.write("##%%\'(,-//\61\61=B``bb~~\u0080\u0080\3\2\63;\3\2\62\64") 91 | buf.write("\3\2\63\64\3\2\62\63\3\2\62\65\3\2\62\67\4\2DDdd\4\2Z") 92 | buf.write("Zzz\4\2GGgg\4\2FFff\7\2\2\n\f\f\17!))^^\6\2&&C\\aac|\7") 93 | buf.write("\2\2\n\f\f\17!$$^^\5\2\2\n\20!^^\5\2\"#%]_\u0081\5\2\"") 94 | buf.write("(*]_\u0081\6\2--\61;C\\c|\3\2\62;\5\2\62;CHch\4\2--//") 95 | buf.write("\r\2$$))\61\62AA^^cdhhppttvvxx\4\2\f\f\17\17\5\2\13\13") 96 | buf.write("\r\16\"\"\2\u0342\2\3\3\2\2\2\2\5\3\2\2\2\2\7\3\2\2\2") 97 | buf.write("\2\t\3\2\2\2\2\13\3\2\2\2\2\r\3\2\2\2\2\17\3\2\2\2\2\21") 98 | buf.write("\3\2\2\2\2\23\3\2\2\2\2\25\3\2\2\2\2\27\3\2\2\2\2\31\3") 99 | buf.write("\2\2\2\2\33\3\2\2\2\2\35\3\2\2\2\2\37\3\2\2\2\2!\3\2\2") 100 | buf.write("\2\2#\3\2\2\2\2\67\3\2\2\2\29\3\2\2\2\2;\3\2\2\2\2=\3") 101 | buf.write("\2\2\2\2?\3\2\2\2\2C\3\2\2\2\2G\3\2\2\2\2M\3\2\2\2\2O") 102 | buf.write("\3\2\2\2\2Q\3\2\2\2\2]\3\2\2\2\2_\3\2\2\2\2m\3\2\2\2\3") 103 | buf.write("\u00a1\3\2\2\2\5\u00a3\3\2\2\2\7\u00a5\3\2\2\2\t\u00a7") 104 | buf.write("\3\2\2\2\13\u00a9\3\2\2\2\r\u00ab\3\2\2\2\17\u00ad\3\2") 105 | buf.write("\2\2\21\u00af\3\2\2\2\23\u00b1\3\2\2\2\25\u00b3\3\2\2") 106 | buf.write("\2\27\u00b6\3\2\2\2\31\u00ba\3\2\2\2\33\u00c7\3\2\2\2") 107 | buf.write("\35\u00d3\3\2\2\2\37\u0116\3\2\2\2!\u0121\3\2\2\2#\u0132") 108 | buf.write("\3\2\2\2%\u0134\3\2\2\2\'\u014e\3\2\2\2)\u0154\3\2\2\2") 109 | buf.write("+\u015c\3\2\2\2-\u015e\3\2\2\2/\u016d\3\2\2\2\61\u0173") 110 | buf.write("\3\2\2\2\63\u0175\3\2\2\2\65\u0178\3\2\2\2\67\u0183\3") 111 | buf.write("\2\2\29\u0192\3\2\2\2;\u0197\3\2\2\2=\u01ad\3\2\2\2?\u01af") 112 | buf.write("\3\2\2\2A\u01b5\3\2\2\2C\u01be\3\2\2\2E\u01c5\3\2\2\2") 113 | buf.write("G\u01ce\3\2\2\2I\u01d6\3\2\2\2K\u01d9\3\2\2\2M\u01db\3") 114 | buf.write("\2\2\2O\u01e3\3\2\2\2Q\u01e7\3\2\2\2S\u01ef\3\2\2\2U\u01f6") 115 | buf.write("\3\2\2\2W\u01f9\3\2\2\2Y\u01fb\3\2\2\2[\u0200\3\2\2\2") 116 | buf.write("]\u0202\3\2\2\2_\u0214\3\2\2\2a\u0234\3\2\2\2c\u023e\3") 117 | buf.write("\2\2\2e\u0242\3\2\2\2g\u0246\3\2\2\2i\u024a\3\2\2\2k\u024e") 118 | buf.write("\3\2\2\2m\u0250\3\2\2\2o\u0265\3\2\2\2q\u0267\3\2\2\2") 119 | buf.write("s\u027e\3\2\2\2u\u0295\3\2\2\2w\u02ac\3\2\2\2y\u02ae\3") 120 | buf.write("\2\2\2{\u02b1\3\2\2\2}\u02b4\3\2\2\2\177\u02b6\3\2\2\2") 121 | buf.write("\u0081\u02b8\3\2\2\2\u0083\u02c7\3\2\2\2\u0085\u02d5\3") 122 | buf.write("\2\2\2\u0087\u02d7\3\2\2\2\u0089\u02d9\3\2\2\2\u008b\u02db") 123 | buf.write("\3\2\2\2\u008d\u02dd\3\2\2\2\u008f\u02df\3\2\2\2\u0091") 124 | buf.write("\u02e4\3\2\2\2\u0093\u02e6\3\2\2\2\u0095\u0301\3\2\2\2") 125 | buf.write("\u0097\u0303\3\2\2\2\u0099\u030a\3\2\2\2\u009b\u030f\3") 126 | buf.write("\2\2\2\u009d\u0311\3\2\2\2\u009f\u0313\3\2\2\2\u00a1\u00a2") 127 | buf.write("\7]\2\2\u00a2\4\3\2\2\2\u00a3\u00a4\7_\2\2\u00a4\6\3\2") 128 | buf.write("\2\2\u00a5\u00a6\7*\2\2\u00a6\b\3\2\2\2\u00a7\u00a8\7") 129 | buf.write("+\2\2\u00a8\n\3\2\2\2\u00a9\u00aa\7}\2\2\u00aa\f\3\2\2") 130 | buf.write("\2\u00ab\u00ac\7\177\2\2\u00ac\16\3\2\2\2\u00ad\u00ae") 131 | buf.write("\7.\2\2\u00ae\20\3\2\2\2\u00af\u00b0\7<\2\2\u00b0\22\3") 132 | buf.write("\2\2\2\u00b1\u00b2\7\60\2\2\u00b2\24\3\2\2\2\u00b3\u00b4") 133 | buf.write("\t\2\2\2\u00b4\26\3\2\2\2\u00b5\u00b7\5\u0099M\2\u00b6") 134 | buf.write("\u00b5\3\2\2\2\u00b7\u00b8\3\2\2\2\u00b8\u00b6\3\2\2\2") 135 | buf.write("\u00b8\u00b9\3\2\2\2\u00b9\30\3\2\2\2\u00ba\u00bb\7\61") 136 | buf.write("\2\2\u00bb\u00bc\7\61\2\2\u00bc\u00c0\3\2\2\2\u00bd\u00bf") 137 | buf.write("\13\2\2\2\u00be\u00bd\3\2\2\2\u00bf\u00c2\3\2\2\2\u00c0") 138 | buf.write("\u00c1\3\2\2\2\u00c0\u00be\3\2\2\2\u00c1\u00c5\3\2\2\2") 139 | buf.write("\u00c2\u00c0\3\2\2\2\u00c3\u00c6\5\u009bN\2\u00c4\u00c6") 140 | buf.write("\7\2\2\3\u00c5\u00c3\3\2\2\2\u00c5\u00c4\3\2\2\2\u00c6") 141 | buf.write("\32\3\2\2\2\u00c7\u00c8\7\61\2\2\u00c8\u00c9\7,\2\2\u00c9") 142 | buf.write("\u00cd\3\2\2\2\u00ca\u00cc\13\2\2\2\u00cb\u00ca\3\2\2") 143 | buf.write("\2\u00cc\u00cf\3\2\2\2\u00cd\u00ce\3\2\2\2\u00cd\u00cb") 144 | buf.write("\3\2\2\2\u00ce\u00d0\3\2\2\2\u00cf\u00cd\3\2\2\2\u00d0") 145 | buf.write("\u00d1\7,\2\2\u00d1\u00d2\7\61\2\2\u00d2\34\3\2\2\2\u00d3") 146 | buf.write("\u00d4\7p\2\2\u00d4\u00d5\7w\2\2\u00d5\u00d6\7n\2\2\u00d6") 147 | buf.write("\u00d7\7n\2\2\u00d7\36\3\2\2\2\u00d8\u00d9\7d\2\2\u00d9") 148 | buf.write("\u00da\7q\2\2\u00da\u00db\7q\2\2\u00db\u0117\7n\2\2\u00dc") 149 | buf.write("\u00dd\7k\2\2\u00dd\u00de\7p\2\2\u00de\u0117\7v\2\2\u00df") 150 | buf.write("\u00e0\7h\2\2\u00e0\u00e1\7n\2\2\u00e1\u00e2\7q\2\2\u00e2") 151 | buf.write("\u00e3\7c\2\2\u00e3\u0117\7v\2\2\u00e4\u00e5\7f\2\2\u00e5") 152 | buf.write("\u00e6\7g\2\2\u00e6\u00e7\7e\2\2\u00e7\u00e8\7k\2\2\u00e8") 153 | buf.write("\u00e9\7o\2\2\u00e9\u00ea\7c\2\2\u00ea\u0117\7n\2\2\u00eb") 154 | buf.write("\u00ec\7v\2\2\u00ec\u00ed\7k\2\2\u00ed\u00ee\7o\2\2\u00ee") 155 | buf.write("\u00ef\7g\2\2\u00ef\u00f0\7u\2\2\u00f0\u00f1\7v\2\2\u00f1") 156 | buf.write("\u00f2\7c\2\2\u00f2\u00f3\7o\2\2\u00f3\u0117\7r\2\2\u00f4") 157 | buf.write("\u00f5\7u\2\2\u00f5\u00f6\7{\2\2\u00f6\u00f7\7o\2\2\u00f7") 158 | buf.write("\u00f8\7d\2\2\u00f8\u00f9\7q\2\2\u00f9\u0117\7n\2\2\u00fa") 159 | buf.write("\u00fb\7u\2\2\u00fb\u00fc\7v\2\2\u00fc\u00fd\7t\2\2\u00fd") 160 | buf.write("\u00fe\7k\2\2\u00fe\u00ff\7p\2\2\u00ff\u0117\7i\2\2\u0100") 161 | buf.write("\u0101\7e\2\2\u0101\u0102\7n\2\2\u0102\u0103\7q\2\2\u0103") 162 | buf.write("\u0117\7d\2\2\u0104\u0105\7d\2\2\u0105\u0106\7n\2\2\u0106") 163 | buf.write("\u0107\7q\2\2\u0107\u0117\7d\2\2\u0108\u0109\7n\2\2\u0109") 164 | buf.write("\u010a\7k\2\2\u010a\u010b\7u\2\2\u010b\u0117\7v\2\2\u010c") 165 | buf.write("\u010d\7u\2\2\u010d\u010e\7g\2\2\u010e\u010f\7z\2\2\u010f") 166 | buf.write("\u0117\7r\2\2\u0110\u0111\7u\2\2\u0111\u0112\7v\2\2\u0112") 167 | buf.write("\u0113\7t\2\2\u0113\u0114\7w\2\2\u0114\u0115\7e\2\2\u0115") 168 | buf.write("\u0117\7v\2\2\u0116\u00d8\3\2\2\2\u0116\u00dc\3\2\2\2") 169 | buf.write("\u0116\u00df\3\2\2\2\u0116\u00e4\3\2\2\2\u0116\u00eb\3") 170 | buf.write("\2\2\2\u0116\u00f4\3\2\2\2\u0116\u00fa\3\2\2\2\u0116\u0100") 171 | buf.write("\3\2\2\2\u0116\u0104\3\2\2\2\u0116\u0108\3\2\2\2\u0116") 172 | buf.write("\u010c\3\2\2\2\u0116\u0110\3\2\2\2\u0117 \3\2\2\2\u0118") 173 | buf.write("\u0119\7v\2\2\u0119\u011a\7t\2\2\u011a\u011b\7w\2\2\u011b") 174 | buf.write("\u0122\7g\2\2\u011c\u011d\7h\2\2\u011d\u011e\7c\2\2\u011e") 175 | buf.write("\u011f\7n\2\2\u011f\u0120\7u\2\2\u0120\u0122\7g\2\2\u0121") 176 | buf.write("\u0118\3\2\2\2\u0121\u011c\3\2\2\2\u0122\"\3\2\2\2\u0123") 177 | buf.write("\u0128\5%\23\2\u0124\u0126\7V\2\2\u0125\u0127\5-\27\2") 178 | buf.write("\u0126\u0125\3\2\2\2\u0126\u0127\3\2\2\2\u0127\u0129\3") 179 | buf.write("\2\2\2\u0128\u0124\3\2\2\2\u0128\u0129\3\2\2\2\u0129\u0133") 180 | buf.write("\3\2\2\2\u012a\u012b\5\'\24\2\u012b\u012c\7/\2\2\u012c") 181 | buf.write("\u012d\5)\25\2\u012d\u012e\7V\2\2\u012e\u0133\3\2\2\2") 182 | buf.write("\u012f\u0130\5\'\24\2\u0130\u0131\7V\2\2\u0131\u0133\3") 183 | buf.write("\2\2\2\u0132\u0123\3\2\2\2\u0132\u012a\3\2\2\2\u0132\u012f") 184 | buf.write("\3\2\2\2\u0133$\3\2\2\2\u0134\u0135\5\'\24\2\u0135\u0136") 185 | buf.write("\7/\2\2\u0136\u0137\5)\25\2\u0137\u0138\7/\2\2\u0138\u0139") 186 | buf.write("\5+\26\2\u0139&\3\2\2\2\u013a\u013b\7\62\2\2\u013b\u013c") 187 | buf.write("\7\62\2\2\u013c\u013d\7\62\2\2\u013d\u013e\3\2\2\2\u013e") 188 | buf.write("\u014f\t\3\2\2\u013f\u0140\7\62\2\2\u0140\u0141\7\62\2") 189 | buf.write("\2\u0141\u0142\3\2\2\2\u0142\u0143\t\3\2\2\u0143\u014f") 190 | buf.write("\5\u0087D\2\u0144\u0145\7\62\2\2\u0145\u0146\t\3\2\2\u0146") 191 | buf.write("\u0147\5\u0087D\2\u0147\u0148\5\u0087D\2\u0148\u014f\3") 192 | buf.write("\2\2\2\u0149\u014a\t\3\2\2\u014a\u014b\5\u0087D\2\u014b") 193 | buf.write("\u014c\5\u0087D\2\u014c\u014d\5\u0087D\2\u014d\u014f\3") 194 | buf.write("\2\2\2\u014e\u013a\3\2\2\2\u014e\u013f\3\2\2\2\u014e\u0144") 195 | buf.write("\3\2\2\2\u014e\u0149\3\2\2\2\u014f(\3\2\2\2\u0150\u0151") 196 | buf.write("\7\62\2\2\u0151\u0155\t\3\2\2\u0152\u0153\7\63\2\2\u0153") 197 | buf.write("\u0155\t\4\2\2\u0154\u0150\3\2\2\2\u0154\u0152\3\2\2\2") 198 | buf.write("\u0155*\3\2\2\2\u0156\u0157\7\62\2\2\u0157\u015d\t\3\2") 199 | buf.write("\2\u0158\u0159\t\5\2\2\u0159\u015d\5\u0087D\2\u015a\u015b") 200 | buf.write("\7\65\2\2\u015b\u015d\t\6\2\2\u015c\u0156\3\2\2\2\u015c") 201 | buf.write("\u0158\3\2\2\2\u015c\u015a\3\2\2\2\u015d,\3\2\2\2\u015e") 202 | buf.write("\u015f\5\61\31\2\u015f\u0160\7<\2\2\u0160\u0163\5\63\32") 203 | buf.write("\2\u0161\u0162\7<\2\2\u0162\u0164\5\65\33\2\u0163\u0161") 204 | buf.write("\3\2\2\2\u0163\u0164\3\2\2\2\u0164\u0165\3\2\2\2\u0165") 205 | buf.write("\u0166\5/\30\2\u0166.\3\2\2\2\u0167\u016e\7\\\2\2\u0168") 206 | buf.write("\u0169\5\u008dG\2\u0169\u016a\5\61\31\2\u016a\u016b\7") 207 | buf.write("<\2\2\u016b\u016c\5\63\32\2\u016c\u016e\3\2\2\2\u016d") 208 | buf.write("\u0167\3\2\2\2\u016d\u0168\3\2\2\2\u016e\60\3\2\2\2\u016f") 209 | buf.write("\u0170\t\6\2\2\u0170\u0174\5\u0087D\2\u0171\u0172\7\64") 210 | buf.write("\2\2\u0172\u0174\t\7\2\2\u0173\u016f\3\2\2\2\u0173\u0171") 211 | buf.write("\3\2\2\2\u0174\62\3\2\2\2\u0175\u0176\t\b\2\2\u0176\u0177") 212 | buf.write("\5\u0087D\2\u0177\64\3\2\2\2\u0178\u0179\t\b\2\2\u0179") 213 | buf.write("\u0180\5\u0087D\2\u017a\u017c\7\60\2\2\u017b\u017d\5\u0087") 214 | buf.write("D\2\u017c\u017b\3\2\2\2\u017d\u017e\3\2\2\2\u017e\u017c") 215 | buf.write("\3\2\2\2\u017e\u017f\3\2\2\2\u017f\u0181\3\2\2\2\u0180") 216 | buf.write("\u017a\3\2\2\2\u0180\u0181\3\2\2\2\u0181\66\3\2\2\2\u0182") 217 | buf.write("\u0184\7/\2\2\u0183\u0182\3\2\2\2\u0183\u0184\3\2\2\2") 218 | buf.write("\u0184\u0185\3\2\2\2\u0185\u0186\7\62\2\2\u0186\u0187") 219 | buf.write("\t\t\2\2\u0187\u018e\5\u008bF\2\u0188\u018a\5\u009fP\2") 220 | buf.write("\u0189\u0188\3\2\2\2\u0189\u018a\3\2\2\2\u018a\u018b\3") 221 | buf.write("\2\2\2\u018b\u018d\5\u008bF\2\u018c\u0189\3\2\2\2\u018d") 222 | buf.write("\u0190\3\2\2\2\u018e\u018c\3\2\2\2\u018e\u018f\3\2\2\2") 223 | buf.write("\u018f8\3\2\2\2\u0190\u018e\3\2\2\2\u0191\u0193\7/\2\2") 224 | buf.write("\u0192\u0191\3\2\2\2\u0192\u0193\3\2\2\2\u0193\u0194\3") 225 | buf.write("\2\2\2\u0194\u0195\5\u0083B\2\u0195:\3\2\2\2\u0196\u0198") 226 | buf.write("\7/\2\2\u0197\u0196\3\2\2\2\u0197\u0198\3\2\2\2\u0198") 227 | buf.write("\u0199\3\2\2\2\u0199\u019a\7\62\2\2\u019a\u019b\t\n\2") 228 | buf.write("\2\u019b\u01a2\5\u0089E\2\u019c\u019e\5\u009fP\2\u019d") 229 | buf.write("\u019c\3\2\2\2\u019d\u019e\3\2\2\2\u019e\u019f\3\2\2\2") 230 | buf.write("\u019f\u01a1\5\u0089E\2\u01a0\u019d\3\2\2\2\u01a1\u01a4") 231 | buf.write("\3\2\2\2\u01a2\u01a0\3\2\2\2\u01a2\u01a3\3\2\2\2\u01a3") 232 | buf.write("<\3\2\2\2\u01a4\u01a2\3\2\2\2\u01a5\u01a6\5\u008dG\2\u01a6") 233 | buf.write("\u01a7\7k\2\2\u01a7\u01a8\7p\2\2\u01a8\u01a9\7h\2\2\u01a9") 234 | buf.write("\u01ae\3\2\2\2\u01aa\u01ab\7p\2\2\u01ab\u01ac\7c\2\2\u01ac") 235 | buf.write("\u01ae\7p\2\2\u01ad\u01a5\3\2\2\2\u01ad\u01aa\3\2\2\2") 236 | buf.write("\u01ae>\3\2\2\2\u01af\u01b1\59\35\2\u01b0\u01b2\5\u0085") 237 | buf.write("C\2\u01b1\u01b0\3\2\2\2\u01b1\u01b2\3\2\2\2\u01b2\u01b3") 238 | buf.write("\3\2\2\2\u01b3\u01b4\5A!\2\u01b4@\3\2\2\2\u01b5\u01b7") 239 | buf.write("\t\13\2\2\u01b6\u01b8\5\u008dG\2\u01b7\u01b6\3\2\2\2\u01b7") 240 | buf.write("\u01b8\3\2\2\2\u01b8\u01ba\3\2\2\2\u01b9\u01bb\5\u0087") 241 | buf.write("D\2\u01ba\u01b9\3\2\2\2\u01bb\u01bc\3\2\2\2\u01bc\u01ba") 242 | buf.write("\3\2\2\2\u01bc\u01bd\3\2\2\2\u01bdB\3\2\2\2\u01be\u01c0") 243 | buf.write("\59\35\2\u01bf\u01c1\5\u0085C\2\u01c0\u01bf\3\2\2\2\u01c0") 244 | buf.write("\u01c1\3\2\2\2\u01c1\u01c3\3\2\2\2\u01c2\u01c4\5E#\2\u01c3") 245 | buf.write("\u01c2\3\2\2\2\u01c3\u01c4\3\2\2\2\u01c4D\3\2\2\2\u01c5") 246 | buf.write("\u01c7\t\f\2\2\u01c6\u01c8\5\u008dG\2\u01c7\u01c6\3\2") 247 | buf.write("\2\2\u01c7\u01c8\3\2\2\2\u01c8\u01ca\3\2\2\2\u01c9\u01cb") 248 | buf.write("\5\u0087D\2\u01ca\u01c9\3\2\2\2\u01cb\u01cc\3\2\2\2\u01cc") 249 | buf.write("\u01ca\3\2\2\2\u01cc\u01cd\3\2\2\2\u01cdF\3\2\2\2\u01ce") 250 | buf.write("\u01cf\5}?\2\u01cf\u01d0\5I%\2\u01d0\u01d1\5}?\2\u01d1") 251 | buf.write("H\3\2\2\2\u01d2\u01d5\5[.\2\u01d3\u01d5\5K&\2\u01d4\u01d2") 252 | buf.write("\3\2\2\2\u01d4\u01d3\3\2\2\2\u01d5\u01d8\3\2\2\2\u01d6") 253 | buf.write("\u01d4\3\2\2\2\u01d6\u01d7\3\2\2\2\u01d7J\3\2\2\2\u01d8") 254 | buf.write("\u01d6\3\2\2\2\u01d9\u01da\n\r\2\2\u01daL\3\2\2\2\u01db") 255 | buf.write("\u01e0\t\16\2\2\u01dc\u01df\t\16\2\2\u01dd\u01df\5\u0087") 256 | buf.write("D\2\u01de\u01dc\3\2\2\2\u01de\u01dd\3\2\2\2\u01df\u01e2") 257 | buf.write("\3\2\2\2\u01e0\u01de\3\2\2\2\u01e0\u01e1\3\2\2\2\u01e1") 258 | buf.write("N\3\2\2\2\u01e2\u01e0\3\2\2\2\u01e3\u01e4\5\177@\2\u01e4") 259 | buf.write("\u01e5\5S*\2\u01e5\u01e6\5\177@\2\u01e6P\3\2\2\2\u01e7") 260 | buf.write("\u01e8\5\u0081A\2\u01e8\u01e9\5U+\2\u01e9\u01ea\5\u0081") 261 | buf.write("A\2\u01eaR\3\2\2\2\u01eb\u01ee\5[.\2\u01ec\u01ee\5W,\2") 262 | buf.write("\u01ed\u01eb\3\2\2\2\u01ed\u01ec\3\2\2\2\u01ee\u01f1\3") 263 | buf.write("\2\2\2\u01ef\u01ed\3\2\2\2\u01ef\u01f0\3\2\2\2\u01f0T") 264 | buf.write("\3\2\2\2\u01f1\u01ef\3\2\2\2\u01f2\u01f5\5[.\2\u01f3\u01f5") 265 | buf.write("\5Y-\2\u01f4\u01f2\3\2\2\2\u01f4\u01f3\3\2\2\2\u01f5\u01f8") 266 | buf.write("\3\2\2\2\u01f6\u01f7\3\2\2\2\u01f6\u01f4\3\2\2\2\u01f7") 267 | buf.write("V\3\2\2\2\u01f8\u01f6\3\2\2\2\u01f9\u01fa\n\17\2\2\u01fa") 268 | buf.write("X\3\2\2\2\u01fb\u01fc\n\20\2\2\u01fcZ\3\2\2\2\u01fd\u0201") 269 | buf.write("\5\u008fH\2\u01fe\u0201\5\u0093J\2\u01ff\u0201\5\u0095") 270 | buf.write("K\2\u0200\u01fd\3\2\2\2\u0200\u01fe\3\2\2\2\u0200\u01ff") 271 | buf.write("\3\2\2\2\u0201\\\3\2\2\2\u0202\u0206\5y=\2\u0203\u0205") 272 | buf.write("\5\u0099M\2\u0204\u0203\3\2\2\2\u0205\u0208\3\2\2\2\u0206") 273 | buf.write("\u0204\3\2\2\2\u0206\u0207\3\2\2\2\u0207\u0209\3\2\2\2") 274 | buf.write("\u0208\u0206\3\2\2\2\u0209\u020a\5\177@\2\u020a\u020b") 275 | buf.write("\5a\61\2\u020b\u020f\5\177@\2\u020c\u020e\5\u0099M\2\u020d") 276 | buf.write("\u020c\3\2\2\2\u020e\u0211\3\2\2\2\u020f\u020d\3\2\2\2") 277 | buf.write("\u020f\u0210\3\2\2\2\u0210\u0212\3\2\2\2\u0211\u020f\3") 278 | buf.write("\2\2\2\u0212\u0213\5{>\2\u0213^\3\2\2\2\u0214\u0224\5") 279 | buf.write("y=\2\u0215\u0217\5\u0099M\2\u0216\u0215\3\2\2\2\u0217") 280 | buf.write("\u021a\3\2\2\2\u0218\u0216\3\2\2\2\u0218\u0219\3\2\2\2") 281 | buf.write("\u0219\u021b\3\2\2\2\u021a\u0218\3\2\2\2\u021b\u021f\5") 282 | buf.write("\u0081A\2\u021c\u021e\5c\62\2\u021d\u021c\3\2\2\2\u021e") 283 | buf.write("\u0221\3\2\2\2\u021f\u0220\3\2\2\2\u021f\u021d\3\2\2\2") 284 | buf.write("\u0220\u0222\3\2\2\2\u0221\u021f\3\2\2\2\u0222\u0223\5") 285 | buf.write("\u0081A\2\u0223\u0225\3\2\2\2\u0224\u0218\3\2\2\2\u0225") 286 | buf.write("\u0226\3\2\2\2\u0226\u0224\3\2\2\2\u0226\u0227\3\2\2\2") 287 | buf.write("\u0227\u022b\3\2\2\2\u0228\u022a\5\u0099M\2\u0229\u0228") 288 | buf.write("\3\2\2\2\u022a\u022d\3\2\2\2\u022b\u0229\3\2\2\2\u022b") 289 | buf.write("\u022c\3\2\2\2\u022c\u022e\3\2\2\2\u022d\u022b\3\2\2\2") 290 | buf.write("\u022e\u022f\5{>\2\u022f`\3\2\2\2\u0230\u0233\5k\66\2") 291 | buf.write("\u0231\u0233\5g\64\2\u0232\u0230\3\2\2\2\u0232\u0231\3") 292 | buf.write("\2\2\2\u0233\u0236\3\2\2\2\u0234\u0232\3\2\2\2\u0234\u0235") 293 | buf.write("\3\2\2\2\u0235b\3\2\2\2\u0236\u0234\3\2\2\2\u0237\u023f") 294 | buf.write("\5e\63\2\u0238\u0239\7)\2\2\u0239\u023f\5e\63\2\u023a") 295 | buf.write("\u023b\7)\2\2\u023b\u023c\7)\2\2\u023c\u023d\3\2\2\2\u023d") 296 | buf.write("\u023f\5e\63\2\u023e\u0237\3\2\2\2\u023e\u0238\3\2\2\2") 297 | buf.write("\u023e\u023a\3\2\2\2\u023fd\3\2\2\2\u0240\u0243\5k\66") 298 | buf.write("\2\u0241\u0243\5i\65\2\u0242\u0240\3\2\2\2\u0242\u0241") 299 | buf.write("\3\2\2\2\u0243f\3\2\2\2\u0244\u0247\t\21\2\2\u0245\u0247") 300 | buf.write("\5\u009dO\2\u0246\u0244\3\2\2\2\u0246\u0245\3\2\2\2\u0247") 301 | buf.write("h\3\2\2\2\u0248\u024b\t\22\2\2\u0249\u024b\5\u0099M\2") 302 | buf.write("\u024a\u0248\3\2\2\2\u024a\u0249\3\2\2\2\u024bj\3\2\2") 303 | buf.write("\2\u024c\u024f\5\u008fH\2\u024d\u024f\5\u0093J\2\u024e") 304 | buf.write("\u024c\3\2\2\2\u024e\u024d\3\2\2\2\u024fl\3\2\2\2\u0250") 305 | buf.write("\u0255\5y=\2\u0251\u0254\5q9\2\u0252\u0254\5\u0099M\2") 306 | buf.write("\u0253\u0251\3\2\2\2\u0253\u0252\3\2\2\2\u0254\u0257\3") 307 | buf.write("\2\2\2\u0255\u0253\3\2\2\2\u0255\u0256\3\2\2\2\u0256\u0259") 308 | buf.write("\3\2\2\2\u0257\u0255\3\2\2\2\u0258\u025a\5o8\2\u0259\u0258") 309 | buf.write("\3\2\2\2\u0259\u025a\3\2\2\2\u025a\u025e\3\2\2\2\u025b") 310 | buf.write("\u025d\5\u0099M\2\u025c\u025b\3\2\2\2\u025d\u0260\3\2") 311 | buf.write("\2\2\u025e\u025c\3\2\2\2\u025e\u025f\3\2\2\2\u025f\u0261") 312 | buf.write("\3\2\2\2\u0260\u025e\3\2\2\2\u0261\u0262\5{>\2\u0262n") 313 | buf.write("\3\2\2\2\u0263\u0266\5s:\2\u0264\u0266\5u;\2\u0265\u0263") 314 | buf.write("\3\2\2\2\u0265\u0264\3\2\2\2\u0266p\3\2\2\2\u0267\u026b") 315 | buf.write("\5w<\2\u0268\u026a\5\u0099M\2\u0269\u0268\3\2\2\2\u026a") 316 | buf.write("\u026d\3\2\2\2\u026b\u0269\3\2\2\2\u026b\u026c\3\2\2\2") 317 | buf.write("\u026c\u026e\3\2\2\2\u026d\u026b\3\2\2\2\u026e\u0272\5") 318 | buf.write("w<\2\u026f\u0271\5\u0099M\2\u0270\u026f\3\2\2\2\u0271") 319 | buf.write("\u0274\3\2\2\2\u0272\u0270\3\2\2\2\u0272\u0273\3\2\2\2") 320 | buf.write("\u0273\u0275\3\2\2\2\u0274\u0272\3\2\2\2\u0275\u0279\5") 321 | buf.write("w<\2\u0276\u0278\5\u0099M\2\u0277\u0276\3\2\2\2\u0278") 322 | buf.write("\u027b\3\2\2\2\u0279\u0277\3\2\2\2\u0279\u027a\3\2\2\2") 323 | buf.write("\u027a\u027c\3\2\2\2\u027b\u0279\3\2\2\2\u027c\u027d\5") 324 | buf.write("w<\2\u027dr\3\2\2\2\u027e\u0282\5w<\2\u027f\u0281\5\u0099") 325 | buf.write("M\2\u0280\u027f\3\2\2\2\u0281\u0284\3\2\2\2\u0282\u0280") 326 | buf.write("\3\2\2\2\u0282\u0283\3\2\2\2\u0283\u0285\3\2\2\2\u0284") 327 | buf.write("\u0282\3\2\2\2\u0285\u0289\5w<\2\u0286\u0288\5\u0099M") 328 | buf.write("\2\u0287\u0286\3\2\2\2\u0288\u028b\3\2\2\2\u0289\u0287") 329 | buf.write("\3\2\2\2\u0289\u028a\3\2\2\2\u028a\u028c\3\2\2\2\u028b") 330 | buf.write("\u0289\3\2\2\2\u028c\u0290\5w<\2\u028d\u028f\5\u0099M") 331 | buf.write("\2\u028e\u028d\3\2\2\2\u028f\u0292\3\2\2\2\u0290\u028e") 332 | buf.write("\3\2\2\2\u0290\u0291\3\2\2\2\u0291\u0293\3\2\2\2\u0292") 333 | buf.write("\u0290\3\2\2\2\u0293\u0294\7?\2\2\u0294t\3\2\2\2\u0295") 334 | buf.write("\u0299\5w<\2\u0296\u0298\5\u0099M\2\u0297\u0296\3\2\2") 335 | buf.write("\2\u0298\u029b\3\2\2\2\u0299\u0297\3\2\2\2\u0299\u029a") 336 | buf.write("\3\2\2\2\u029a\u029c\3\2\2\2\u029b\u0299\3\2\2\2\u029c") 337 | buf.write("\u02a0\5w<\2\u029d\u029f\5\u0099M\2\u029e\u029d\3\2\2") 338 | buf.write("\2\u029f\u02a2\3\2\2\2\u02a0\u029e\3\2\2\2\u02a0\u02a1") 339 | buf.write("\3\2\2\2\u02a1\u02a3\3\2\2\2\u02a2\u02a0\3\2\2\2\u02a3") 340 | buf.write("\u02a7\7?\2\2\u02a4\u02a6\5\u0099M\2\u02a5\u02a4\3\2\2") 341 | buf.write("\2\u02a6\u02a9\3\2\2\2\u02a7\u02a5\3\2\2\2\u02a7\u02a8") 342 | buf.write("\3\2\2\2\u02a8\u02aa\3\2\2\2\u02a9\u02a7\3\2\2\2\u02aa") 343 | buf.write("\u02ab\7?\2\2\u02abv\3\2\2\2\u02ac\u02ad\t\23\2\2\u02ad") 344 | buf.write("x\3\2\2\2\u02ae\u02af\7}\2\2\u02af\u02b0\7}\2\2\u02b0") 345 | buf.write("z\3\2\2\2\u02b1\u02b2\7\177\2\2\u02b2\u02b3\7\177\2\2") 346 | buf.write("\u02b3|\3\2\2\2\u02b4\u02b5\7)\2\2\u02b5~\3\2\2\2\u02b6") 347 | buf.write("\u02b7\7$\2\2\u02b7\u0080\3\2\2\2\u02b8\u02b9\7)\2\2\u02b9") 348 | buf.write("\u02ba\7)\2\2\u02ba\u02bb\7)\2\2\u02bb\u0082\3\2\2\2\u02bc") 349 | buf.write("\u02c8\7\62\2\2\u02bd\u02c4\t\3\2\2\u02be\u02c0\5\u009f") 350 | buf.write("P\2\u02bf\u02be\3\2\2\2\u02bf\u02c0\3\2\2\2\u02c0\u02c1") 351 | buf.write("\3\2\2\2\u02c1\u02c3\5\u0087D\2\u02c2\u02bf\3\2\2\2\u02c3") 352 | buf.write("\u02c6\3\2\2\2\u02c4\u02c2\3\2\2\2\u02c4\u02c5\3\2\2\2") 353 | buf.write("\u02c5\u02c8\3\2\2\2\u02c6\u02c4\3\2\2\2\u02c7\u02bc\3") 354 | buf.write("\2\2\2\u02c7\u02bd\3\2\2\2\u02c8\u0084\3\2\2\2\u02c9\u02d6") 355 | buf.write("\7\60\2\2\u02ca\u02cb\7\60\2\2\u02cb\u02d2\5\u0087D\2") 356 | buf.write("\u02cc\u02ce\5\u009fP\2\u02cd\u02cc\3\2\2\2\u02cd\u02ce") 357 | buf.write("\3\2\2\2\u02ce\u02cf\3\2\2\2\u02cf\u02d1\5\u0087D\2\u02d0") 358 | buf.write("\u02cd\3\2\2\2\u02d1\u02d4\3\2\2\2\u02d2\u02d0\3\2\2\2") 359 | buf.write("\u02d2\u02d3\3\2\2\2\u02d3\u02d6\3\2\2\2\u02d4\u02d2\3") 360 | buf.write("\2\2\2\u02d5\u02c9\3\2\2\2\u02d5\u02ca\3\2\2\2\u02d6\u0086") 361 | buf.write("\3\2\2\2\u02d7\u02d8\t\24\2\2\u02d8\u0088\3\2\2\2\u02d9") 362 | buf.write("\u02da\t\25\2\2\u02da\u008a\3\2\2\2\u02db\u02dc\t\6\2") 363 | buf.write("\2\u02dc\u008c\3\2\2\2\u02dd\u02de\t\26\2\2\u02de\u008e") 364 | buf.write("\3\2\2\2\u02df\u02e0\7^\2\2\u02e0\u02e1\5\u0091I\2\u02e1") 365 | buf.write("\u0090\3\2\2\2\u02e2\u02e5\t\27\2\2\u02e3\u02e5\5\u009b") 366 | buf.write("N\2\u02e4\u02e2\3\2\2\2\u02e4\u02e3\3\2\2\2\u02e5\u0092") 367 | buf.write("\3\2\2\2\u02e6\u02e7\7^\2\2\u02e7\u02e8\7z\2\2\u02e8\u02e9") 368 | buf.write("\3\2\2\2\u02e9\u02ea\5\u0089E\2\u02ea\u02eb\5\u0089E\2") 369 | buf.write("\u02eb\u0094\3\2\2\2\u02ec\u02ed\7^\2\2\u02ed\u02ee\7") 370 | buf.write("w\2\2\u02ee\u02ef\3\2\2\2\u02ef\u0302\5\u0097L\2\u02f0") 371 | buf.write("\u02f1\7^\2\2\u02f1\u02f2\7W\2\2\u02f2\u02f3\7\62\2\2") 372 | buf.write("\u02f3\u02f4\7\62\2\2\u02f4\u02f5\7\62\2\2\u02f5\u02f6") 373 | buf.write("\3\2\2\2\u02f6\u02f7\5\u0097L\2\u02f7\u02f8\5\u0089E\2") 374 | buf.write("\u02f8\u0302\3\2\2\2\u02f9\u02fa\7^\2\2\u02fa\u02fb\7") 375 | buf.write("W\2\2\u02fb\u02fc\7\62\2\2\u02fc\u02fd\7\62\2\2\u02fd") 376 | buf.write("\u02fe\7\63\2\2\u02fe\u02ff\7\62\2\2\u02ff\u0300\3\2\2") 377 | buf.write("\2\u0300\u0302\5\u0097L\2\u0301\u02ec\3\2\2\2\u0301\u02f0") 378 | buf.write("\3\2\2\2\u0301\u02f9\3\2\2\2\u0302\u0096\3\2\2\2\u0303") 379 | buf.write("\u0304\5\u0089E\2\u0304\u0305\5\u0089E\2\u0305\u0306\5") 380 | buf.write("\u0089E\2\u0306\u0307\5\u0089E\2\u0307\u0098\3\2\2\2\u0308") 381 | buf.write("\u030b\5\u009dO\2\u0309\u030b\t\30\2\2\u030a\u0308\3\2") 382 | buf.write("\2\2\u030a\u0309\3\2\2\2\u030b\u009a\3\2\2\2\u030c\u030d") 383 | buf.write("\7\17\2\2\u030d\u0310\7\f\2\2\u030e\u0310\t\30\2\2\u030f") 384 | buf.write("\u030c\3\2\2\2\u030f\u030e\3\2\2\2\u0310\u009c\3\2\2\2") 385 | buf.write("\u0311\u0312\t\31\2\2\u0312\u009e\3\2\2\2\u0313\u0314") 386 | buf.write("\7a\2\2\u0314\u00a0\3\2\2\2Q\2\u00b8\u00c0\u00c5\u00cd") 387 | buf.write("\u0116\u0121\u0126\u0128\u0132\u014e\u0154\u015c\u0163") 388 | buf.write("\u016d\u0173\u017e\u0180\u0183\u0189\u018e\u0192\u0197") 389 | buf.write("\u019d\u01a2\u01ad\u01b1\u01b7\u01bc\u01c0\u01c3\u01c7") 390 | buf.write("\u01cc\u01d4\u01d6\u01de\u01e0\u01ed\u01ef\u01f4\u01f6") 391 | buf.write("\u0200\u0206\u020f\u0218\u021f\u0226\u022b\u0232\u0234") 392 | buf.write("\u023e\u0242\u0246\u024a\u024e\u0253\u0255\u0259\u025e") 393 | buf.write("\u0265\u026b\u0272\u0279\u0282\u0289\u0290\u0299\u02a0") 394 | buf.write("\u02a7\u02bf\u02c4\u02c7\u02cd\u02d2\u02d5\u02e4\u0301") 395 | buf.write("\u030a\u030f\2") 396 | return buf.getvalue() 397 | 398 | 399 | class IonTextLexer(Lexer): 400 | 401 | atn = ATNDeserializer().deserialize(serializedATN()) 402 | 403 | decisionsToDFA = [ DFA(ds, i) for i, ds in enumerate(atn.decisionToState) ] 404 | 405 | L_BRACKET = 1 406 | R_BRACKET = 2 407 | L_PAREN = 3 408 | R_PAREN = 4 409 | L_CURLY = 5 410 | R_CURLY = 6 411 | COMMA = 7 412 | COLON = 8 413 | DOT = 9 414 | NON_DOT_OPERATOR = 10 415 | WHITESPACE = 11 416 | INLINE_COMMENT = 12 417 | BLOCK_COMMENT = 13 418 | NULL = 14 419 | TYPE = 15 420 | BOOL = 16 421 | TIMESTAMP = 17 422 | BIN_INTEGER = 18 423 | DEC_INTEGER = 19 424 | HEX_INTEGER = 20 425 | SPECIAL_FLOAT = 21 426 | FLOAT = 22 427 | DECIMAL = 23 428 | QUOTED_SYMBOL = 24 429 | IDENTIFIER_SYMBOL = 25 430 | SHORT_QUOTED_STRING = 26 431 | LONG_QUOTED_STRING = 27 432 | SHORT_QUOTED_CLOB = 28 433 | LONG_QUOTED_CLOB = 29 434 | BLOB = 30 435 | 436 | channelNames = [ u"DEFAULT_TOKEN_CHANNEL", u"HIDDEN" ] 437 | 438 | modeNames = [ "DEFAULT_MODE" ] 439 | 440 | literalNames = [ "", 441 | "'['", "']'", "'('", "')'", "'{'", "'}'", "','", "':'", "'.'", 442 | "'null'" ] 443 | 444 | symbolicNames = [ "", 445 | "L_BRACKET", "R_BRACKET", "L_PAREN", "R_PAREN", "L_CURLY", "R_CURLY", 446 | "COMMA", "COLON", "DOT", "NON_DOT_OPERATOR", "WHITESPACE", "INLINE_COMMENT", 447 | "BLOCK_COMMENT", "NULL", "TYPE", "BOOL", "TIMESTAMP", "BIN_INTEGER", 448 | "DEC_INTEGER", "HEX_INTEGER", "SPECIAL_FLOAT", "FLOAT", "DECIMAL", 449 | "QUOTED_SYMBOL", "IDENTIFIER_SYMBOL", "SHORT_QUOTED_STRING", 450 | "LONG_QUOTED_STRING", "SHORT_QUOTED_CLOB", "LONG_QUOTED_CLOB", 451 | "BLOB" ] 452 | 453 | ruleNames = [ "L_BRACKET", "R_BRACKET", "L_PAREN", "R_PAREN", "L_CURLY", 454 | "R_CURLY", "COMMA", "COLON", "DOT", "NON_DOT_OPERATOR", 455 | "WHITESPACE", "INLINE_COMMENT", "BLOCK_COMMENT", "NULL", 456 | "TYPE", "BOOL", "TIMESTAMP", "DATE", "YEAR", "MONTH", 457 | "DAY", "TIME", "OFFSET", "HOUR", "MINUTE", "SECOND", "BIN_INTEGER", 458 | "DEC_INTEGER", "HEX_INTEGER", "SPECIAL_FLOAT", "FLOAT", 459 | "FLOAT_EXP", "DECIMAL", "DECIMAL_EXP", "QUOTED_SYMBOL", 460 | "SYMBOL_TEXT", "SYMBOL_TEXT_ALLOWED", "IDENTIFIER_SYMBOL", 461 | "SHORT_QUOTED_STRING", "LONG_QUOTED_STRING", "STRING_SHORT_TEXT", 462 | "STRING_LONG_TEXT", "STRING_SHORT_TEXT_ALLOWED", "STRING_LONG_TEXT_ALLOWED", 463 | "TEXT_ESCAPE", "SHORT_QUOTED_CLOB", "LONG_QUOTED_CLOB", 464 | "CLOB_SHORT_TEXT", "CLOB_LONG_TEXT", "CLOB_LONG_TEXT_NO_QUOTE", 465 | "CLOB_SHORT_TEXT_ALLOWED", "CLOB_LONG_TEXT_ALLOWED", "CLOB_ESCAPE", 466 | "BLOB", "BASE_64_PAD", "BASE_64_QUARTET", "BASE_64_PAD1", 467 | "BASE_64_PAD2", "BASE_64_CHAR", "LOB_START", "LOB_END", 468 | "SYMBOL_QUOTE", "SHORT_QUOTE", "LONG_QUOTE", "DEC_UNSIGNED_INTEGER", 469 | "DEC_FRAC", "DEC_DIGIT", "HEX_DIGIT", "BINARY_DIGIT", 470 | "PLUS_OR_MINUS", "COMMON_ESCAPE", "COMMON_ESCAPE_CODE", 471 | "HEX_ESCAPE", "UNICODE_ESCAPE", "HEX_DIGIT_QUARTET", "WS", 472 | "NL", "WS_NOT_NL", "UNDERSCORE" ] 473 | 474 | grammarFileName = "IonText.g4" 475 | 476 | def __init__(self, input=None, output:TextIO = sys.stdout): 477 | super().__init__(input, output) 478 | self.checkVersion("4.7") 479 | self._interp = LexerATNSimulator(self, self.atn, self.decisionsToDFA, PredictionContextCache()) 480 | self._actions = None 481 | self._predicates = None 482 | 483 | 484 | -------------------------------------------------------------------------------- /pionic/antlr/IonTextLexer.tokens: -------------------------------------------------------------------------------- 1 | L_BRACKET=1 2 | R_BRACKET=2 3 | L_PAREN=3 4 | R_PAREN=4 5 | L_CURLY=5 6 | R_CURLY=6 7 | COMMA=7 8 | COLON=8 9 | DOT=9 10 | NON_DOT_OPERATOR=10 11 | WHITESPACE=11 12 | INLINE_COMMENT=12 13 | BLOCK_COMMENT=13 14 | NULL=14 15 | TYPE=15 16 | BOOL=16 17 | TIMESTAMP=17 18 | BIN_INTEGER=18 19 | DEC_INTEGER=19 20 | HEX_INTEGER=20 21 | SPECIAL_FLOAT=21 22 | FLOAT=22 23 | DECIMAL=23 24 | QUOTED_SYMBOL=24 25 | IDENTIFIER_SYMBOL=25 26 | SHORT_QUOTED_STRING=26 27 | LONG_QUOTED_STRING=27 28 | SHORT_QUOTED_CLOB=28 29 | LONG_QUOTED_CLOB=29 30 | BLOB=30 31 | '['=1 32 | ']'=2 33 | '('=3 34 | ')'=4 35 | '{'=5 36 | '}'=6 37 | ','=7 38 | ':'=8 39 | '.'=9 40 | 'null'=14 41 | -------------------------------------------------------------------------------- /pionic/antlr/IonTextListener.py: -------------------------------------------------------------------------------- 1 | # Generated from IonText.g4 by ANTLR 4.7 2 | from antlr4 import * 3 | if __name__ is not None and "." in __name__: 4 | from .IonTextParser import IonTextParser 5 | else: 6 | from IonTextParser import IonTextParser 7 | 8 | # This class defines a complete listener for a parse tree produced by IonTextParser. 9 | class IonTextListener(ParseTreeListener): 10 | 11 | # Enter a parse tree produced by IonTextParser#top_level. 12 | def enterTop_level(self, ctx:IonTextParser.Top_levelContext): 13 | pass 14 | 15 | # Exit a parse tree produced by IonTextParser#top_level. 16 | def exitTop_level(self, ctx:IonTextParser.Top_levelContext): 17 | pass 18 | 19 | 20 | # Enter a parse tree produced by IonTextParser#top_level_value. 21 | def enterTop_level_value(self, ctx:IonTextParser.Top_level_valueContext): 22 | pass 23 | 24 | # Exit a parse tree produced by IonTextParser#top_level_value. 25 | def exitTop_level_value(self, ctx:IonTextParser.Top_level_valueContext): 26 | pass 27 | 28 | 29 | # Enter a parse tree produced by IonTextParser#value. 30 | def enterValue(self, ctx:IonTextParser.ValueContext): 31 | pass 32 | 33 | # Exit a parse tree produced by IonTextParser#value. 34 | def exitValue(self, ctx:IonTextParser.ValueContext): 35 | pass 36 | 37 | 38 | # Enter a parse tree produced by IonTextParser#entity. 39 | def enterEntity(self, ctx:IonTextParser.EntityContext): 40 | pass 41 | 42 | # Exit a parse tree produced by IonTextParser#entity. 43 | def exitEntity(self, ctx:IonTextParser.EntityContext): 44 | pass 45 | 46 | 47 | # Enter a parse tree produced by IonTextParser#delimiting_entity. 48 | def enterDelimiting_entity(self, ctx:IonTextParser.Delimiting_entityContext): 49 | pass 50 | 51 | # Exit a parse tree produced by IonTextParser#delimiting_entity. 52 | def exitDelimiting_entity(self, ctx:IonTextParser.Delimiting_entityContext): 53 | pass 54 | 55 | 56 | # Enter a parse tree produced by IonTextParser#keyword_delimiting_entity. 57 | def enterKeyword_delimiting_entity(self, ctx:IonTextParser.Keyword_delimiting_entityContext): 58 | pass 59 | 60 | # Exit a parse tree produced by IonTextParser#keyword_delimiting_entity. 61 | def exitKeyword_delimiting_entity(self, ctx:IonTextParser.Keyword_delimiting_entityContext): 62 | pass 63 | 64 | 65 | # Enter a parse tree produced by IonTextParser#keyword_entity. 66 | def enterKeyword_entity(self, ctx:IonTextParser.Keyword_entityContext): 67 | pass 68 | 69 | # Exit a parse tree produced by IonTextParser#keyword_entity. 70 | def exitKeyword_entity(self, ctx:IonTextParser.Keyword_entityContext): 71 | pass 72 | 73 | 74 | # Enter a parse tree produced by IonTextParser#numeric_entity. 75 | def enterNumeric_entity(self, ctx:IonTextParser.Numeric_entityContext): 76 | pass 77 | 78 | # Exit a parse tree produced by IonTextParser#numeric_entity. 79 | def exitNumeric_entity(self, ctx:IonTextParser.Numeric_entityContext): 80 | pass 81 | 82 | 83 | # Enter a parse tree produced by IonTextParser#annotation. 84 | def enterAnnotation(self, ctx:IonTextParser.AnnotationContext): 85 | pass 86 | 87 | # Exit a parse tree produced by IonTextParser#annotation. 88 | def exitAnnotation(self, ctx:IonTextParser.AnnotationContext): 89 | pass 90 | 91 | 92 | # Enter a parse tree produced by IonTextParser#quoted_annotation. 93 | def enterQuoted_annotation(self, ctx:IonTextParser.Quoted_annotationContext): 94 | pass 95 | 96 | # Exit a parse tree produced by IonTextParser#quoted_annotation. 97 | def exitQuoted_annotation(self, ctx:IonTextParser.Quoted_annotationContext): 98 | pass 99 | 100 | 101 | # Enter a parse tree produced by IonTextParser#list_type. 102 | def enterList_type(self, ctx:IonTextParser.List_typeContext): 103 | pass 104 | 105 | # Exit a parse tree produced by IonTextParser#list_type. 106 | def exitList_type(self, ctx:IonTextParser.List_typeContext): 107 | pass 108 | 109 | 110 | # Enter a parse tree produced by IonTextParser#sexp. 111 | def enterSexp(self, ctx:IonTextParser.SexpContext): 112 | pass 113 | 114 | # Exit a parse tree produced by IonTextParser#sexp. 115 | def exitSexp(self, ctx:IonTextParser.SexpContext): 116 | pass 117 | 118 | 119 | # Enter a parse tree produced by IonTextParser#sexp_value. 120 | def enterSexp_value(self, ctx:IonTextParser.Sexp_valueContext): 121 | pass 122 | 123 | # Exit a parse tree produced by IonTextParser#sexp_value. 124 | def exitSexp_value(self, ctx:IonTextParser.Sexp_valueContext): 125 | pass 126 | 127 | 128 | # Enter a parse tree produced by IonTextParser#sexp_delimiting_entity. 129 | def enterSexp_delimiting_entity(self, ctx:IonTextParser.Sexp_delimiting_entityContext): 130 | pass 131 | 132 | # Exit a parse tree produced by IonTextParser#sexp_delimiting_entity. 133 | def exitSexp_delimiting_entity(self, ctx:IonTextParser.Sexp_delimiting_entityContext): 134 | pass 135 | 136 | 137 | # Enter a parse tree produced by IonTextParser#sexp_keyword_delimiting_entity. 138 | def enterSexp_keyword_delimiting_entity(self, ctx:IonTextParser.Sexp_keyword_delimiting_entityContext): 139 | pass 140 | 141 | # Exit a parse tree produced by IonTextParser#sexp_keyword_delimiting_entity. 142 | def exitSexp_keyword_delimiting_entity(self, ctx:IonTextParser.Sexp_keyword_delimiting_entityContext): 143 | pass 144 | 145 | 146 | # Enter a parse tree produced by IonTextParser#sexp_null_delimiting_entity. 147 | def enterSexp_null_delimiting_entity(self, ctx:IonTextParser.Sexp_null_delimiting_entityContext): 148 | pass 149 | 150 | # Exit a parse tree produced by IonTextParser#sexp_null_delimiting_entity. 151 | def exitSexp_null_delimiting_entity(self, ctx:IonTextParser.Sexp_null_delimiting_entityContext): 152 | pass 153 | 154 | 155 | # Enter a parse tree produced by IonTextParser#sexp_keyword_entity. 156 | def enterSexp_keyword_entity(self, ctx:IonTextParser.Sexp_keyword_entityContext): 157 | pass 158 | 159 | # Exit a parse tree produced by IonTextParser#sexp_keyword_entity. 160 | def exitSexp_keyword_entity(self, ctx:IonTextParser.Sexp_keyword_entityContext): 161 | pass 162 | 163 | 164 | # Enter a parse tree produced by IonTextParser#operator. 165 | def enterOperator(self, ctx:IonTextParser.OperatorContext): 166 | pass 167 | 168 | # Exit a parse tree produced by IonTextParser#operator. 169 | def exitOperator(self, ctx:IonTextParser.OperatorContext): 170 | pass 171 | 172 | 173 | # Enter a parse tree produced by IonTextParser#struct. 174 | def enterStruct(self, ctx:IonTextParser.StructContext): 175 | pass 176 | 177 | # Exit a parse tree produced by IonTextParser#struct. 178 | def exitStruct(self, ctx:IonTextParser.StructContext): 179 | pass 180 | 181 | 182 | # Enter a parse tree produced by IonTextParser#field. 183 | def enterField(self, ctx:IonTextParser.FieldContext): 184 | pass 185 | 186 | # Exit a parse tree produced by IonTextParser#field. 187 | def exitField(self, ctx:IonTextParser.FieldContext): 188 | pass 189 | 190 | 191 | # Enter a parse tree produced by IonTextParser#any_null. 192 | def enterAny_null(self, ctx:IonTextParser.Any_nullContext): 193 | pass 194 | 195 | # Exit a parse tree produced by IonTextParser#any_null. 196 | def exitAny_null(self, ctx:IonTextParser.Any_nullContext): 197 | pass 198 | 199 | 200 | # Enter a parse tree produced by IonTextParser#typed_null. 201 | def enterTyped_null(self, ctx:IonTextParser.Typed_nullContext): 202 | pass 203 | 204 | # Exit a parse tree produced by IonTextParser#typed_null. 205 | def exitTyped_null(self, ctx:IonTextParser.Typed_nullContext): 206 | pass 207 | 208 | 209 | # Enter a parse tree produced by IonTextParser#field_name. 210 | def enterField_name(self, ctx:IonTextParser.Field_nameContext): 211 | pass 212 | 213 | # Exit a parse tree produced by IonTextParser#field_name. 214 | def exitField_name(self, ctx:IonTextParser.Field_nameContext): 215 | pass 216 | 217 | 218 | # Enter a parse tree produced by IonTextParser#quoted_text. 219 | def enterQuoted_text(self, ctx:IonTextParser.Quoted_textContext): 220 | pass 221 | 222 | # Exit a parse tree produced by IonTextParser#quoted_text. 223 | def exitQuoted_text(self, ctx:IonTextParser.Quoted_textContext): 224 | pass 225 | 226 | 227 | # Enter a parse tree produced by IonTextParser#symbol. 228 | def enterSymbol(self, ctx:IonTextParser.SymbolContext): 229 | pass 230 | 231 | # Exit a parse tree produced by IonTextParser#symbol. 232 | def exitSymbol(self, ctx:IonTextParser.SymbolContext): 233 | pass 234 | 235 | 236 | # Enter a parse tree produced by IonTextParser#ws. 237 | def enterWs(self, ctx:IonTextParser.WsContext): 238 | pass 239 | 240 | # Exit a parse tree produced by IonTextParser#ws. 241 | def exitWs(self, ctx:IonTextParser.WsContext): 242 | pass 243 | 244 | 245 | -------------------------------------------------------------------------------- /pionic/core.py: -------------------------------------------------------------------------------- 1 | from antlr4 import CommonTokenStream, InputStream 2 | from antlr4.error import Errors, ErrorListener 3 | from antlr4.tree.Tree import TerminalNodeImpl 4 | from pionic.antlr.IonTextLexer import IonTextLexer 5 | from pionic.antlr.IonTextParser import IonTextParser 6 | import arrow 7 | from decimal import Decimal 8 | from base64 import b64decode, b64encode 9 | from collections.abc import Mapping 10 | from datetime import datetime as Datetime, timezone as Timezone 11 | 12 | 13 | LONG_QUOTE = "'''" 14 | SHORT_QUOTE = '"' 15 | UTC_FORMAT = '%Y-%m-%dT%H:%M:%SZ' 16 | TZ_FORMAT = '%Y-%m-%dT%H:%M:%S%z' 17 | 18 | 19 | class PionException(Exception): 20 | pass 21 | 22 | 23 | class ThrowingErrorListener(ErrorListener.ErrorListener): 24 | def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e): 25 | raise Errors.ParseCancellationException( 26 | "line " + str(line) + ":" + str(column) + " " + msg) 27 | 28 | 29 | def load(file_like): 30 | return loads(file_like.read()) 31 | 32 | 33 | def dump(obj, file_like): 34 | file_like.write(dumps(obj)) 35 | 36 | 37 | def loads(ion_str): 38 | lexer = IonTextLexer(InputStream(ion_str)) 39 | lexer.removeErrorListeners() 40 | lexer.addErrorListener(ThrowingErrorListener()) 41 | stream = CommonTokenStream(lexer) 42 | parser = IonTextParser(stream) 43 | parser.removeErrorListeners() 44 | parser.addErrorListener(ThrowingErrorListener()) 45 | 46 | try: 47 | tree = parser.top_level() 48 | except Errors.ParseCancellationException as e: 49 | raise PionException(str(e)) from e 50 | 51 | return parse(tree) 52 | 53 | 54 | _trans_dec = str.maketrans('dD', 'ee', '_') 55 | 56 | 57 | def parse(node): 58 | # print("parse start") 59 | # print(str(type(node))) 60 | # print("node text" + node.getText()) 61 | if isinstance(node, IonTextParser.StructContext): 62 | val = {} 63 | for child in node.getChildren(): 64 | if isinstance(child, IonTextParser.FieldContext): 65 | for c in child.getChildren(): 66 | if isinstance(c, IonTextParser.Field_nameContext): 67 | k = parse(c) 68 | elif isinstance(c, IonTextParser.EntityContext): 69 | v = parse(c) 70 | val[k] = v 71 | 72 | elif isinstance(node, IonTextParser.List_typeContext): 73 | val = [] 74 | for child in node.getChildren(): 75 | if isinstance(child, IonTextParser.ValueContext): 76 | val.append(parse(child)) 77 | 78 | elif isinstance( 79 | node, ( 80 | IonTextParser.Top_levelContext, 81 | IonTextParser.Top_level_valueContext, 82 | IonTextParser.Delimiting_entityContext, 83 | IonTextParser.ValueContext, 84 | IonTextParser.SymbolContext, 85 | IonTextParser.EntityContext, 86 | IonTextParser.Keyword_entityContext, 87 | IonTextParser.Numeric_entityContext, 88 | IonTextParser.AnnotationContext, 89 | IonTextParser.Field_nameContext, 90 | IonTextParser.Sexp_valueContext, 91 | IonTextParser.Sexp_delimiting_entityContext, 92 | IonTextParser.Sexp_keyword_entityContext, 93 | IonTextParser.Sexp_keyword_delimiting_entityContext)): 94 | 95 | children = [] 96 | for c in node.getChildren(): 97 | if isinstance(c, TerminalNodeImpl) and \ 98 | c.getPayload().type == IonTextParser.EOF: 99 | continue 100 | elif isinstance( 101 | c, ( 102 | IonTextParser.AnnotationContext, 103 | IonTextParser.WsContext)): 104 | continue 105 | 106 | children.append(c) 107 | 108 | if len(children) == 1: 109 | val = parse(children[0]) 110 | else: 111 | raise PionException("Thought there would always be one child.") 112 | elif isinstance(node, IonTextParser.Any_nullContext): 113 | val = None 114 | elif isinstance(node, TerminalNodeImpl): 115 | token = node.getPayload() 116 | token_type = token.type 117 | token_text = token.text 118 | 119 | if token_type == IonTextParser.TIMESTAMP: 120 | try: 121 | val = arrow.get(token_text).datetime 122 | except arrow.parser.ParserError as e: 123 | raise PionException( 124 | "Can't parse the timestamp '" + token.text + "'.") from e 125 | 126 | elif token_type == IonTextParser.BOOL: 127 | val = token.text == 'true' 128 | 129 | elif token_type in ( 130 | IonTextParser.IDENTIFIER_SYMBOL, 131 | IonTextParser.NON_DOT_OPERATOR, 132 | IonTextParser.DOT): 133 | val = token.text 134 | 135 | elif token_type in ( 136 | IonTextParser.BIN_INTEGER, IonTextParser.DEC_INTEGER, 137 | IonTextParser.HEX_INTEGER): 138 | val = int(token.text.replace('_', ''), 0) 139 | 140 | elif token_type == IonTextParser.DECIMAL: 141 | val = Decimal(token.text.translate(_trans_dec)) 142 | 143 | elif token_type == IonTextParser.FLOAT: 144 | val = float(token.text.replace('_', '')) 145 | 146 | elif token_type in ( 147 | IonTextParser.SHORT_QUOTED_STRING, 148 | IonTextParser.QUOTED_SYMBOL): 149 | val = unescape(token.text[1:-1]) 150 | 151 | elif token_type == IonTextParser.LONG_QUOTED_STRING: 152 | val = unescape(token.text[3:-3]) 153 | 154 | elif token_type == IonTextParser.BLOB: 155 | val = bytearray(b64decode(token.text)) 156 | 157 | elif token_type == IonTextParser.SHORT_QUOTED_CLOB: 158 | val = bytes(unescape(token_text[2:-2].strip()[1:-1]), 'ascii') 159 | 160 | elif token_type == IonTextParser.LONG_QUOTED_CLOB: 161 | clobs = [] 162 | clobs_str = token_text[2:-2].strip() 163 | start = clobs_str.find("'''") 164 | while start != -1: 165 | finish = clobs_str.find("'''", start + 3) 166 | clobs.append(clobs_str[start + 3:finish]) 167 | start = clobs_str.find("'''", finish + 3) 168 | val = bytes(unescape(''.join(clobs)), 'ascii') 169 | else: 170 | raise PionException( 171 | "Don't recognize the token type: " + str(token_type) + ".") 172 | elif isinstance(node, IonTextParser.SexpContext): 173 | sexp = [] 174 | for child in node.getChildren(): 175 | if isinstance( 176 | child, ( 177 | IonTextParser.Sexp_valueContext, 178 | IonTextParser.ValueContext)): 179 | for c in child.getChildren(): 180 | if not isinstance(c, IonTextParser.WsContext): 181 | sexp.append(parse(c)) 182 | val = tuple(sexp) 183 | elif isinstance( 184 | node, ( 185 | IonTextParser.Quoted_textContext, 186 | IonTextParser.OperatorContext)): 187 | s = [] 188 | for c in node.getChildren(): 189 | if not isinstance(c, IonTextParser.WsContext): 190 | s.append(parse(c)) 191 | val = ''.join(s) 192 | else: 193 | raise PionException( 194 | "Don't know what to do with type " + str(type(node)) + 195 | " with value " + str(node) + ".") 196 | return val 197 | 198 | 199 | ESCAPES = { 200 | '0': '\u0000', # NUL 201 | 'a': '\u0007', # alert BEL 202 | 'b': '\u0008', # backspace BS 203 | 't': '\u0009', # horizontal tab HT 204 | 'n': '\u000A', # linefeed LF 205 | 'f': '\u000C', # form feed FF 206 | 'r': '\u000D', # carriage return CR 207 | 'v': '\u000B', # vertical tab VT 208 | '"': '\u0022', # double quote 209 | "'": '\u0027', # single quote 210 | '?': '\u003F', # question mark 211 | '\\': '\u005C', # backslash 212 | '/': '\u002F', # forward slash 213 | '\u000D\u000A': '', # empty string 214 | '\u000D': '', # empty string 215 | '\u000A': ''} # empty string 216 | 217 | 218 | def unescape(escaped_str): 219 | i = escaped_str.find('\\') 220 | if i == -1: 221 | return escaped_str 222 | else: 223 | head_str = escaped_str[:i] 224 | tail_str = escaped_str[i+1:] 225 | for k, v in ESCAPES.items(): 226 | if tail_str.startswith(k): 227 | return head_str + v + unescape(tail_str[len(k):]) 228 | 229 | for prefix, digits in (('x', 2), ('u', 4), ('U', 8)): 230 | if tail_str.startswith(prefix): 231 | hex_str = tail_str[1:1 + digits] 232 | v = chr(int(hex_str, 16)) 233 | return head_str + v + unescape(tail_str[1 + digits:]) 234 | 235 | raise PionException( 236 | "Can't find a valid string following the first backslash of '" + 237 | escaped_str + "'.") 238 | 239 | 240 | def dumps(obj): 241 | return _dump(obj, '') 242 | 243 | 244 | def _dump(obj, indent): 245 | if isinstance(obj, Mapping): 246 | new_indent = indent + ' ' 247 | items = [] 248 | for k, v in sorted(obj.items()): 249 | items.append( 250 | '\n' + new_indent + "'" + k + "': " + _dump(v, new_indent)) 251 | return '{' + ','.join(items) + '}' 252 | elif isinstance(obj, bool): 253 | return 'true' if obj else 'false' 254 | elif isinstance(obj, list): 255 | new_indent = indent + ' ' 256 | b = ','.join('\n' + new_indent + _dump(v, new_indent) for v in obj) 257 | return '[' + b + ']' 258 | elif isinstance(obj, (int, float, Decimal)): 259 | return str(obj) 260 | elif obj is None: 261 | return 'null' 262 | elif isinstance(obj, str): 263 | quote = LONG_QUOTE if '\n' in obj or '\r' in obj else SHORT_QUOTE 264 | return quote + obj + quote 265 | elif isinstance(obj, bytearray): 266 | return '{{ ' + b64encode(obj).decode() + ' }}' 267 | elif isinstance(obj, bytes): 268 | return "{{ '" + obj.decode() + "' }}" 269 | elif isinstance(obj, Datetime): 270 | if obj.tzinfo is None or obj.tzinfo == Timezone.utc: 271 | fmt = UTC_FORMAT 272 | else: 273 | fmt = TZ_FORMAT 274 | return obj.strftime(fmt) 275 | else: 276 | raise PionException("Type " + str(type(obj)) + " not recognised.") 277 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [versioneer] 2 | VCS = git 3 | style = pep440 4 | versionfile_source = pionic/_version.py 5 | versionfile_build = pionic/_version.py 6 | tag_prefix = 7 | parentdir_prefix = pionic- 8 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup 4 | import versioneer 5 | 6 | 7 | setup( 8 | name='pionic', 9 | version=versioneer.get_version(), 10 | description='A Python 3 library for the Ion format.', 11 | author='Tony Locke', 12 | author_email='tlocke@tlocke.org.uk', 13 | url='https://github.com/tlocke/pionic', 14 | cmdclass=versioneer.get_cmdclass(), 15 | packages=[ 16 | 'pionic', 'pionic.antlr'], 17 | install_requires=[ 18 | 'antlr4-python3-runtime==4.7', 19 | 'arrow==0.10.0']) 20 | -------------------------------------------------------------------------------- /tests/test_pionic.py: -------------------------------------------------------------------------------- 1 | from pionic import load, loads, PionException, dump, dumps 2 | from io import StringIO 3 | from datetime import ( 4 | datetime as Datetime, timezone as Timezone, timedelta as Timedelta) 5 | import pytest 6 | from decimal import Decimal 7 | from base64 import b64decode 8 | from collections import OrderedDict 9 | 10 | 11 | def test_load(): 12 | assert load(StringIO('{}')) == {} 13 | 14 | 15 | def test_dump(): 16 | f = StringIO() 17 | dump({}, f) 18 | assert f.getvalue() == '{}' 19 | 20 | 21 | @pytest.mark.parametrize( 22 | "ion_str,pyth", [ 23 | # 24 | # Timestamp 25 | # 26 | 27 | # A null timestamp value 28 | ( 29 | 'null.timestamp', 30 | None), 31 | 32 | # Seconds are optional, but local offset is not 33 | ( 34 | '2007-02-23T12:14Z', 35 | Datetime(2007, 2, 23, 12, 14, tzinfo=Timezone.utc)), 36 | 37 | # A timestamp with millisecond precision and PST local time 38 | ( 39 | '2007-02-23T12:14:33.079-08:00', 40 | Datetime( 41 | 2007, 2, 23, 12, 14, 33, 79000, 42 | tzinfo=Timezone(Timedelta(hours=-8)))), 43 | 44 | # The same instant in UTC ("zero" or "zulu") 45 | ( 46 | '2007-02-23T20:14:33.079Z', 47 | Datetime(2007, 2, 23, 20, 14, 33, 79000, tzinfo=Timezone.utc)), 48 | 49 | # The same instant, with explicit local offset 50 | ( 51 | '2007-02-23T20:14:33.079+00:00', 52 | Datetime( 53 | 2007, 2, 23, 20, 14, 33, 79000, 54 | tzinfo=Timezone(Timedelta(hours=0)))), 55 | 56 | # The same instant, with unknown local offset 57 | ( 58 | '2007-02-23T20:14:33.079-00:00', 59 | Datetime( 60 | 2007, 2, 23, 20, 14, 33, 79000, 61 | tzinfo=Timezone(Timedelta(hours=0)))), 62 | 63 | # Happy New Year in UTC, unknown local offset 64 | ( 65 | '2007-01-01T00:00-00:00', 66 | Datetime(2007, 1, 1, tzinfo=Timezone.utc)), 67 | 68 | # The same instant, with days precision, unknown local offset 69 | ( 70 | '2007-01-01', 71 | Datetime(2007, 1, 1, tzinfo=Timezone.utc)), 72 | 73 | # The same value, different syntax. 74 | # Shouldn't actually be an error, but arrow says it is. 75 | ( 76 | '2007-01-01T', 77 | Exception()), 78 | 79 | # The same instant, with months precision, unknown local offset 80 | # Shouldn't actually be an error, but arrow says it is. 81 | ( 82 | '2007-01T', 83 | Exception()), 84 | 85 | # The same instant, with years precision, unknown local offset 86 | # Shouldn't actually be an error, but arrow says it is. 87 | ( 88 | '2007T', 89 | Exception()), 90 | 91 | # A day, unknown local offset 92 | ( 93 | '2007-02-23', 94 | Datetime(2007, 2, 23, tzinfo=Timezone.utc)), 95 | 96 | # The same instant, but more precise and in UTC 97 | ( 98 | '2007-02-23T00:00Z', 99 | Datetime(2007, 2, 23, tzinfo=Timezone.utc)), 100 | 101 | # An equivalent format for the same value 102 | ( 103 | '2007-02-23T00:00+00:00', 104 | Datetime(2007, 2, 23, tzinfo=Timezone.utc)), 105 | 106 | # The same instant, with seconds precision 107 | ( 108 | '2007-02-23T00:00:00-00:00', 109 | Datetime(2007, 2, 23, tzinfo=Timezone.utc)), 110 | 111 | # Not a timestamp, but an int 112 | ( 113 | '2007', 114 | 2007), 115 | 116 | # ERROR: Must end with 'T' if not whole-day precision, this results 117 | # as an invalid-numeric-stopper error 118 | ( 119 | '2007-01', 120 | Exception()), 121 | 122 | # ERROR: Must have at least one digit precision after decimal point. 123 | ( 124 | '2007-02-23T20:14:33.Z', 125 | Exception()), 126 | 127 | # 128 | # Null Values 129 | # 130 | 131 | ('null', None), 132 | 133 | # Identical to unadorned null 134 | ('null.null', None), 135 | 136 | ('null.bool', None), 137 | ('null.int', None), 138 | ('null.float', None), 139 | ('null.decimal', None), 140 | ('null.timestamp', None), 141 | ('null.string', None), 142 | ('null.symbol', None), 143 | ('null.blob', None), 144 | ('null.clob', None), 145 | ('null.struct', None), 146 | ('null.list', None), 147 | ('null.sexp', None), 148 | 149 | # 150 | # Booleans 151 | # 152 | 153 | ('true', True), 154 | ('false', False), 155 | 156 | # 157 | # Integers 158 | # 159 | 160 | # Zero. Surprise! 161 | ('0', 0), 162 | 163 | # ...the same value with a minus sign 164 | ('-0', 0), 165 | 166 | # A normal int 167 | ('123', 123), 168 | 169 | # Another negative int 170 | ('-123', -123), 171 | 172 | # An int denoted in hexadecimal 173 | ('0xBeef', 0xBeef), 174 | 175 | # An int denoted in binary 176 | ('0b0101', 0b0101), 177 | 178 | # An int with underscores 179 | ('1_2_3', 123), 180 | 181 | # An int denoted in hexadecimal with underscores 182 | ('0xFA_CE', 0xFACE), 183 | 184 | # An int denoted in binary with underscores 185 | ('0b10_10_10', 0b101010), 186 | 187 | # ERROR: leading plus not allowed 188 | ('+1', PionException()), 189 | 190 | # ERROR: leading zeros not allowed (no support for octal notation) 191 | ('0123', PionException()), 192 | 193 | # ERROR: trailing underscore not allowed 194 | ('1_', PionException()), 195 | 196 | # ERROR: consecutive underscores not allowed 197 | ('1__2', PionException()), 198 | 199 | # ERROR: underscore can only appear between digits (the radix prefix is 200 | # not a digit) 201 | ('0x_12', PionException()), 202 | 203 | # A symbol (ints cannot start with underscores) 204 | ('_1', '_1'), 205 | 206 | 207 | # 208 | # Real Numbers 209 | # 210 | 211 | # Type is decimal 212 | ('0.123', Decimal('0.123')), 213 | 214 | # Type is float 215 | ('-0.12e4', -0.12e4), 216 | 217 | # Type is decimal 218 | ('-0.12d4', Decimal('-0.12e4')), 219 | 220 | # Zero as float 221 | ('0E0', float(0)), 222 | 223 | # Zero as decimal 224 | ('0D0', Decimal('0')), 225 | 226 | # ...the same value with different notation 227 | ('0.', Decimal('0')), 228 | 229 | # Negative zero float (distinct from positive zero) 230 | ('-0e0', float(-0)), 231 | 232 | # Negative zero decimal (distinct from positive zero) 233 | ('-0d0', Decimal('-0')), 234 | 235 | # ...the same value with different notation 236 | ('-0.', Decimal('-0')), 237 | 238 | # Decimal maintains precision: -0. != -0.0 239 | ('-0d-1', Decimal('-0.0')), 240 | 241 | # Decimal with underscores 242 | ('123_456.789_012', Decimal('123456.789012')), 243 | 244 | # ERROR: underscores may not appear next to the decimal point 245 | ('123_._456', PionException()), 246 | 247 | # ERROR: consecutive underscores not allowed 248 | ('12__34.56', PionException()), 249 | 250 | # ERROR: trailing underscore not allowed 251 | ('123.456_', PionException()), 252 | 253 | # ERROR: underscore after negative sign not allowed 254 | ('-_123.456', PionException()), 255 | 256 | # ERROR: the symbol '_123' followed by an unexpected dot 257 | ('_123.456', PionException()), 258 | 259 | 260 | # 261 | # Strings 262 | # 263 | 264 | # An empty string value 265 | ('""', ''), 266 | 267 | # A normal string 268 | ('" my string "', ' my string '), 269 | 270 | # Contains one double-quote character 271 | ('"\\""', '"'), 272 | 273 | # Contains one unicode character 274 | (r'"\uABCD"', '\uABCD'), 275 | 276 | # String with type annotation 'xml' 277 | ('xml::"c"', "c"), 278 | 279 | # Sexp with one element 280 | ("( '''hello '''\r'''world!''' )", ('hello world!',)), 281 | 282 | # The exact same sexp value 283 | ('("hello world!")', ('hello world!',)), 284 | 285 | # This Ion value is a string containing three newlines. The serialized 286 | # form's first newline is escaped into nothingness. 287 | (r"""'''\ 288 | The first line of the string. 289 | This is the second line of the string, 290 | and this is the third line. 291 | '''""", """The first line of the string. 292 | This is the second line of the string, 293 | and this is the third line. 294 | """), 295 | 296 | 297 | # 298 | # Symbols 299 | # 300 | 301 | # A symbol 302 | ("'myVar2'", 'myVar2'), 303 | 304 | # The same symbol 305 | ('myVar2', 'myVar2'), 306 | 307 | # A different symbol 308 | ('myvar2', 'myvar2'), 309 | 310 | # Symbol requiring quotes 311 | ("'hi ho'", 'hi ho'), 312 | 313 | # A symbol with embedded quotes 314 | (r"'\'ahoy\''", "'ahoy'"), 315 | 316 | # The empty symbol 317 | ("''", ''), 318 | 319 | # S-expression with three symbols 320 | ("( 'x' '+' 'y' )", ('x', '+', 'y')), 321 | 322 | # The same three symbols 323 | ("( x + y )", ('x', '+', 'y')), 324 | 325 | # The same three symbols 326 | ("(x+y)", ('x', '+', 'y')), 327 | 328 | # S-expression with seven symbols 329 | ("(a==b&&c==d)", ('a', '==', 'b', '&&', 'c', '==', 'd')), 330 | 331 | # 332 | # Blobs 333 | # 334 | 335 | # A valid blob value with zero padding characters. 336 | ( 337 | """{{ 338 | +AB/ 339 | }}""", 340 | bytearray(b64decode('+AB/'))), 341 | 342 | # A valid blob value with one required padding character. 343 | ( 344 | '{{ VG8gaW5maW5pdHkuLi4gYW5kIGJleW9uZCE= }}', 345 | bytearray(b64decode('VG8gaW5maW5pdHkuLi4gYW5kIGJleW9uZCE='))), 346 | 347 | # ERROR: Incorrect number of padding characters. 348 | ( 349 | '{{ VG8gaW5maW5pdHkuLi4gYW5kIGJleW9uZCE== }}', 350 | PionException()), 351 | 352 | # ERROR: Padding character within the data. 353 | ( 354 | '{{ VG8gaW5maW5pdHku=Li4gYW5kIGJleW9uZCE= }}', 355 | PionException()), 356 | 357 | # A valid blob value with two required padding characters. 358 | ( 359 | '{{ dHdvIHBhZGRpbmcgY2hhcmFjdGVycw== }}', 360 | bytearray(b64decode('dHdvIHBhZGRpbmcgY2hhcmFjdGVycw=='))), 361 | 362 | # ERROR: Invalid character within the data. 363 | ( 364 | '{{ dHdvIHBhZGRpbmc_gY2hhcmFjdGVycw= }}', 365 | PionException()), 366 | 367 | 368 | # 369 | # Clobs 370 | # 371 | 372 | ( 373 | '{{ "This is a CLOB of text." }}', 374 | b"This is a CLOB of text."), 375 | 376 | ( 377 | """shift_jis :: 378 | {{ 379 | '''Another clob with user-defined encoding, ''' 380 | '''this time on multiple lines.''' 381 | }}""", 382 | b"Another clob with user-defined encoding, " 383 | b"this time on multiple lines."), 384 | 385 | ( 386 | """{{ 387 | // ERROR 388 | "comments not allowed" 389 | }}""", 390 | PionException()), 391 | 392 | 393 | # 394 | # Structures 395 | # 396 | 397 | # An empty struct value 398 | ( 399 | '{ }', 400 | {}), 401 | 402 | # Structure with two fields 403 | ( 404 | '{ first : "Tom" , last: "Riddle" }', 405 | {'first': "Tom", 'last': "Riddle"}), 406 | 407 | # The same value with confusing style 408 | ( 409 | '{"first":"Tom","last":"Riddle"}', 410 | {"first": "Tom", "last": "Riddle"}), 411 | 412 | # Nested struct 413 | ( 414 | '{center:{x:1.0, y:12.5}, radius:3}', 415 | {'center': {'x': 1.0, 'y': 12.5}, 'radius': 3}), 416 | 417 | # Trailing comma is legal in Ion (unlike JSON) 418 | ( 419 | '{ x:1, }', 420 | {'x': 1}), 421 | 422 | # A struct value containing a field with an empty name 423 | ( 424 | '{ "":42 }', 425 | {"": 42}), 426 | 427 | # WARNING: repeated name 'x' leads to undefined behavior 428 | ( 429 | '{ x:1, x:null.int }', 430 | {'x': None}), 431 | 432 | # ERROR: missing field between commas 433 | ( 434 | '{ x:1, , }', 435 | PionException()), 436 | 437 | # 438 | # Lists 439 | # 440 | 441 | # An empty list value 442 | ( 443 | '[]', 444 | []), 445 | 446 | # List of three ints 447 | ( 448 | '[1, 2, 3]', 449 | [1, 2, 3]), 450 | 451 | # List of an int and a symbol 452 | ( 453 | '[ 1 , two ]', 454 | [1, 'two']), 455 | 456 | # Nested list 457 | ( 458 | '[a , [b]]', 459 | ['a', ['b']]), 460 | 461 | # Trailing comma is legal in Ion (unlike JSON) 462 | ( 463 | '[ 1.2, ]', 464 | [Decimal('1.2')]), 465 | 466 | # ERROR: missing element between commas 467 | ( 468 | '[ 1, , 2 ]', 469 | PionException()), 470 | 471 | # 472 | # S-Expressions 473 | 474 | # An empty expression value 475 | ( 476 | '()', 477 | ()), 478 | 479 | # S-expression of three values 480 | ( 481 | '(cons 1 2)', 482 | ('cons', 1, 2)), 483 | 484 | # S-expression containing two lists 485 | ( 486 | '([hello][there])', 487 | (['hello'], ['there'])), 488 | 489 | # Equivalent; three symbols 490 | ( 491 | "(a+-b)", 492 | ('a', '+-', 'b')), 493 | 494 | ( 495 | "( 'a' '+-' 'b' )", 496 | ('a', '+-', 'b')), 497 | 498 | # Equivalent; four symbols 499 | ( 500 | "(a.b;)", 501 | ('a', '.', 'b', ';')), 502 | 503 | ( 504 | "( 'a' '.' 'b' ';')", 505 | ('a', '.', 'b', ';')), 506 | 507 | # 508 | # Type Annotations 509 | # 510 | 511 | # Suggests 32 bits as end-user type 512 | ( 513 | 'nt32::12', 514 | 12), 515 | 516 | # Gives a struct a user-defined type 517 | ( 518 | "'my.custom.type' :: { x : 12 , y : -1 }", 519 | {'x': 12, 'y': -1}), 520 | 521 | # Field's name must precede annotations of its value 522 | ( 523 | "{ field: something::'another thing'::value }", 524 | {'field': 'value'}), 525 | 526 | # Indicates the blob contains jpeg data 527 | ( 528 | "jpeg :: {{ +AB/ }}", 529 | bytearray(b64decode('+AB/'))), 530 | 531 | # A very misleading annotation on the integer null 532 | ( 533 | 'bool :: null.int', 534 | None), 535 | 536 | # An empty annotation 537 | ( 538 | " '' :: 1", 539 | 1), 540 | 541 | # ERROR: type annotation cannot be null 542 | ( 543 | "null.symbol :: 1", 544 | PionException())]) 545 | def test_loads(ion_str, pyth): 546 | if isinstance(pyth, Exception): 547 | with pytest.raises(PionException): 548 | loads(ion_str) 549 | else: 550 | assert loads(ion_str) == pyth 551 | 552 | 553 | def test_dumps(): 554 | book = OrderedDict( 555 | ( 556 | ('title', 'A Hero of Our Time'), 557 | ('read_date', Datetime(2017, 7, 16, 14, 5, tzinfo=Timezone.utc)), 558 | ('would_recommend', True), 559 | ('description', None), 560 | ('number_of_novellas', 5), 561 | ('price', Decimal('7.99')), 562 | ('weight', 6.88), 563 | ('key', bytearray(b'kshhgrl')), 564 | ('tags', ['russian', 'novel', '19th centuary']))) 565 | 566 | ion_str = """{ 567 | 'description': null, 568 | 'key': {{ a3NoaGdybA== }}, 569 | 'number_of_novellas': 5, 570 | 'price': 7.99, 571 | 'read_date': 2017-07-16T14:05:00Z, 572 | 'tags': [ 573 | "russian", 574 | "novel", 575 | "19th centuary"], 576 | 'title': "A Hero of Our Time", 577 | 'weight': 6.88, 578 | 'would_recommend': true}""" 579 | 580 | assert dumps(book) == ion_str 581 | 582 | 583 | ''' 584 | def test_str(): 585 | pstr = '{ first : "Tom" , last: "Riddle" }' 586 | conv = loads(pstr) 587 | assert conv == {'first': "Tom", 'last': "Riddle"} 588 | for c in pstr: 589 | print(ord(c)) 590 | for c in conv: 591 | print(ord(c)) 592 | raise Exception() 593 | ''' 594 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py35 3 | 4 | 5 | [testenv] 6 | commands = 7 | flake8 --exclude=venv,.tox,pionic/antlr,build 8 | python setup.py check 9 | pytest 10 | python -m doctest README.adoc 11 | deps = 12 | pytest 13 | flake8 14 | -------------------------------------------------------------------------------- /versioneer.py: -------------------------------------------------------------------------------- 1 | 2 | # Version: 0.18 3 | 4 | """The Versioneer - like a rocketeer, but for versions. 5 | 6 | The Versioneer 7 | ============== 8 | 9 | * like a rocketeer, but for versions! 10 | * https://github.com/warner/python-versioneer 11 | * Brian Warner 12 | * License: Public Domain 13 | * Compatible With: python2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, and pypy 14 | * [![Latest Version] 15 | (https://pypip.in/version/versioneer/badge.svg?style=flat) 16 | ](https://pypi.python.org/pypi/versioneer/) 17 | * [![Build Status] 18 | (https://travis-ci.org/warner/python-versioneer.png?branch=master) 19 | ](https://travis-ci.org/warner/python-versioneer) 20 | 21 | This is a tool for managing a recorded version number in distutils-based 22 | python projects. The goal is to remove the tedious and error-prone "update 23 | the embedded version string" step from your release process. Making a new 24 | release should be as easy as recording a new tag in your version-control 25 | system, and maybe making new tarballs. 26 | 27 | 28 | ## Quick Install 29 | 30 | * `pip install versioneer` to somewhere to your $PATH 31 | * add a `[versioneer]` section to your setup.cfg (see below) 32 | * run `versioneer install` in your source tree, commit the results 33 | 34 | ## Version Identifiers 35 | 36 | Source trees come from a variety of places: 37 | 38 | * a version-control system checkout (mostly used by developers) 39 | * a nightly tarball, produced by build automation 40 | * a snapshot tarball, produced by a web-based VCS browser, like github's 41 | "tarball from tag" feature 42 | * a release tarball, produced by "setup.py sdist", distributed through PyPI 43 | 44 | Within each source tree, the version identifier (either a string or a number, 45 | this tool is format-agnostic) can come from a variety of places: 46 | 47 | * ask the VCS tool itself, e.g. "git describe" (for checkouts), which knows 48 | about recent "tags" and an absolute revision-id 49 | * the name of the directory into which the tarball was unpacked 50 | * an expanded VCS keyword ($Id$, etc) 51 | * a `_version.py` created by some earlier build step 52 | 53 | For released software, the version identifier is closely related to a VCS 54 | tag. Some projects use tag names that include more than just the version 55 | string (e.g. "myproject-1.2" instead of just "1.2"), in which case the tool 56 | needs to strip the tag prefix to extract the version identifier. For 57 | unreleased software (between tags), the version identifier should provide 58 | enough information to help developers recreate the same tree, while also 59 | giving them an idea of roughly how old the tree is (after version 1.2, before 60 | version 1.3). Many VCS systems can report a description that captures this, 61 | for example `git describe --tags --dirty --always` reports things like 62 | "0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the 63 | 0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has 64 | uncommitted changes. 65 | 66 | The version identifier is used for multiple purposes: 67 | 68 | * to allow the module to self-identify its version: `myproject.__version__` 69 | * to choose a name and prefix for a 'setup.py sdist' tarball 70 | 71 | ## Theory of Operation 72 | 73 | Versioneer works by adding a special `_version.py` file into your source 74 | tree, where your `__init__.py` can import it. This `_version.py` knows how to 75 | dynamically ask the VCS tool for version information at import time. 76 | 77 | `_version.py` also contains `$Revision$` markers, and the installation 78 | process marks `_version.py` to have this marker rewritten with a tag name 79 | during the `git archive` command. As a result, generated tarballs will 80 | contain enough information to get the proper version. 81 | 82 | To allow `setup.py` to compute a version too, a `versioneer.py` is added to 83 | the top level of your source tree, next to `setup.py` and the `setup.cfg` 84 | that configures it. This overrides several distutils/setuptools commands to 85 | compute the version when invoked, and changes `setup.py build` and `setup.py 86 | sdist` to replace `_version.py` with a small static file that contains just 87 | the generated version data. 88 | 89 | ## Installation 90 | 91 | See [INSTALL.md](./INSTALL.md) for detailed installation instructions. 92 | 93 | ## Version-String Flavors 94 | 95 | Code which uses Versioneer can learn about its version string at runtime by 96 | importing `_version` from your main `__init__.py` file and running the 97 | `get_versions()` function. From the "outside" (e.g. in `setup.py`), you can 98 | import the top-level `versioneer.py` and run `get_versions()`. 99 | 100 | Both functions return a dictionary with different flavors of version 101 | information: 102 | 103 | * `['version']`: A condensed version string, rendered using the selected 104 | style. This is the most commonly used value for the project's version 105 | string. The default "pep440" style yields strings like `0.11`, 106 | `0.11+2.g1076c97`, or `0.11+2.g1076c97.dirty`. See the "Styles" section 107 | below for alternative styles. 108 | 109 | * `['full-revisionid']`: detailed revision identifier. For Git, this is the 110 | full SHA1 commit id, e.g. "1076c978a8d3cfc70f408fe5974aa6c092c949ac". 111 | 112 | * `['date']`: Date and time of the latest `HEAD` commit. For Git, it is the 113 | commit date in ISO 8601 format. This will be None if the date is not 114 | available. 115 | 116 | * `['dirty']`: a boolean, True if the tree has uncommitted changes. Note that 117 | this is only accurate if run in a VCS checkout, otherwise it is likely to 118 | be False or None 119 | 120 | * `['error']`: if the version string could not be computed, this will be set 121 | to a string describing the problem, otherwise it will be None. It may be 122 | useful to throw an exception in setup.py if this is set, to avoid e.g. 123 | creating tarballs with a version string of "unknown". 124 | 125 | Some variants are more useful than others. Including `full-revisionid` in a 126 | bug report should allow developers to reconstruct the exact code being tested 127 | (or indicate the presence of local changes that should be shared with the 128 | developers). `version` is suitable for display in an "about" box or a CLI 129 | `--version` output: it can be easily compared against release notes and lists 130 | of bugs fixed in various releases. 131 | 132 | The installer adds the following text to your `__init__.py` to place a basic 133 | version in `YOURPROJECT.__version__`: 134 | 135 | from ._version import get_versions 136 | __version__ = get_versions()['version'] 137 | del get_versions 138 | 139 | ## Styles 140 | 141 | The setup.cfg `style=` configuration controls how the VCS information is 142 | rendered into a version string. 143 | 144 | The default style, "pep440", produces a PEP440-compliant string, equal to the 145 | un-prefixed tag name for actual releases, and containing an additional "local 146 | version" section with more detail for in-between builds. For Git, this is 147 | TAG[+DISTANCE.gHEX[.dirty]] , using information from `git describe --tags 148 | --dirty --always`. For example "0.11+2.g1076c97.dirty" indicates that the 149 | tree is like the "1076c97" commit but has uncommitted changes (".dirty"), and 150 | that this commit is two revisions ("+2") beyond the "0.11" tag. For released 151 | software (exactly equal to a known tag), the identifier will only contain the 152 | stripped tag, e.g. "0.11". 153 | 154 | Other styles are available. See [details.md](details.md) in the Versioneer 155 | source tree for descriptions. 156 | 157 | ## Debugging 158 | 159 | Versioneer tries to avoid fatal errors: if something goes wrong, it will tend 160 | to return a version of "0+unknown". To investigate the problem, run `setup.py 161 | version`, which will run the version-lookup code in a verbose mode, and will 162 | display the full contents of `get_versions()` (including the `error` string, 163 | which may help identify what went wrong). 164 | 165 | ## Known Limitations 166 | 167 | Some situations are known to cause problems for Versioneer. This details the 168 | most significant ones. More can be found on Github 169 | [issues page](https://github.com/warner/python-versioneer/issues). 170 | 171 | ### Subprojects 172 | 173 | Versioneer has limited support for source trees in which `setup.py` is not in 174 | the root directory (e.g. `setup.py` and `.git/` are *not* siblings). The are 175 | two common reasons why `setup.py` might not be in the root: 176 | 177 | * Source trees which contain multiple subprojects, such as 178 | [Buildbot](https://github.com/buildbot/buildbot), which contains both 179 | "master" and "slave" subprojects, each with their own `setup.py`, 180 | `setup.cfg`, and `tox.ini`. Projects like these produce multiple PyPI 181 | distributions (and upload multiple independently-installable tarballs). 182 | * Source trees whose main purpose is to contain a C library, but which also 183 | provide bindings to Python (and perhaps other langauges) in subdirectories. 184 | 185 | Versioneer will look for `.git` in parent directories, and most operations 186 | should get the right version string. However `pip` and `setuptools` have bugs 187 | and implementation details which frequently cause `pip install .` from a 188 | subproject directory to fail to find a correct version string (so it usually 189 | defaults to `0+unknown`). 190 | 191 | `pip install --editable .` should work correctly. `setup.py install` might 192 | work too. 193 | 194 | Pip-8.1.1 is known to have this problem, but hopefully it will get fixed in 195 | some later version. 196 | 197 | [Bug #38](https://github.com/warner/python-versioneer/issues/38) is tracking 198 | this issue. The discussion in 199 | [PR #61](https://github.com/warner/python-versioneer/pull/61) describes the 200 | issue from the Versioneer side in more detail. 201 | [pip PR#3176](https://github.com/pypa/pip/pull/3176) and 202 | [pip PR#3615](https://github.com/pypa/pip/pull/3615) contain work to improve 203 | pip to let Versioneer work correctly. 204 | 205 | Versioneer-0.16 and earlier only looked for a `.git` directory next to the 206 | `setup.cfg`, so subprojects were completely unsupported with those releases. 207 | 208 | ### Editable installs with setuptools <= 18.5 209 | 210 | `setup.py develop` and `pip install --editable .` allow you to install a 211 | project into a virtualenv once, then continue editing the source code (and 212 | test) without re-installing after every change. 213 | 214 | "Entry-point scripts" (`setup(entry_points={"console_scripts": ..})`) are a 215 | convenient way to specify executable scripts that should be installed along 216 | with the python package. 217 | 218 | These both work as expected when using modern setuptools. When using 219 | setuptools-18.5 or earlier, however, certain operations will cause 220 | `pkg_resources.DistributionNotFound` errors when running the entrypoint 221 | script, which must be resolved by re-installing the package. This happens 222 | when the install happens with one version, then the egg_info data is 223 | regenerated while a different version is checked out. Many setup.py commands 224 | cause egg_info to be rebuilt (including `sdist`, `wheel`, and installing into 225 | a different virtualenv), so this can be surprising. 226 | 227 | [Bug #83](https://github.com/warner/python-versioneer/issues/83) describes 228 | this one, but upgrading to a newer version of setuptools should probably 229 | resolve it. 230 | 231 | ### Unicode version strings 232 | 233 | While Versioneer works (and is continually tested) with both Python 2 and 234 | Python 3, it is not entirely consistent with bytes-vs-unicode distinctions. 235 | Newer releases probably generate unicode version strings on py2. It's not 236 | clear that this is wrong, but it may be surprising for applications when then 237 | write these strings to a network connection or include them in bytes-oriented 238 | APIs like cryptographic checksums. 239 | 240 | [Bug #71](https://github.com/warner/python-versioneer/issues/71) investigates 241 | this question. 242 | 243 | 244 | ## Updating Versioneer 245 | 246 | To upgrade your project to a new release of Versioneer, do the following: 247 | 248 | * install the new Versioneer (`pip install -U versioneer` or equivalent) 249 | * edit `setup.cfg`, if necessary, to include any new configuration settings 250 | indicated by the release notes. See [UPGRADING](./UPGRADING.md) for details. 251 | * re-run `versioneer install` in your source tree, to replace 252 | `SRC/_version.py` 253 | * commit any changed files 254 | 255 | ## Future Directions 256 | 257 | This tool is designed to make it easily extended to other version-control 258 | systems: all VCS-specific components are in separate directories like 259 | src/git/ . The top-level `versioneer.py` script is assembled from these 260 | components by running make-versioneer.py . In the future, make-versioneer.py 261 | will take a VCS name as an argument, and will construct a version of 262 | `versioneer.py` that is specific to the given VCS. It might also take the 263 | configuration arguments that are currently provided manually during 264 | installation by editing setup.py . Alternatively, it might go the other 265 | direction and include code from all supported VCS systems, reducing the 266 | number of intermediate scripts. 267 | 268 | 269 | ## License 270 | 271 | To make Versioneer easier to embed, all its code is dedicated to the public 272 | domain. The `_version.py` that it creates is also in the public domain. 273 | Specifically, both are released under the Creative Commons "Public Domain 274 | Dedication" license (CC0-1.0), as described in 275 | https://creativecommons.org/publicdomain/zero/1.0/ . 276 | 277 | """ 278 | 279 | from __future__ import print_function 280 | try: 281 | import configparser 282 | except ImportError: 283 | import ConfigParser as configparser 284 | import errno 285 | import json 286 | import os 287 | import re 288 | import subprocess 289 | import sys 290 | 291 | 292 | class VersioneerConfig: 293 | """Container for Versioneer configuration parameters.""" 294 | 295 | 296 | def get_root(): 297 | """Get the project root directory. 298 | 299 | We require that all commands are run from the project root, i.e. the 300 | directory that contains setup.py, setup.cfg, and versioneer.py . 301 | """ 302 | root = os.path.realpath(os.path.abspath(os.getcwd())) 303 | setup_py = os.path.join(root, "setup.py") 304 | versioneer_py = os.path.join(root, "versioneer.py") 305 | if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): 306 | # allow 'python path/to/setup.py COMMAND' 307 | root = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0]))) 308 | setup_py = os.path.join(root, "setup.py") 309 | versioneer_py = os.path.join(root, "versioneer.py") 310 | if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): 311 | err = ("Versioneer was unable to run the project root directory. " 312 | "Versioneer requires setup.py to be executed from " 313 | "its immediate directory (like 'python setup.py COMMAND'), " 314 | "or in a way that lets it use sys.argv[0] to find the root " 315 | "(like 'python path/to/setup.py COMMAND').") 316 | raise VersioneerBadRootError(err) 317 | try: 318 | # Certain runtime workflows (setup.py install/develop in a setuptools 319 | # tree) execute all dependencies in a single python process, so 320 | # "versioneer" may be imported multiple times, and python's shared 321 | # module-import table will cache the first one. So we can't use 322 | # os.path.dirname(__file__), as that will find whichever 323 | # versioneer.py was first imported, even in later projects. 324 | me = os.path.realpath(os.path.abspath(__file__)) 325 | me_dir = os.path.normcase(os.path.splitext(me)[0]) 326 | vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0]) 327 | if me_dir != vsr_dir: 328 | print("Warning: build in %s is using versioneer.py from %s" 329 | % (os.path.dirname(me), versioneer_py)) 330 | except NameError: 331 | pass 332 | return root 333 | 334 | 335 | def get_config_from_root(root): 336 | """Read the project setup.cfg file to determine Versioneer config.""" 337 | # This might raise EnvironmentError (if setup.cfg is missing), or 338 | # configparser.NoSectionError (if it lacks a [versioneer] section), or 339 | # configparser.NoOptionError (if it lacks "VCS="). See the docstring at 340 | # the top of versioneer.py for instructions on writing your setup.cfg . 341 | setup_cfg = os.path.join(root, "setup.cfg") 342 | parser = configparser.SafeConfigParser() 343 | with open(setup_cfg, "r") as f: 344 | parser.readfp(f) 345 | VCS = parser.get("versioneer", "VCS") # mandatory 346 | 347 | def get(parser, name): 348 | if parser.has_option("versioneer", name): 349 | return parser.get("versioneer", name) 350 | return None 351 | cfg = VersioneerConfig() 352 | cfg.VCS = VCS 353 | cfg.style = get(parser, "style") or "" 354 | cfg.versionfile_source = get(parser, "versionfile_source") 355 | cfg.versionfile_build = get(parser, "versionfile_build") 356 | cfg.tag_prefix = get(parser, "tag_prefix") 357 | if cfg.tag_prefix in ("''", '""'): 358 | cfg.tag_prefix = "" 359 | cfg.parentdir_prefix = get(parser, "parentdir_prefix") 360 | cfg.verbose = get(parser, "verbose") 361 | return cfg 362 | 363 | 364 | class NotThisMethod(Exception): 365 | """Exception raised if a method is not valid for the current scenario.""" 366 | 367 | 368 | # these dictionaries contain VCS-specific tools 369 | LONG_VERSION_PY = {} 370 | HANDLERS = {} 371 | 372 | 373 | def register_vcs_handler(vcs, method): # decorator 374 | """Decorator to mark a method as the handler for a particular VCS.""" 375 | def decorate(f): 376 | """Store f in HANDLERS[vcs][method].""" 377 | if vcs not in HANDLERS: 378 | HANDLERS[vcs] = {} 379 | HANDLERS[vcs][method] = f 380 | return f 381 | return decorate 382 | 383 | 384 | def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, 385 | env=None): 386 | """Call the given command(s).""" 387 | assert isinstance(commands, list) 388 | p = None 389 | for c in commands: 390 | try: 391 | dispcmd = str([c] + args) 392 | # remember shell=False, so use git.cmd on windows, not just git 393 | p = subprocess.Popen([c] + args, cwd=cwd, env=env, 394 | stdout=subprocess.PIPE, 395 | stderr=(subprocess.PIPE if hide_stderr 396 | else None)) 397 | break 398 | except EnvironmentError: 399 | e = sys.exc_info()[1] 400 | if e.errno == errno.ENOENT: 401 | continue 402 | if verbose: 403 | print("unable to run %s" % dispcmd) 404 | print(e) 405 | return None, None 406 | else: 407 | if verbose: 408 | print("unable to find command, tried %s" % (commands,)) 409 | return None, None 410 | stdout = p.communicate()[0].strip() 411 | if sys.version_info[0] >= 3: 412 | stdout = stdout.decode() 413 | if p.returncode != 0: 414 | if verbose: 415 | print("unable to run %s (error)" % dispcmd) 416 | print("stdout was %s" % stdout) 417 | return None, p.returncode 418 | return stdout, p.returncode 419 | 420 | 421 | LONG_VERSION_PY['git'] = ''' 422 | # This file helps to compute a version number in source trees obtained from 423 | # git-archive tarball (such as those provided by githubs download-from-tag 424 | # feature). Distribution tarballs (built by setup.py sdist) and build 425 | # directories (produced by setup.py build) will contain a much shorter file 426 | # that just contains the computed version number. 427 | 428 | # This file is released into the public domain. Generated by 429 | # versioneer-0.18 (https://github.com/warner/python-versioneer) 430 | 431 | """Git implementation of _version.py.""" 432 | 433 | import errno 434 | import os 435 | import re 436 | import subprocess 437 | import sys 438 | 439 | 440 | def get_keywords(): 441 | """Get the keywords needed to look up the version information.""" 442 | # these strings will be replaced by git during git-archive. 443 | # setup.py/versioneer.py will grep for the variable names, so they must 444 | # each be defined on a line of their own. _version.py will just call 445 | # get_keywords(). 446 | git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s" 447 | git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s" 448 | git_date = "%(DOLLAR)sFormat:%%ci%(DOLLAR)s" 449 | keywords = {"refnames": git_refnames, "full": git_full, "date": git_date} 450 | return keywords 451 | 452 | 453 | class VersioneerConfig: 454 | """Container for Versioneer configuration parameters.""" 455 | 456 | 457 | def get_config(): 458 | """Create, populate and return the VersioneerConfig() object.""" 459 | # these strings are filled in when 'setup.py versioneer' creates 460 | # _version.py 461 | cfg = VersioneerConfig() 462 | cfg.VCS = "git" 463 | cfg.style = "%(STYLE)s" 464 | cfg.tag_prefix = "%(TAG_PREFIX)s" 465 | cfg.parentdir_prefix = "%(PARENTDIR_PREFIX)s" 466 | cfg.versionfile_source = "%(VERSIONFILE_SOURCE)s" 467 | cfg.verbose = False 468 | return cfg 469 | 470 | 471 | class NotThisMethod(Exception): 472 | """Exception raised if a method is not valid for the current scenario.""" 473 | 474 | 475 | LONG_VERSION_PY = {} 476 | HANDLERS = {} 477 | 478 | 479 | def register_vcs_handler(vcs, method): # decorator 480 | """Decorator to mark a method as the handler for a particular VCS.""" 481 | def decorate(f): 482 | """Store f in HANDLERS[vcs][method].""" 483 | if vcs not in HANDLERS: 484 | HANDLERS[vcs] = {} 485 | HANDLERS[vcs][method] = f 486 | return f 487 | return decorate 488 | 489 | 490 | def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, 491 | env=None): 492 | """Call the given command(s).""" 493 | assert isinstance(commands, list) 494 | p = None 495 | for c in commands: 496 | try: 497 | dispcmd = str([c] + args) 498 | # remember shell=False, so use git.cmd on windows, not just git 499 | p = subprocess.Popen([c] + args, cwd=cwd, env=env, 500 | stdout=subprocess.PIPE, 501 | stderr=(subprocess.PIPE if hide_stderr 502 | else None)) 503 | break 504 | except EnvironmentError: 505 | e = sys.exc_info()[1] 506 | if e.errno == errno.ENOENT: 507 | continue 508 | if verbose: 509 | print("unable to run %%s" %% dispcmd) 510 | print(e) 511 | return None, None 512 | else: 513 | if verbose: 514 | print("unable to find command, tried %%s" %% (commands,)) 515 | return None, None 516 | stdout = p.communicate()[0].strip() 517 | if sys.version_info[0] >= 3: 518 | stdout = stdout.decode() 519 | if p.returncode != 0: 520 | if verbose: 521 | print("unable to run %%s (error)" %% dispcmd) 522 | print("stdout was %%s" %% stdout) 523 | return None, p.returncode 524 | return stdout, p.returncode 525 | 526 | 527 | def versions_from_parentdir(parentdir_prefix, root, verbose): 528 | """Try to determine the version from the parent directory name. 529 | 530 | Source tarballs conventionally unpack into a directory that includes both 531 | the project name and a version string. We will also support searching up 532 | two directory levels for an appropriately named parent directory 533 | """ 534 | rootdirs = [] 535 | 536 | for i in range(3): 537 | dirname = os.path.basename(root) 538 | if dirname.startswith(parentdir_prefix): 539 | return {"version": dirname[len(parentdir_prefix):], 540 | "full-revisionid": None, 541 | "dirty": False, "error": None, "date": None} 542 | else: 543 | rootdirs.append(root) 544 | root = os.path.dirname(root) # up a level 545 | 546 | if verbose: 547 | print("Tried directories %%s but none started with prefix %%s" %% 548 | (str(rootdirs), parentdir_prefix)) 549 | raise NotThisMethod("rootdir doesn't start with parentdir_prefix") 550 | 551 | 552 | @register_vcs_handler("git", "get_keywords") 553 | def git_get_keywords(versionfile_abs): 554 | """Extract version information from the given file.""" 555 | # the code embedded in _version.py can just fetch the value of these 556 | # keywords. When used from setup.py, we don't want to import _version.py, 557 | # so we do it with a regexp instead. This function is not used from 558 | # _version.py. 559 | keywords = {} 560 | try: 561 | f = open(versionfile_abs, "r") 562 | for line in f.readlines(): 563 | if line.strip().startswith("git_refnames ="): 564 | mo = re.search(r'=\s*"(.*)"', line) 565 | if mo: 566 | keywords["refnames"] = mo.group(1) 567 | if line.strip().startswith("git_full ="): 568 | mo = re.search(r'=\s*"(.*)"', line) 569 | if mo: 570 | keywords["full"] = mo.group(1) 571 | if line.strip().startswith("git_date ="): 572 | mo = re.search(r'=\s*"(.*)"', line) 573 | if mo: 574 | keywords["date"] = mo.group(1) 575 | f.close() 576 | except EnvironmentError: 577 | pass 578 | return keywords 579 | 580 | 581 | @register_vcs_handler("git", "keywords") 582 | def git_versions_from_keywords(keywords, tag_prefix, verbose): 583 | """Get version information from git keywords.""" 584 | if not keywords: 585 | raise NotThisMethod("no keywords at all, weird") 586 | date = keywords.get("date") 587 | if date is not None: 588 | # git-2.2.0 added "%%cI", which expands to an ISO-8601 -compliant 589 | # datestamp. However we prefer "%%ci" (which expands to an "ISO-8601 590 | # -like" string, which we must then edit to make compliant), because 591 | # it's been around since git-1.5.3, and it's too difficult to 592 | # discover which version we're using, or to work around using an 593 | # older one. 594 | date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) 595 | refnames = keywords["refnames"].strip() 596 | if refnames.startswith("$Format"): 597 | if verbose: 598 | print("keywords are unexpanded, not using") 599 | raise NotThisMethod("unexpanded keywords, not a git-archive tarball") 600 | refs = set([r.strip() for r in refnames.strip("()").split(",")]) 601 | # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of 602 | # just "foo-1.0". If we see a "tag: " prefix, prefer those. 603 | TAG = "tag: " 604 | tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) 605 | if not tags: 606 | # Either we're using git < 1.8.3, or there really are no tags. We use 607 | # a heuristic: assume all version tags have a digit. The old git %%d 608 | # expansion behaves like git log --decorate=short and strips out the 609 | # refs/heads/ and refs/tags/ prefixes that would let us distinguish 610 | # between branches and tags. By ignoring refnames without digits, we 611 | # filter out many common branch names like "release" and 612 | # "stabilization", as well as "HEAD" and "master". 613 | tags = set([r for r in refs if re.search(r'\d', r)]) 614 | if verbose: 615 | print("discarding '%%s', no digits" %% ",".join(refs - tags)) 616 | if verbose: 617 | print("likely tags: %%s" %% ",".join(sorted(tags))) 618 | for ref in sorted(tags): 619 | # sorting will prefer e.g. "2.0" over "2.0rc1" 620 | if ref.startswith(tag_prefix): 621 | r = ref[len(tag_prefix):] 622 | if verbose: 623 | print("picking %%s" %% r) 624 | return {"version": r, 625 | "full-revisionid": keywords["full"].strip(), 626 | "dirty": False, "error": None, 627 | "date": date} 628 | # no suitable tags, so version is "0+unknown", but full hex is still there 629 | if verbose: 630 | print("no suitable tags, using unknown + full revision id") 631 | return {"version": "0+unknown", 632 | "full-revisionid": keywords["full"].strip(), 633 | "dirty": False, "error": "no suitable tags", "date": None} 634 | 635 | 636 | @register_vcs_handler("git", "pieces_from_vcs") 637 | def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): 638 | """Get version from 'git describe' in the root of the source tree. 639 | 640 | This only gets called if the git-archive 'subst' keywords were *not* 641 | expanded, and _version.py hasn't already been rewritten with a short 642 | version string, meaning we're inside a checked out source tree. 643 | """ 644 | GITS = ["git"] 645 | if sys.platform == "win32": 646 | GITS = ["git.cmd", "git.exe"] 647 | 648 | out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, 649 | hide_stderr=True) 650 | if rc != 0: 651 | if verbose: 652 | print("Directory %%s not under git control" %% root) 653 | raise NotThisMethod("'git rev-parse --git-dir' returned error") 654 | 655 | # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] 656 | # if there isn't one, this yields HEX[-dirty] (no NUM) 657 | describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty", 658 | "--always", "--long", 659 | "--match", "%%s*" %% tag_prefix], 660 | cwd=root) 661 | # --long was added in git-1.5.5 662 | if describe_out is None: 663 | raise NotThisMethod("'git describe' failed") 664 | describe_out = describe_out.strip() 665 | full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) 666 | if full_out is None: 667 | raise NotThisMethod("'git rev-parse' failed") 668 | full_out = full_out.strip() 669 | 670 | pieces = {} 671 | pieces["long"] = full_out 672 | pieces["short"] = full_out[:7] # maybe improved later 673 | pieces["error"] = None 674 | 675 | # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] 676 | # TAG might have hyphens. 677 | git_describe = describe_out 678 | 679 | # look for -dirty suffix 680 | dirty = git_describe.endswith("-dirty") 681 | pieces["dirty"] = dirty 682 | if dirty: 683 | git_describe = git_describe[:git_describe.rindex("-dirty")] 684 | 685 | # now we have TAG-NUM-gHEX or HEX 686 | 687 | if "-" in git_describe: 688 | # TAG-NUM-gHEX 689 | mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) 690 | if not mo: 691 | # unparseable. Maybe git-describe is misbehaving? 692 | pieces["error"] = ("unable to parse git-describe output: '%%s'" 693 | %% describe_out) 694 | return pieces 695 | 696 | # tag 697 | full_tag = mo.group(1) 698 | if not full_tag.startswith(tag_prefix): 699 | if verbose: 700 | fmt = "tag '%%s' doesn't start with prefix '%%s'" 701 | print(fmt %% (full_tag, tag_prefix)) 702 | pieces["error"] = ("tag '%%s' doesn't start with prefix '%%s'" 703 | %% (full_tag, tag_prefix)) 704 | return pieces 705 | pieces["closest-tag"] = full_tag[len(tag_prefix):] 706 | 707 | # distance: number of commits since tag 708 | pieces["distance"] = int(mo.group(2)) 709 | 710 | # commit: short hex revision ID 711 | pieces["short"] = mo.group(3) 712 | 713 | else: 714 | # HEX: no tags 715 | pieces["closest-tag"] = None 716 | count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], 717 | cwd=root) 718 | pieces["distance"] = int(count_out) # total number of commits 719 | 720 | # commit date: see ISO-8601 comment in git_versions_from_keywords() 721 | date = run_command(GITS, ["show", "-s", "--format=%%ci", "HEAD"], 722 | cwd=root)[0].strip() 723 | pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) 724 | 725 | return pieces 726 | 727 | 728 | def plus_or_dot(pieces): 729 | """Return a + if we don't already have one, else return a .""" 730 | if "+" in pieces.get("closest-tag", ""): 731 | return "." 732 | return "+" 733 | 734 | 735 | def render_pep440(pieces): 736 | """Build up version string, with post-release "local version identifier". 737 | 738 | Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you 739 | get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty 740 | 741 | Exceptions: 742 | 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] 743 | """ 744 | if pieces["closest-tag"]: 745 | rendered = pieces["closest-tag"] 746 | if pieces["distance"] or pieces["dirty"]: 747 | rendered += plus_or_dot(pieces) 748 | rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"]) 749 | if pieces["dirty"]: 750 | rendered += ".dirty" 751 | else: 752 | # exception #1 753 | rendered = "0+untagged.%%d.g%%s" %% (pieces["distance"], 754 | pieces["short"]) 755 | if pieces["dirty"]: 756 | rendered += ".dirty" 757 | return rendered 758 | 759 | 760 | def render_pep440_pre(pieces): 761 | """TAG[.post.devDISTANCE] -- No -dirty. 762 | 763 | Exceptions: 764 | 1: no tags. 0.post.devDISTANCE 765 | """ 766 | if pieces["closest-tag"]: 767 | rendered = pieces["closest-tag"] 768 | if pieces["distance"]: 769 | rendered += ".post.dev%%d" %% pieces["distance"] 770 | else: 771 | # exception #1 772 | rendered = "0.post.dev%%d" %% pieces["distance"] 773 | return rendered 774 | 775 | 776 | def render_pep440_post(pieces): 777 | """TAG[.postDISTANCE[.dev0]+gHEX] . 778 | 779 | The ".dev0" means dirty. Note that .dev0 sorts backwards 780 | (a dirty tree will appear "older" than the corresponding clean one), 781 | but you shouldn't be releasing software with -dirty anyways. 782 | 783 | Exceptions: 784 | 1: no tags. 0.postDISTANCE[.dev0] 785 | """ 786 | if pieces["closest-tag"]: 787 | rendered = pieces["closest-tag"] 788 | if pieces["distance"] or pieces["dirty"]: 789 | rendered += ".post%%d" %% pieces["distance"] 790 | if pieces["dirty"]: 791 | rendered += ".dev0" 792 | rendered += plus_or_dot(pieces) 793 | rendered += "g%%s" %% pieces["short"] 794 | else: 795 | # exception #1 796 | rendered = "0.post%%d" %% pieces["distance"] 797 | if pieces["dirty"]: 798 | rendered += ".dev0" 799 | rendered += "+g%%s" %% pieces["short"] 800 | return rendered 801 | 802 | 803 | def render_pep440_old(pieces): 804 | """TAG[.postDISTANCE[.dev0]] . 805 | 806 | The ".dev0" means dirty. 807 | 808 | Eexceptions: 809 | 1: no tags. 0.postDISTANCE[.dev0] 810 | """ 811 | if pieces["closest-tag"]: 812 | rendered = pieces["closest-tag"] 813 | if pieces["distance"] or pieces["dirty"]: 814 | rendered += ".post%%d" %% pieces["distance"] 815 | if pieces["dirty"]: 816 | rendered += ".dev0" 817 | else: 818 | # exception #1 819 | rendered = "0.post%%d" %% pieces["distance"] 820 | if pieces["dirty"]: 821 | rendered += ".dev0" 822 | return rendered 823 | 824 | 825 | def render_git_describe(pieces): 826 | """TAG[-DISTANCE-gHEX][-dirty]. 827 | 828 | Like 'git describe --tags --dirty --always'. 829 | 830 | Exceptions: 831 | 1: no tags. HEX[-dirty] (note: no 'g' prefix) 832 | """ 833 | if pieces["closest-tag"]: 834 | rendered = pieces["closest-tag"] 835 | if pieces["distance"]: 836 | rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) 837 | else: 838 | # exception #1 839 | rendered = pieces["short"] 840 | if pieces["dirty"]: 841 | rendered += "-dirty" 842 | return rendered 843 | 844 | 845 | def render_git_describe_long(pieces): 846 | """TAG-DISTANCE-gHEX[-dirty]. 847 | 848 | Like 'git describe --tags --dirty --always -long'. 849 | The distance/hash is unconditional. 850 | 851 | Exceptions: 852 | 1: no tags. HEX[-dirty] (note: no 'g' prefix) 853 | """ 854 | if pieces["closest-tag"]: 855 | rendered = pieces["closest-tag"] 856 | rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) 857 | else: 858 | # exception #1 859 | rendered = pieces["short"] 860 | if pieces["dirty"]: 861 | rendered += "-dirty" 862 | return rendered 863 | 864 | 865 | def render(pieces, style): 866 | """Render the given version pieces into the requested style.""" 867 | if pieces["error"]: 868 | return {"version": "unknown", 869 | "full-revisionid": pieces.get("long"), 870 | "dirty": None, 871 | "error": pieces["error"], 872 | "date": None} 873 | 874 | if not style or style == "default": 875 | style = "pep440" # the default 876 | 877 | if style == "pep440": 878 | rendered = render_pep440(pieces) 879 | elif style == "pep440-pre": 880 | rendered = render_pep440_pre(pieces) 881 | elif style == "pep440-post": 882 | rendered = render_pep440_post(pieces) 883 | elif style == "pep440-old": 884 | rendered = render_pep440_old(pieces) 885 | elif style == "git-describe": 886 | rendered = render_git_describe(pieces) 887 | elif style == "git-describe-long": 888 | rendered = render_git_describe_long(pieces) 889 | else: 890 | raise ValueError("unknown style '%%s'" %% style) 891 | 892 | return {"version": rendered, "full-revisionid": pieces["long"], 893 | "dirty": pieces["dirty"], "error": None, 894 | "date": pieces.get("date")} 895 | 896 | 897 | def get_versions(): 898 | """Get version information or return default if unable to do so.""" 899 | # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have 900 | # __file__, we can work backwards from there to the root. Some 901 | # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which 902 | # case we can only use expanded keywords. 903 | 904 | cfg = get_config() 905 | verbose = cfg.verbose 906 | 907 | try: 908 | return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, 909 | verbose) 910 | except NotThisMethod: 911 | pass 912 | 913 | try: 914 | root = os.path.realpath(__file__) 915 | # versionfile_source is the relative path from the top of the source 916 | # tree (where the .git directory might live) to this file. Invert 917 | # this to find the root from __file__. 918 | for i in cfg.versionfile_source.split('/'): 919 | root = os.path.dirname(root) 920 | except NameError: 921 | return {"version": "0+unknown", "full-revisionid": None, 922 | "dirty": None, 923 | "error": "unable to find root of source tree", 924 | "date": None} 925 | 926 | try: 927 | pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) 928 | return render(pieces, cfg.style) 929 | except NotThisMethod: 930 | pass 931 | 932 | try: 933 | if cfg.parentdir_prefix: 934 | return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) 935 | except NotThisMethod: 936 | pass 937 | 938 | return {"version": "0+unknown", "full-revisionid": None, 939 | "dirty": None, 940 | "error": "unable to compute version", "date": None} 941 | ''' 942 | 943 | 944 | @register_vcs_handler("git", "get_keywords") 945 | def git_get_keywords(versionfile_abs): 946 | """Extract version information from the given file.""" 947 | # the code embedded in _version.py can just fetch the value of these 948 | # keywords. When used from setup.py, we don't want to import _version.py, 949 | # so we do it with a regexp instead. This function is not used from 950 | # _version.py. 951 | keywords = {} 952 | try: 953 | f = open(versionfile_abs, "r") 954 | for line in f.readlines(): 955 | if line.strip().startswith("git_refnames ="): 956 | mo = re.search(r'=\s*"(.*)"', line) 957 | if mo: 958 | keywords["refnames"] = mo.group(1) 959 | if line.strip().startswith("git_full ="): 960 | mo = re.search(r'=\s*"(.*)"', line) 961 | if mo: 962 | keywords["full"] = mo.group(1) 963 | if line.strip().startswith("git_date ="): 964 | mo = re.search(r'=\s*"(.*)"', line) 965 | if mo: 966 | keywords["date"] = mo.group(1) 967 | f.close() 968 | except EnvironmentError: 969 | pass 970 | return keywords 971 | 972 | 973 | @register_vcs_handler("git", "keywords") 974 | def git_versions_from_keywords(keywords, tag_prefix, verbose): 975 | """Get version information from git keywords.""" 976 | if not keywords: 977 | raise NotThisMethod("no keywords at all, weird") 978 | date = keywords.get("date") 979 | if date is not None: 980 | # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant 981 | # datestamp. However we prefer "%ci" (which expands to an "ISO-8601 982 | # -like" string, which we must then edit to make compliant), because 983 | # it's been around since git-1.5.3, and it's too difficult to 984 | # discover which version we're using, or to work around using an 985 | # older one. 986 | date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) 987 | refnames = keywords["refnames"].strip() 988 | if refnames.startswith("$Format"): 989 | if verbose: 990 | print("keywords are unexpanded, not using") 991 | raise NotThisMethod("unexpanded keywords, not a git-archive tarball") 992 | refs = set([r.strip() for r in refnames.strip("()").split(",")]) 993 | # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of 994 | # just "foo-1.0". If we see a "tag: " prefix, prefer those. 995 | TAG = "tag: " 996 | tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) 997 | if not tags: 998 | # Either we're using git < 1.8.3, or there really are no tags. We use 999 | # a heuristic: assume all version tags have a digit. The old git %d 1000 | # expansion behaves like git log --decorate=short and strips out the 1001 | # refs/heads/ and refs/tags/ prefixes that would let us distinguish 1002 | # between branches and tags. By ignoring refnames without digits, we 1003 | # filter out many common branch names like "release" and 1004 | # "stabilization", as well as "HEAD" and "master". 1005 | tags = set([r for r in refs if re.search(r'\d', r)]) 1006 | if verbose: 1007 | print("discarding '%s', no digits" % ",".join(refs - tags)) 1008 | if verbose: 1009 | print("likely tags: %s" % ",".join(sorted(tags))) 1010 | for ref in sorted(tags): 1011 | # sorting will prefer e.g. "2.0" over "2.0rc1" 1012 | if ref.startswith(tag_prefix): 1013 | r = ref[len(tag_prefix):] 1014 | if verbose: 1015 | print("picking %s" % r) 1016 | return {"version": r, 1017 | "full-revisionid": keywords["full"].strip(), 1018 | "dirty": False, "error": None, 1019 | "date": date} 1020 | # no suitable tags, so version is "0+unknown", but full hex is still there 1021 | if verbose: 1022 | print("no suitable tags, using unknown + full revision id") 1023 | return {"version": "0+unknown", 1024 | "full-revisionid": keywords["full"].strip(), 1025 | "dirty": False, "error": "no suitable tags", "date": None} 1026 | 1027 | 1028 | @register_vcs_handler("git", "pieces_from_vcs") 1029 | def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): 1030 | """Get version from 'git describe' in the root of the source tree. 1031 | 1032 | This only gets called if the git-archive 'subst' keywords were *not* 1033 | expanded, and _version.py hasn't already been rewritten with a short 1034 | version string, meaning we're inside a checked out source tree. 1035 | """ 1036 | GITS = ["git"] 1037 | if sys.platform == "win32": 1038 | GITS = ["git.cmd", "git.exe"] 1039 | 1040 | out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, 1041 | hide_stderr=True) 1042 | if rc != 0: 1043 | if verbose: 1044 | print("Directory %s not under git control" % root) 1045 | raise NotThisMethod("'git rev-parse --git-dir' returned error") 1046 | 1047 | # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] 1048 | # if there isn't one, this yields HEX[-dirty] (no NUM) 1049 | describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty", 1050 | "--always", "--long", 1051 | "--match", "%s*" % tag_prefix], 1052 | cwd=root) 1053 | # --long was added in git-1.5.5 1054 | if describe_out is None: 1055 | raise NotThisMethod("'git describe' failed") 1056 | describe_out = describe_out.strip() 1057 | full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) 1058 | if full_out is None: 1059 | raise NotThisMethod("'git rev-parse' failed") 1060 | full_out = full_out.strip() 1061 | 1062 | pieces = {} 1063 | pieces["long"] = full_out 1064 | pieces["short"] = full_out[:7] # maybe improved later 1065 | pieces["error"] = None 1066 | 1067 | # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] 1068 | # TAG might have hyphens. 1069 | git_describe = describe_out 1070 | 1071 | # look for -dirty suffix 1072 | dirty = git_describe.endswith("-dirty") 1073 | pieces["dirty"] = dirty 1074 | if dirty: 1075 | git_describe = git_describe[:git_describe.rindex("-dirty")] 1076 | 1077 | # now we have TAG-NUM-gHEX or HEX 1078 | 1079 | if "-" in git_describe: 1080 | # TAG-NUM-gHEX 1081 | mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) 1082 | if not mo: 1083 | # unparseable. Maybe git-describe is misbehaving? 1084 | pieces["error"] = ("unable to parse git-describe output: '%s'" 1085 | % describe_out) 1086 | return pieces 1087 | 1088 | # tag 1089 | full_tag = mo.group(1) 1090 | if not full_tag.startswith(tag_prefix): 1091 | if verbose: 1092 | fmt = "tag '%s' doesn't start with prefix '%s'" 1093 | print(fmt % (full_tag, tag_prefix)) 1094 | pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" 1095 | % (full_tag, tag_prefix)) 1096 | return pieces 1097 | pieces["closest-tag"] = full_tag[len(tag_prefix):] 1098 | 1099 | # distance: number of commits since tag 1100 | pieces["distance"] = int(mo.group(2)) 1101 | 1102 | # commit: short hex revision ID 1103 | pieces["short"] = mo.group(3) 1104 | 1105 | else: 1106 | # HEX: no tags 1107 | pieces["closest-tag"] = None 1108 | count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], 1109 | cwd=root) 1110 | pieces["distance"] = int(count_out) # total number of commits 1111 | 1112 | # commit date: see ISO-8601 comment in git_versions_from_keywords() 1113 | date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"], 1114 | cwd=root)[0].strip() 1115 | pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) 1116 | 1117 | return pieces 1118 | 1119 | 1120 | def do_vcs_install(manifest_in, versionfile_source, ipy): 1121 | """Git-specific installation logic for Versioneer. 1122 | 1123 | For Git, this means creating/changing .gitattributes to mark _version.py 1124 | for export-subst keyword substitution. 1125 | """ 1126 | GITS = ["git"] 1127 | if sys.platform == "win32": 1128 | GITS = ["git.cmd", "git.exe"] 1129 | files = [manifest_in, versionfile_source] 1130 | if ipy: 1131 | files.append(ipy) 1132 | try: 1133 | me = __file__ 1134 | if me.endswith(".pyc") or me.endswith(".pyo"): 1135 | me = os.path.splitext(me)[0] + ".py" 1136 | versioneer_file = os.path.relpath(me) 1137 | except NameError: 1138 | versioneer_file = "versioneer.py" 1139 | files.append(versioneer_file) 1140 | present = False 1141 | try: 1142 | f = open(".gitattributes", "r") 1143 | for line in f.readlines(): 1144 | if line.strip().startswith(versionfile_source): 1145 | if "export-subst" in line.strip().split()[1:]: 1146 | present = True 1147 | f.close() 1148 | except EnvironmentError: 1149 | pass 1150 | if not present: 1151 | f = open(".gitattributes", "a+") 1152 | f.write("%s export-subst\n" % versionfile_source) 1153 | f.close() 1154 | files.append(".gitattributes") 1155 | run_command(GITS, ["add", "--"] + files) 1156 | 1157 | 1158 | def versions_from_parentdir(parentdir_prefix, root, verbose): 1159 | """Try to determine the version from the parent directory name. 1160 | 1161 | Source tarballs conventionally unpack into a directory that includes both 1162 | the project name and a version string. We will also support searching up 1163 | two directory levels for an appropriately named parent directory 1164 | """ 1165 | rootdirs = [] 1166 | 1167 | for i in range(3): 1168 | dirname = os.path.basename(root) 1169 | if dirname.startswith(parentdir_prefix): 1170 | return {"version": dirname[len(parentdir_prefix):], 1171 | "full-revisionid": None, 1172 | "dirty": False, "error": None, "date": None} 1173 | else: 1174 | rootdirs.append(root) 1175 | root = os.path.dirname(root) # up a level 1176 | 1177 | if verbose: 1178 | print("Tried directories %s but none started with prefix %s" % 1179 | (str(rootdirs), parentdir_prefix)) 1180 | raise NotThisMethod("rootdir doesn't start with parentdir_prefix") 1181 | 1182 | 1183 | SHORT_VERSION_PY = """ 1184 | # This file was generated by 'versioneer.py' (0.18) from 1185 | # revision-control system data, or from the parent directory name of an 1186 | # unpacked source archive. Distribution tarballs contain a pre-generated copy 1187 | # of this file. 1188 | 1189 | import json 1190 | 1191 | version_json = ''' 1192 | %s 1193 | ''' # END VERSION_JSON 1194 | 1195 | 1196 | def get_versions(): 1197 | return json.loads(version_json) 1198 | """ 1199 | 1200 | 1201 | def versions_from_file(filename): 1202 | """Try to determine the version from _version.py if present.""" 1203 | try: 1204 | with open(filename) as f: 1205 | contents = f.read() 1206 | except EnvironmentError: 1207 | raise NotThisMethod("unable to read _version.py") 1208 | mo = re.search(r"version_json = '''\n(.*)''' # END VERSION_JSON", 1209 | contents, re.M | re.S) 1210 | if not mo: 1211 | mo = re.search(r"version_json = '''\r\n(.*)''' # END VERSION_JSON", 1212 | contents, re.M | re.S) 1213 | if not mo: 1214 | raise NotThisMethod("no version_json in _version.py") 1215 | return json.loads(mo.group(1)) 1216 | 1217 | 1218 | def write_to_version_file(filename, versions): 1219 | """Write the given version number to the given _version.py file.""" 1220 | os.unlink(filename) 1221 | contents = json.dumps(versions, sort_keys=True, 1222 | indent=1, separators=(",", ": ")) 1223 | with open(filename, "w") as f: 1224 | f.write(SHORT_VERSION_PY % contents) 1225 | 1226 | print("set %s to '%s'" % (filename, versions["version"])) 1227 | 1228 | 1229 | def plus_or_dot(pieces): 1230 | """Return a + if we don't already have one, else return a .""" 1231 | if "+" in pieces.get("closest-tag", ""): 1232 | return "." 1233 | return "+" 1234 | 1235 | 1236 | def render_pep440(pieces): 1237 | """Build up version string, with post-release "local version identifier". 1238 | 1239 | Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you 1240 | get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty 1241 | 1242 | Exceptions: 1243 | 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] 1244 | """ 1245 | if pieces["closest-tag"]: 1246 | rendered = pieces["closest-tag"] 1247 | if pieces["distance"] or pieces["dirty"]: 1248 | rendered += plus_or_dot(pieces) 1249 | rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) 1250 | if pieces["dirty"]: 1251 | rendered += ".dirty" 1252 | else: 1253 | # exception #1 1254 | rendered = "0+untagged.%d.g%s" % (pieces["distance"], 1255 | pieces["short"]) 1256 | if pieces["dirty"]: 1257 | rendered += ".dirty" 1258 | return rendered 1259 | 1260 | 1261 | def render_pep440_pre(pieces): 1262 | """TAG[.post.devDISTANCE] -- No -dirty. 1263 | 1264 | Exceptions: 1265 | 1: no tags. 0.post.devDISTANCE 1266 | """ 1267 | if pieces["closest-tag"]: 1268 | rendered = pieces["closest-tag"] 1269 | if pieces["distance"]: 1270 | rendered += ".post.dev%d" % pieces["distance"] 1271 | else: 1272 | # exception #1 1273 | rendered = "0.post.dev%d" % pieces["distance"] 1274 | return rendered 1275 | 1276 | 1277 | def render_pep440_post(pieces): 1278 | """TAG[.postDISTANCE[.dev0]+gHEX] . 1279 | 1280 | The ".dev0" means dirty. Note that .dev0 sorts backwards 1281 | (a dirty tree will appear "older" than the corresponding clean one), 1282 | but you shouldn't be releasing software with -dirty anyways. 1283 | 1284 | Exceptions: 1285 | 1: no tags. 0.postDISTANCE[.dev0] 1286 | """ 1287 | if pieces["closest-tag"]: 1288 | rendered = pieces["closest-tag"] 1289 | if pieces["distance"] or pieces["dirty"]: 1290 | rendered += ".post%d" % pieces["distance"] 1291 | if pieces["dirty"]: 1292 | rendered += ".dev0" 1293 | rendered += plus_or_dot(pieces) 1294 | rendered += "g%s" % pieces["short"] 1295 | else: 1296 | # exception #1 1297 | rendered = "0.post%d" % pieces["distance"] 1298 | if pieces["dirty"]: 1299 | rendered += ".dev0" 1300 | rendered += "+g%s" % pieces["short"] 1301 | return rendered 1302 | 1303 | 1304 | def render_pep440_old(pieces): 1305 | """TAG[.postDISTANCE[.dev0]] . 1306 | 1307 | The ".dev0" means dirty. 1308 | 1309 | Eexceptions: 1310 | 1: no tags. 0.postDISTANCE[.dev0] 1311 | """ 1312 | if pieces["closest-tag"]: 1313 | rendered = pieces["closest-tag"] 1314 | if pieces["distance"] or pieces["dirty"]: 1315 | rendered += ".post%d" % pieces["distance"] 1316 | if pieces["dirty"]: 1317 | rendered += ".dev0" 1318 | else: 1319 | # exception #1 1320 | rendered = "0.post%d" % pieces["distance"] 1321 | if pieces["dirty"]: 1322 | rendered += ".dev0" 1323 | return rendered 1324 | 1325 | 1326 | def render_git_describe(pieces): 1327 | """TAG[-DISTANCE-gHEX][-dirty]. 1328 | 1329 | Like 'git describe --tags --dirty --always'. 1330 | 1331 | Exceptions: 1332 | 1: no tags. HEX[-dirty] (note: no 'g' prefix) 1333 | """ 1334 | if pieces["closest-tag"]: 1335 | rendered = pieces["closest-tag"] 1336 | if pieces["distance"]: 1337 | rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) 1338 | else: 1339 | # exception #1 1340 | rendered = pieces["short"] 1341 | if pieces["dirty"]: 1342 | rendered += "-dirty" 1343 | return rendered 1344 | 1345 | 1346 | def render_git_describe_long(pieces): 1347 | """TAG-DISTANCE-gHEX[-dirty]. 1348 | 1349 | Like 'git describe --tags --dirty --always -long'. 1350 | The distance/hash is unconditional. 1351 | 1352 | Exceptions: 1353 | 1: no tags. HEX[-dirty] (note: no 'g' prefix) 1354 | """ 1355 | if pieces["closest-tag"]: 1356 | rendered = pieces["closest-tag"] 1357 | rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) 1358 | else: 1359 | # exception #1 1360 | rendered = pieces["short"] 1361 | if pieces["dirty"]: 1362 | rendered += "-dirty" 1363 | return rendered 1364 | 1365 | 1366 | def render(pieces, style): 1367 | """Render the given version pieces into the requested style.""" 1368 | if pieces["error"]: 1369 | return {"version": "unknown", 1370 | "full-revisionid": pieces.get("long"), 1371 | "dirty": None, 1372 | "error": pieces["error"], 1373 | "date": None} 1374 | 1375 | if not style or style == "default": 1376 | style = "pep440" # the default 1377 | 1378 | if style == "pep440": 1379 | rendered = render_pep440(pieces) 1380 | elif style == "pep440-pre": 1381 | rendered = render_pep440_pre(pieces) 1382 | elif style == "pep440-post": 1383 | rendered = render_pep440_post(pieces) 1384 | elif style == "pep440-old": 1385 | rendered = render_pep440_old(pieces) 1386 | elif style == "git-describe": 1387 | rendered = render_git_describe(pieces) 1388 | elif style == "git-describe-long": 1389 | rendered = render_git_describe_long(pieces) 1390 | else: 1391 | raise ValueError("unknown style '%s'" % style) 1392 | 1393 | return {"version": rendered, "full-revisionid": pieces["long"], 1394 | "dirty": pieces["dirty"], "error": None, 1395 | "date": pieces.get("date")} 1396 | 1397 | 1398 | class VersioneerBadRootError(Exception): 1399 | """The project root directory is unknown or missing key files.""" 1400 | 1401 | 1402 | def get_versions(verbose=False): 1403 | """Get the project version from whatever source is available. 1404 | 1405 | Returns dict with two keys: 'version' and 'full'. 1406 | """ 1407 | if "versioneer" in sys.modules: 1408 | # see the discussion in cmdclass.py:get_cmdclass() 1409 | del sys.modules["versioneer"] 1410 | 1411 | root = get_root() 1412 | cfg = get_config_from_root(root) 1413 | 1414 | assert cfg.VCS is not None, "please set [versioneer]VCS= in setup.cfg" 1415 | handlers = HANDLERS.get(cfg.VCS) 1416 | assert handlers, "unrecognized VCS '%s'" % cfg.VCS 1417 | verbose = verbose or cfg.verbose 1418 | assert cfg.versionfile_source is not None, \ 1419 | "please set versioneer.versionfile_source" 1420 | assert cfg.tag_prefix is not None, "please set versioneer.tag_prefix" 1421 | 1422 | versionfile_abs = os.path.join(root, cfg.versionfile_source) 1423 | 1424 | # extract version from first of: _version.py, VCS command (e.g. 'git 1425 | # describe'), parentdir. This is meant to work for developers using a 1426 | # source checkout, for users of a tarball created by 'setup.py sdist', 1427 | # and for users of a tarball/zipball created by 'git archive' or github's 1428 | # download-from-tag feature or the equivalent in other VCSes. 1429 | 1430 | get_keywords_f = handlers.get("get_keywords") 1431 | from_keywords_f = handlers.get("keywords") 1432 | if get_keywords_f and from_keywords_f: 1433 | try: 1434 | keywords = get_keywords_f(versionfile_abs) 1435 | ver = from_keywords_f(keywords, cfg.tag_prefix, verbose) 1436 | if verbose: 1437 | print("got version from expanded keyword %s" % ver) 1438 | return ver 1439 | except NotThisMethod: 1440 | pass 1441 | 1442 | try: 1443 | ver = versions_from_file(versionfile_abs) 1444 | if verbose: 1445 | print("got version from file %s %s" % (versionfile_abs, ver)) 1446 | return ver 1447 | except NotThisMethod: 1448 | pass 1449 | 1450 | from_vcs_f = handlers.get("pieces_from_vcs") 1451 | if from_vcs_f: 1452 | try: 1453 | pieces = from_vcs_f(cfg.tag_prefix, root, verbose) 1454 | ver = render(pieces, cfg.style) 1455 | if verbose: 1456 | print("got version from VCS %s" % ver) 1457 | return ver 1458 | except NotThisMethod: 1459 | pass 1460 | 1461 | try: 1462 | if cfg.parentdir_prefix: 1463 | ver = versions_from_parentdir(cfg.parentdir_prefix, root, verbose) 1464 | if verbose: 1465 | print("got version from parentdir %s" % ver) 1466 | return ver 1467 | except NotThisMethod: 1468 | pass 1469 | 1470 | if verbose: 1471 | print("unable to compute version") 1472 | 1473 | return {"version": "0+unknown", "full-revisionid": None, 1474 | "dirty": None, "error": "unable to compute version", 1475 | "date": None} 1476 | 1477 | 1478 | def get_version(): 1479 | """Get the short version string for this project.""" 1480 | return get_versions()["version"] 1481 | 1482 | 1483 | def get_cmdclass(): 1484 | """Get the custom setuptools/distutils subclasses used by Versioneer.""" 1485 | if "versioneer" in sys.modules: 1486 | del sys.modules["versioneer"] 1487 | # this fixes the "python setup.py develop" case (also 'install' and 1488 | # 'easy_install .'), in which subdependencies of the main project are 1489 | # built (using setup.py bdist_egg) in the same python process. Assume 1490 | # a main project A and a dependency B, which use different versions 1491 | # of Versioneer. A's setup.py imports A's Versioneer, leaving it in 1492 | # sys.modules by the time B's setup.py is executed, causing B to run 1493 | # with the wrong versioneer. Setuptools wraps the sub-dep builds in a 1494 | # sandbox that restores sys.modules to it's pre-build state, so the 1495 | # parent is protected against the child's "import versioneer". By 1496 | # removing ourselves from sys.modules here, before the child build 1497 | # happens, we protect the child from the parent's versioneer too. 1498 | # Also see https://github.com/warner/python-versioneer/issues/52 1499 | 1500 | cmds = {} 1501 | 1502 | # we add "version" to both distutils and setuptools 1503 | from distutils.core import Command 1504 | 1505 | class cmd_version(Command): 1506 | description = "report generated version string" 1507 | user_options = [] 1508 | boolean_options = [] 1509 | 1510 | def initialize_options(self): 1511 | pass 1512 | 1513 | def finalize_options(self): 1514 | pass 1515 | 1516 | def run(self): 1517 | vers = get_versions(verbose=True) 1518 | print("Version: %s" % vers["version"]) 1519 | print(" full-revisionid: %s" % vers.get("full-revisionid")) 1520 | print(" dirty: %s" % vers.get("dirty")) 1521 | print(" date: %s" % vers.get("date")) 1522 | if vers["error"]: 1523 | print(" error: %s" % vers["error"]) 1524 | cmds["version"] = cmd_version 1525 | 1526 | # we override "build_py" in both distutils and setuptools 1527 | # 1528 | # most invocation pathways end up running build_py: 1529 | # distutils/build -> build_py 1530 | # distutils/install -> distutils/build ->.. 1531 | # setuptools/bdist_wheel -> distutils/install ->.. 1532 | # setuptools/bdist_egg -> distutils/install_lib -> build_py 1533 | # setuptools/install -> bdist_egg ->.. 1534 | # setuptools/develop -> ? 1535 | # pip install: 1536 | # copies source tree to a tempdir before running egg_info/etc 1537 | # if .git isn't copied too, 'git describe' will fail 1538 | # then does setup.py bdist_wheel, or sometimes setup.py install 1539 | # setup.py egg_info -> ? 1540 | 1541 | # we override different "build_py" commands for both environments 1542 | if "setuptools" in sys.modules: 1543 | from setuptools.command.build_py import build_py as _build_py 1544 | else: 1545 | from distutils.command.build_py import build_py as _build_py 1546 | 1547 | class cmd_build_py(_build_py): 1548 | def run(self): 1549 | root = get_root() 1550 | cfg = get_config_from_root(root) 1551 | versions = get_versions() 1552 | _build_py.run(self) 1553 | # now locate _version.py in the new build/ directory and replace 1554 | # it with an updated value 1555 | if cfg.versionfile_build: 1556 | target_versionfile = os.path.join(self.build_lib, 1557 | cfg.versionfile_build) 1558 | print("UPDATING %s" % target_versionfile) 1559 | write_to_version_file(target_versionfile, versions) 1560 | cmds["build_py"] = cmd_build_py 1561 | 1562 | if "cx_Freeze" in sys.modules: # cx_freeze enabled? 1563 | from cx_Freeze.dist import build_exe as _build_exe 1564 | # nczeczulin reports that py2exe won't like the pep440-style string 1565 | # as FILEVERSION, but it can be used for PRODUCTVERSION, e.g. 1566 | # setup(console=[{ 1567 | # "version": versioneer.get_version().split("+", 1)[0], # FILEVERSION 1568 | # "product_version": versioneer.get_version(), 1569 | # ... 1570 | 1571 | class cmd_build_exe(_build_exe): 1572 | def run(self): 1573 | root = get_root() 1574 | cfg = get_config_from_root(root) 1575 | versions = get_versions() 1576 | target_versionfile = cfg.versionfile_source 1577 | print("UPDATING %s" % target_versionfile) 1578 | write_to_version_file(target_versionfile, versions) 1579 | 1580 | _build_exe.run(self) 1581 | os.unlink(target_versionfile) 1582 | with open(cfg.versionfile_source, "w") as f: 1583 | LONG = LONG_VERSION_PY[cfg.VCS] 1584 | f.write(LONG % 1585 | {"DOLLAR": "$", 1586 | "STYLE": cfg.style, 1587 | "TAG_PREFIX": cfg.tag_prefix, 1588 | "PARENTDIR_PREFIX": cfg.parentdir_prefix, 1589 | "VERSIONFILE_SOURCE": cfg.versionfile_source, 1590 | }) 1591 | cmds["build_exe"] = cmd_build_exe 1592 | del cmds["build_py"] 1593 | 1594 | if 'py2exe' in sys.modules: # py2exe enabled? 1595 | try: 1596 | from py2exe.distutils_buildexe import py2exe as _py2exe # py3 1597 | except ImportError: 1598 | from py2exe.build_exe import py2exe as _py2exe # py2 1599 | 1600 | class cmd_py2exe(_py2exe): 1601 | def run(self): 1602 | root = get_root() 1603 | cfg = get_config_from_root(root) 1604 | versions = get_versions() 1605 | target_versionfile = cfg.versionfile_source 1606 | print("UPDATING %s" % target_versionfile) 1607 | write_to_version_file(target_versionfile, versions) 1608 | 1609 | _py2exe.run(self) 1610 | os.unlink(target_versionfile) 1611 | with open(cfg.versionfile_source, "w") as f: 1612 | LONG = LONG_VERSION_PY[cfg.VCS] 1613 | f.write(LONG % 1614 | {"DOLLAR": "$", 1615 | "STYLE": cfg.style, 1616 | "TAG_PREFIX": cfg.tag_prefix, 1617 | "PARENTDIR_PREFIX": cfg.parentdir_prefix, 1618 | "VERSIONFILE_SOURCE": cfg.versionfile_source, 1619 | }) 1620 | cmds["py2exe"] = cmd_py2exe 1621 | 1622 | # we override different "sdist" commands for both environments 1623 | if "setuptools" in sys.modules: 1624 | from setuptools.command.sdist import sdist as _sdist 1625 | else: 1626 | from distutils.command.sdist import sdist as _sdist 1627 | 1628 | class cmd_sdist(_sdist): 1629 | def run(self): 1630 | versions = get_versions() 1631 | self._versioneer_generated_versions = versions 1632 | # unless we update this, the command will keep using the old 1633 | # version 1634 | self.distribution.metadata.version = versions["version"] 1635 | return _sdist.run(self) 1636 | 1637 | def make_release_tree(self, base_dir, files): 1638 | root = get_root() 1639 | cfg = get_config_from_root(root) 1640 | _sdist.make_release_tree(self, base_dir, files) 1641 | # now locate _version.py in the new base_dir directory 1642 | # (remembering that it may be a hardlink) and replace it with an 1643 | # updated value 1644 | target_versionfile = os.path.join(base_dir, cfg.versionfile_source) 1645 | print("UPDATING %s" % target_versionfile) 1646 | write_to_version_file(target_versionfile, 1647 | self._versioneer_generated_versions) 1648 | cmds["sdist"] = cmd_sdist 1649 | 1650 | return cmds 1651 | 1652 | 1653 | CONFIG_ERROR = """ 1654 | setup.cfg is missing the necessary Versioneer configuration. You need 1655 | a section like: 1656 | 1657 | [versioneer] 1658 | VCS = git 1659 | style = pep440 1660 | versionfile_source = src/myproject/_version.py 1661 | versionfile_build = myproject/_version.py 1662 | tag_prefix = 1663 | parentdir_prefix = myproject- 1664 | 1665 | You will also need to edit your setup.py to use the results: 1666 | 1667 | import versioneer 1668 | setup(version=versioneer.get_version(), 1669 | cmdclass=versioneer.get_cmdclass(), ...) 1670 | 1671 | Please read the docstring in ./versioneer.py for configuration instructions, 1672 | edit setup.cfg, and re-run the installer or 'python versioneer.py setup'. 1673 | """ 1674 | 1675 | SAMPLE_CONFIG = """ 1676 | # See the docstring in versioneer.py for instructions. Note that you must 1677 | # re-run 'versioneer.py setup' after changing this section, and commit the 1678 | # resulting files. 1679 | 1680 | [versioneer] 1681 | #VCS = git 1682 | #style = pep440 1683 | #versionfile_source = 1684 | #versionfile_build = 1685 | #tag_prefix = 1686 | #parentdir_prefix = 1687 | 1688 | """ 1689 | 1690 | INIT_PY_SNIPPET = """ 1691 | from ._version import get_versions 1692 | __version__ = get_versions()['version'] 1693 | del get_versions 1694 | """ 1695 | 1696 | 1697 | def do_setup(): 1698 | """Main VCS-independent setup function for installing Versioneer.""" 1699 | root = get_root() 1700 | try: 1701 | cfg = get_config_from_root(root) 1702 | except (EnvironmentError, configparser.NoSectionError, 1703 | configparser.NoOptionError) as e: 1704 | if isinstance(e, (EnvironmentError, configparser.NoSectionError)): 1705 | print("Adding sample versioneer config to setup.cfg", 1706 | file=sys.stderr) 1707 | with open(os.path.join(root, "setup.cfg"), "a") as f: 1708 | f.write(SAMPLE_CONFIG) 1709 | print(CONFIG_ERROR, file=sys.stderr) 1710 | return 1 1711 | 1712 | print(" creating %s" % cfg.versionfile_source) 1713 | with open(cfg.versionfile_source, "w") as f: 1714 | LONG = LONG_VERSION_PY[cfg.VCS] 1715 | f.write(LONG % {"DOLLAR": "$", 1716 | "STYLE": cfg.style, 1717 | "TAG_PREFIX": cfg.tag_prefix, 1718 | "PARENTDIR_PREFIX": cfg.parentdir_prefix, 1719 | "VERSIONFILE_SOURCE": cfg.versionfile_source, 1720 | }) 1721 | 1722 | ipy = os.path.join(os.path.dirname(cfg.versionfile_source), 1723 | "__init__.py") 1724 | if os.path.exists(ipy): 1725 | try: 1726 | with open(ipy, "r") as f: 1727 | old = f.read() 1728 | except EnvironmentError: 1729 | old = "" 1730 | if INIT_PY_SNIPPET not in old: 1731 | print(" appending to %s" % ipy) 1732 | with open(ipy, "a") as f: 1733 | f.write(INIT_PY_SNIPPET) 1734 | else: 1735 | print(" %s unmodified" % ipy) 1736 | else: 1737 | print(" %s doesn't exist, ok" % ipy) 1738 | ipy = None 1739 | 1740 | # Make sure both the top-level "versioneer.py" and versionfile_source 1741 | # (PKG/_version.py, used by runtime code) are in MANIFEST.in, so 1742 | # they'll be copied into source distributions. Pip won't be able to 1743 | # install the package without this. 1744 | manifest_in = os.path.join(root, "MANIFEST.in") 1745 | simple_includes = set() 1746 | try: 1747 | with open(manifest_in, "r") as f: 1748 | for line in f: 1749 | if line.startswith("include "): 1750 | for include in line.split()[1:]: 1751 | simple_includes.add(include) 1752 | except EnvironmentError: 1753 | pass 1754 | # That doesn't cover everything MANIFEST.in can do 1755 | # (http://docs.python.org/2/distutils/sourcedist.html#commands), so 1756 | # it might give some false negatives. Appending redundant 'include' 1757 | # lines is safe, though. 1758 | if "versioneer.py" not in simple_includes: 1759 | print(" appending 'versioneer.py' to MANIFEST.in") 1760 | with open(manifest_in, "a") as f: 1761 | f.write("include versioneer.py\n") 1762 | else: 1763 | print(" 'versioneer.py' already in MANIFEST.in") 1764 | if cfg.versionfile_source not in simple_includes: 1765 | print(" appending versionfile_source ('%s') to MANIFEST.in" % 1766 | cfg.versionfile_source) 1767 | with open(manifest_in, "a") as f: 1768 | f.write("include %s\n" % cfg.versionfile_source) 1769 | else: 1770 | print(" versionfile_source already in MANIFEST.in") 1771 | 1772 | # Make VCS-specific changes. For git, this means creating/changing 1773 | # .gitattributes to mark _version.py for export-subst keyword 1774 | # substitution. 1775 | do_vcs_install(manifest_in, cfg.versionfile_source, ipy) 1776 | return 0 1777 | 1778 | 1779 | def scan_setup_py(): 1780 | """Validate the contents of setup.py against Versioneer's expectations.""" 1781 | found = set() 1782 | setters = False 1783 | errors = 0 1784 | with open("setup.py", "r") as f: 1785 | for line in f.readlines(): 1786 | if "import versioneer" in line: 1787 | found.add("import") 1788 | if "versioneer.get_cmdclass()" in line: 1789 | found.add("cmdclass") 1790 | if "versioneer.get_version()" in line: 1791 | found.add("get_version") 1792 | if "versioneer.VCS" in line: 1793 | setters = True 1794 | if "versioneer.versionfile_source" in line: 1795 | setters = True 1796 | if len(found) != 3: 1797 | print("") 1798 | print("Your setup.py appears to be missing some important items") 1799 | print("(but I might be wrong). Please make sure it has something") 1800 | print("roughly like the following:") 1801 | print("") 1802 | print(" import versioneer") 1803 | print(" setup( version=versioneer.get_version(),") 1804 | print(" cmdclass=versioneer.get_cmdclass(), ...)") 1805 | print("") 1806 | errors += 1 1807 | if setters: 1808 | print("You should remove lines like 'versioneer.VCS = ' and") 1809 | print("'versioneer.versionfile_source = ' . This configuration") 1810 | print("now lives in setup.cfg, and should be removed from setup.py") 1811 | print("") 1812 | errors += 1 1813 | return errors 1814 | 1815 | 1816 | if __name__ == "__main__": 1817 | cmd = sys.argv[1] 1818 | if cmd == "setup": 1819 | errors = do_setup() 1820 | errors += scan_setup_py() 1821 | if errors: 1822 | sys.exit(1) 1823 | --------------------------------------------------------------------------------