import re import sys import json import packaging.version from collections import UserDict, UserList, namedtuple class JSONList(UserList): def __init__(self, data=None): self.data = [] if data is None else data def __getitem__(self, i): return _wrap(self.data[i]) def __setitem__(self, i, val): self.data[i] = _unwrap(val) def __str__(self): return json.dumps(self.data) class JSONDict(UserDict): data = None def __init__(self, data=None): self.data = {} if data is None else data def __getattr__(self, name): try: return super().__getattribute__(name) except AttributeError as ex: try: return _wrap(self.data[name]) except KeyError: raise ex def __setattr__(self, name, val): try: super().__getattribute__(name) except AttributeError: self.data[name] = _unwrap(val) else: return super().__setattr__(name, val) def __str__(self): return json.dumps(self.data, sort_keys=True) def _wrap(obj): if isinstance(obj, dict): return JSONDict(obj) elif isinstance(obj, list): return JSONList(obj) else: return obj def _unwrap(obj): if isinstance(obj, UserList) or isinstance(obj, UserDict): return obj.data return obj def prompt(prompt="Continue?", choices="Y/n"): default_choice = None for choice in choices: if choice.isupper(): default_choice = choice.lower() break while True: print( "%s [%s]" % (prompt, choices), end=' ', flush=True ) reply = sys.stdin.readline().strip().lower() if not reply: return default_choice elif reply in choices.lower() and reply != '/': return reply else: print("Please answer with one of %s." % choices) REQUIREMENT_RE = re.compile( r'^(?P<name>[^<>=!]+)' r'(?P<specifier>(?:==|!=|>=?|<=?).*)?$' ) Requirement = namedtuple('Requirement', 'name specifier') Requirement.__str__ = lambda self: '%s%s' % (self.name, self.specifier) class Version(packaging.version.Version): def __init__(self, version): if isinstance(version, packaging.version.Version): version = str(version) super().__init__(version) def _compare(self, other, method): if isinstance(other, str): other = Version(other) return super()._compare(other, method) def parse_requirement(text): """ Parse a requirement such as 'foo>=1.0'. Returns a (name, specifier) named tuple. """ from packaging.specifiers import SpecifierSet match = REQUIREMENT_RE.match(text) if not match: raise ValueError("Invalid requirement: %s" % text) name = match.group('name').strip() spec = SpecifierSet(match.group('specifier') or '') return Requirement(name, spec) def parse_game_version(info): if 'info_json' in info: info = info.info_json try: version = '.'.join(info.factorio_version.split('.')[:2]) return Version(version) except AttributeError: return Version('0.12') def match_game_version(release, game_version): if game_version is None: return True release_version = parse_game_version(release) return release_version == game_version class ProgressWidget: def __init__(self, text, file=sys.stderr): self.text = text self.done = False self.file = file self.progress = None self.maxprint = 0 if not self.file.isatty(): self.print(text + "\n") self.done = True else: self(0, 0) def print(self, text): self.maxprint = max(self.maxprint, len(text)) print("\r" + text.ljust(self.maxprint), end='', flush=True, file=self.file) def error(self, exc): if not self.done: self.print("%s error" % self.text) self.done = True print(file=self.file) def finish(self): if not self.done: self.done = True print(file=self.file) def __enter__(self): return self def __exit__(self, exc_type=None, exc_value=None, traceback=None): if exc_value: self.error(exc_value) else: self.finish() def __call__(self, cur, tot): if self.done: return if tot: progress = int(100 * cur / tot) if self.progress == progress: return self.progress = progress self.print("%s %d %%" % (self.text, progress)) if cur == tot: self.finish() else: self.print(self.text) def start_iter(it): first = next(it) def run(): yield first yield from it return run()