diff options
Diffstat (limited to 'Tools/c-analyzer/c_analyzer_common/util.py')
-rw-r--r-- | Tools/c-analyzer/c_analyzer_common/util.py | 214 |
1 files changed, 214 insertions, 0 deletions
diff --git a/Tools/c-analyzer/c_analyzer_common/util.py b/Tools/c-analyzer/c_analyzer_common/util.py new file mode 100644 index 0000000..511c54f --- /dev/null +++ b/Tools/c-analyzer/c_analyzer_common/util.py @@ -0,0 +1,214 @@ +import csv +import subprocess + + +_NOT_SET = object() + + +def run_cmd(argv, **kwargs): + proc = subprocess.run( + argv, + #capture_output=True, + #stderr=subprocess.STDOUT, + stdout=subprocess.PIPE, + text=True, + check=True, + **kwargs + ) + return proc.stdout + + +def read_tsv(infile, header, *, + _open=open, + _get_reader=csv.reader, + ): + """Yield each row of the given TSV (tab-separated) file.""" + if isinstance(infile, str): + with _open(infile, newline='') as infile: + yield from read_tsv(infile, header, + _open=_open, + _get_reader=_get_reader, + ) + return + lines = iter(infile) + + # Validate the header. + try: + actualheader = next(lines).strip() + except StopIteration: + actualheader = '' + if actualheader != header: + raise ValueError(f'bad header {actualheader!r}') + + for row in _get_reader(lines, delimiter='\t'): + yield tuple(v.strip() for v in row) + + +def write_tsv(outfile, header, rows, *, + _open=open, + _get_writer=csv.writer, + ): + """Write each of the rows to the given TSV (tab-separated) file.""" + if isinstance(outfile, str): + with _open(outfile, 'w', newline='') as outfile: + return write_tsv(outfile, header, rows, + _open=_open, + _get_writer=_get_writer, + ) + + if isinstance(header, str): + header = header.split('\t') + writer = _get_writer(outfile, delimiter='\t') + writer.writerow(header) + for row in rows: + writer.writerow('' if v is None else str(v) + for v in row) + + +class Slot: + """A descriptor that provides a slot. + + This is useful for types that can't have slots via __slots__, + e.g. tuple subclasses. + """ + + __slots__ = ('initial', 'default', 'readonly', 'instances', 'name') + + def __init__(self, initial=_NOT_SET, *, + default=_NOT_SET, + readonly=False, + ): + self.initial = initial + self.default = default + self.readonly = readonly + + self.instances = {} + self.name = None + + def __set_name__(self, cls, name): + if self.name is not None: + raise TypeError('already used') + self.name = name + + def __get__(self, obj, cls): + if obj is None: # called on the class + return self + try: + value = self.instances[id(obj)] + except KeyError: + if self.initial is _NOT_SET: + value = self.default + else: + value = self.initial + self.instances[id(obj)] = value + if value is _NOT_SET: + raise AttributeError(self.name) + # XXX Optionally make a copy? + return value + + def __set__(self, obj, value): + if self.readonly: + raise AttributeError(f'{self.name} is readonly') + # XXX Optionally coerce? + self.instances[id(obj)] = value + + def __delete__(self, obj): + if self.readonly: + raise AttributeError(f'{self.name} is readonly') + self.instances[id(obj)] = self.default + + def set(self, obj, value): + """Update the cached value for an object. + + This works even if the descriptor is read-only. This is + particularly useful when initializing the object (e.g. in + its __new__ or __init__). + """ + self.instances[id(obj)] = value + + +class classonly: + """A non-data descriptor that makes a value only visible on the class. + + This is like the "classmethod" builtin, but does not show up on + instances of the class. It may be used as a decorator. + """ + + def __init__(self, value): + self.value = value + self.getter = classmethod(value).__get__ + self.name = None + + def __set_name__(self, cls, name): + if self.name is not None: + raise TypeError('already used') + self.name = name + + def __get__(self, obj, cls): + if obj is not None: + raise AttributeError(self.name) + # called on the class + return self.getter(None, cls) + + +class _NTBase: + + __slots__ = () + + @classonly + def from_raw(cls, raw): + if not raw: + return None + elif isinstance(raw, cls): + return raw + elif isinstance(raw, str): + return cls.from_string(raw) + else: + if hasattr(raw, 'items'): + return cls(**raw) + try: + args = tuple(raw) + except TypeError: + pass + else: + return cls(*args) + raise NotImplementedError + + @classonly + def from_string(cls, value): + """Return a new instance based on the given string.""" + raise NotImplementedError + + @classmethod + def _make(cls, iterable): # The default _make() is not subclass-friendly. + return cls.__new__(cls, *iterable) + + # XXX Always validate? + #def __init__(self, *args, **kwargs): + # self.validate() + + # XXX The default __repr__() is not subclass-friendly (where the name changes). + #def __repr__(self): + # _, _, sig = super().__repr__().partition('(') + # return f'{self.__class__.__name__}({sig}' + + # To make sorting work with None: + def __lt__(self, other): + try: + return super().__lt__(other) + except TypeError: + if None in self: + return True + elif None in other: + return False + else: + raise + + def validate(self): + return + + # XXX Always validate? + #def _replace(self, **kwargs): + # obj = super()._replace(**kwargs) + # obj.validate() + # return obj |