From: Dmitry Selyutin Date: Sat, 26 Aug 2017 19:57:37 +0000 (+0300) Subject: module: hand-written properties; miscellaneous m4/makefile methods X-Git-Url: https://gitweb.git.savannah.gnu.org/gitweb/?a=commitdiff_plain;h=7423e3e4f759db888936286a4cc94bf6a9cd2784;p=gnulib.git module: hand-written properties; miscellaneous m4/makefile methods --- diff --git a/pygnulib/module.py b/pygnulib/module.py index 8ad3498885..86bde5e10f 100644 --- a/pygnulib/module.py +++ b/pygnulib/module.py @@ -2,7 +2,10 @@ # encoding: UTF-8 + import codecs +import collections +import hashlib import os import re @@ -12,20 +15,20 @@ class Module: """gnulib generic module""" _PATTERN_ = re.compile("") _TABLE_ = { - "description" : (0x00, str, None, "Description"), - "comment" : (0x01, str, None, "Comment"), - "status" : (0x02, str, None, "Status"), - "notice" : (0x03, str, None, "Notice"), - "applicability" : (0x04, str, None, "Applicability"), - "files" : (0x05, list, None, "Files"), - "dependencies" : (0x06, list, None, "Depends-on"), - "preconfigure" : (0x07, str, None, "configure.ac-early"), - "configure" : (0x08, str, None, "configure.ac"), - "makefile" : (0x09, str, None, "Makefile.am"), - "include" : (0x0A, list, None, "Include"), - "link" : (0x0B, list, None, "Link"), - "license" : (0x0C, str, None, "License"), - "maintainers" : (0x0D, list, None, "Maintainer"), + "description" : (0x00, str, None, "Description"), + "comment" : (0x01, str, None, "Comment"), + "status" : (0x02, str, None, "Status"), + "notice" : (0x03, str, None, "Notice"), + "applicability" : (0x04, str, None, "Applicability"), + "files" : (0x05, list, None, "Files"), + "dependencies" : (0x06, list, None, "Depends-on"), + "configure_early" : (0x07, str, None, "configure.ac-early"), + "configure" : (0x08, str, None, "configure.ac"), + "makefile" : (0x09, str, None, "Makefile.am"), + "include" : (0x0A, list, None, "Include"), + "link" : (0x0B, list, None, "Link"), + "license" : (0x0C, str, None, "License"), + "maintainers" : (0x0D, list, None, "Maintainer"), } for _key_, (_uid_, _typeid_, _, _field_) in _TABLE_.items(): _suffix_ = ("(?:" + ":|".join([_[3] for _ in _TABLE_.values()]) + ":)") @@ -36,26 +39,277 @@ class Module: def __init__(self, name, **kwargs): if not isinstance(name, str): raise TypeError("name must be of 'str' type") - self.__dict__["_name_"] = name - self.__dict__["_table_"] = dict() + self._name_ = name + self._table_ = {"maintainers": ["all"]} for key in Module._TABLE_: - - self.__dict__["_table_"][key] = "" - self.__dict__["_table_"]["maintainers"] = ["all"] + self._table_[key] = "" for key, value in kwargs.items(): - self[key] = value + self._table_[key] = value @property def name(self): - return self.__dict__["_name_"] + """name""" + return self._name_ + + @name.setter + def name(self, value): + if not isinstance(value, str): + raise TypeError("'str' type is expected") + self._name_ = value + + + @property + def description(self): + """description""" + return self._table_["description"] + + @description.setter + def description(self, value): + if not isinstance(value, str): + raise TypeError("'str' type is expected") + self._table_["description"] = value + + + @property + def comment(self): + """comment""" + return self._table_["comment"] + + @comment.setter + def comment(self, value): + if not isinstance(value, str): + raise TypeError("'str' type is expected") + self._table_["comment"] = value + + + @property + def status(self): + """status""" + return self._table_["status"] + + @status.setter + def status(self, value): + if not isinstance(value, str): + raise TypeError("'str' type is expected") + self._table_["status"] = value + + + @property + def notice(self): + """notice""" + return self._table_["notice"] + + @notice.setter + def notice(self, value): + if not isinstance(value, str): + raise TypeError("'str' type is expected") + self._table_["notice"] = value + + + @property + def applicability(self): + """applicability (usually "main" or "tests")""" + default = "main" if self.name.endswith("-tests") else "tests" + current = self._table_["applicability"] + return current if current.strip() else default + + @applicability.setter + def applicability(self, value): + if not isinstance(value, str): + raise TypeError("'str' type is expected") + self._table_["applicability"] = value + + + @property + def files(self): + """file dependencies iterator (set of strings)""" + for file in self._table_["files"]: + yield file + + @files.setter + def files(self, iterable): + if isinstance(iterable, (bytes, str)) \ + or not isinstance(iterable, collections.Iterable): + raise TypeError("iterable of strings is expected") + result = set() + for item in iterable: + if not isinstance(item, str): + raise TypeError("iterable of strings is expected") + result.update([item]) + self._table_["files"] = result @property def dependencies(self): + """dependencies iterator (name, condition)""" pattern = re.compile("^([A-Za-z0-9_\\-\\+/]+)(?:\\s+(.+))*$", re.S) - for dep in self.__dict__["_table_"]["dependencies"]: - yield pattern.findall(dep)[0] + for entry in self._table_["dependencies"]: + yield pattern.findall(entry)[0] + + @dependencies.setter + def dependencies(self, iterable): + error = TypeError("iterable of pairs (name, condition) is expected") + if isinstance(iterable, (bytes, str)) \ + or not isinstance(iterable, collections.Iterable): + raise error + result = set() + try: + for pair in iterable: + (name, condition) = pair + if not isinstance(name, str) \ + or not isinstance(condition, str): + raise error + result.update([(name, condition)]) + except ValueError: + raise error + self._table_["dependencies"] = result + + + @property + def configure_early(self): + """early configure.ac snippet""" + return self._table_["configure_early"] + + @configure_early.setter + def configure_early(self, value): + if not isinstance(value, str): + raise TypeError("'str' type is expected") + self._table_["configure_early"] = value + + + @property + def configure(self): + """configure.ac snippet""" + return self._table_["configure"] + + @configure.setter + def configure(self, value): + if not isinstance(value, str): + raise TypeError("'str' type is expected") + self._table_["configure"] = value + + + @property + def makefile(self): + """makefile snippet""" + return self._table_["makefile"] + + @makefile.setter + def makefile(self, value): + if not isinstance(value, str): + raise TypeError("'str' type is expected") + self._table_["makefile"] = value + + + @property + def include(self): + """include files iterator (header, comment)""" + pattern = re.compile("^#include\\s\\<([A-Za-z0-9/\\-_]\\.h)\\>(?:\\s+.*^)*$") + for entry in self._table_["include"]: + yield pattern.findall(entry)[0] + + @include.setter + def include(self, iterable): + error = TypeError("iterable of pairs (header, comment) is expected") + if isinstance(iterable, (bytes, str)) \ + or not isinstance(iterable, collections.Iterable): + raise error + result = set() + try: + for pair in iterable: + (header, comment) = pair + if not isinstance(header, str) \ + or not isinstance(comment, str): + raise error + result.update([(header, comment)]) + except ValueError: + raise error + self._table_["include"] = result + + + @property + def link(self): + """linker directives iterator (directive, comment)""" + pattern = re.compile("^\\<([A-Za-z0-9/\\-_])\\>(?:\\s+.*^)*$") + for entry in self._table_["link"]: + yield pattern.findall(entry)[0] + + @link.setter + def link(self, iterable): + error = TypeError("iterable of pairs (directive, comment) is expected") + if isinstance(iterable, (bytes, str)) \ + or not isinstance(iterable, collections.Iterable): + raise error + result = set() + try: + for pair in iterable: + (directive, comment) = pair + if not isinstance(directive, str) \ + or not isinstance(comment, str): + raise error + result.update([(directive, comment)]) + except ValueError: + raise error + self._table_["link"] = result + + + @property + def license(self): + """license""" + return self._table_["license"] + + @license.setter + def license(self, value): + if not isinstance(value, str): + raise TypeError("'str' type is expected") + self._table_["license"] = value + + + @property + def maintainers(self): + """maintainers iterator (maintainer)""" + for entry in self._table_["maintainers"]: + yield entry + + @maintainers.setter + def maintainers(self, iterable): + if isinstance(iterable, (bytes, str)) \ + or not isinstance(iterable, collections.Iterable): + raise TypeError("iterable of strings is expected") + result = set() + for item in iterable: + if not isinstance(item, str): + raise TypeError("iterable of strings is expected") + result.update([item]) + self._table_["maintainers"] = result + + + def shell_variable(self, macro_prefix="gl"): + """Get the name of the shell variable set to true once m4 macros have been executed.""" + module = self.name + if len(module) != len(module.encode()): + module = (module + "\n").encode("UTF-8") + module = hashlib.md5(module).hexdigest() + return "%s_gnulib_enabled_%s" % (macro_prefix, module) + + + def shell_function(self, macro_prefix="gl"): + """Get the name of the shell function containing the m4 macros.""" + module = self.name + if len(module) != len(module.encode()): + module = (module + "\n").encode("UTF-8") + module = hashlib.md5(module).hexdigest() + return "func_%s_gnulib_m4code_%s" % (macro_prefix, module) + + + def conditional_name(self, macro_prefix="gl"): + """Get the automake conditional name.""" + module = self.name + if len(module) != len(module.encode()): + module = (module + "\n").encode("UTF-8") + module = hashlib.md5(module).hexdigest() + return "%s_GNULIB_ENABLED_%s" % (macro_prefix, module) def __hash__(self): @@ -63,7 +317,7 @@ class Module: def __repr__(self): - return self.__dict__["_name_"] + return self._name_ def __str__(self): @@ -71,9 +325,9 @@ class Module: for key, (_, typeid, _, field) in sorted(Module._TABLE_.items(), key=lambda k: k[1][0]): field += ":\n" if typeid is list: - value = "\n".join(self[key]) + value = "\n".join(self._table_[key]) else: - value = self[key] + value = self._table_[key] if value: result += field result += value @@ -81,57 +335,23 @@ class Module: return result.strip() + "\n" - def __setattr__(self, key, value): - self[key] = value - self.__dict__[key] = value - - - def __getattr__(self, key): - return self[key] - - - def __getitem__(self, key): - if key not in Module._TABLE_: - raise ValueError("unsupported key: %r" % key) - return self.__dict__["_table_"][key] - - - def __setitem__(self, key, value): - if key not in Module._TABLE_: - raise ValueError("unsupported key: %r" % key) - typeid = Module._TABLE_[key][1] - if not isinstance(value, typeid): - typename = typeid.__name__ - raise TypeError("%r key must be of %r type" % (key, typename)) - self.__dict__["_table_"][key] = value - - def __lt__(self, value): - if isinstance(value, Module): - return (self != value) and (self.__dict__["_name_"] < value.name) - return TypeError("cannot compare pygnulib.Module with %r type" % type(value)) + return self.name < value.name def __le__(self, value): return self.__lt__(value) or self.__eq__(value) def __eq__(self, value): - if isinstance(value, Module): - return (self.__dict__["_name_"] == value.__dict__["_name_"]) \ - and (self.__dict__["_table_"] == value.__dict__["_table_"]) - return TypeError("cannot compare pygnulib.Module with %r type" % type(value)) + return self.name == value.name def __ne__(self, value): return not self.__eq__(value) def __ge__(self, value): - if isinstance(value, Module): - return value.__le__(self) - return TypeError("cannot compare pygnulib.Module with %r type" % type(value)) + return value.__le__(self) def __gt__(self, value): - if isinstance(value, Module): - return value.__lt__(self) - return TypeError("cannot compare pygnulib.Module with %r type" % type(value)) + return value.__lt__(self) @@ -144,34 +364,38 @@ class FileModule(Module): if mode not in ("r", "w", "rw"): raise ValueError("illegal mode: %r" % mode) if mode == "r": - super(FileModule, self).__init__(name) + super().__init__(name) with codecs.open(path, "rb", "UTF-8") as stream: - data = stream.read() + data = "" + for line in stream: + if line.startswith("#"): + continue + data += line for key, (_, typeid, pattern, _) in Module._TABLE_.items(): pattern = re.compile(pattern) match = pattern.findall(data) - self[key] = [_ for _ in "".join(match).split("\n") if _.strip()] \ - if typeid is list else \ - ("\n".join([_.strip() for _ in match]) if match else "") - self["dependencies"] = [_ for _ in self["dependencies"] if not _.startswith("#")] - self.__dict__["_stream_"] = None + self._table_[key] = [_ for _ in "".join(match).split("\n") if _.strip()] \ + if typeid is list else \ + ("\n".join([_.strip() for _ in match]) if match else "") + self._stream_ = None elif mode == "w": - super(FileModule, self).__init__(name) - self.__dict__["_stream_"] = codecs.open(path, "w+", "UTF-8") + super().__init__(name) + self._stream_ = codecs.open(path, "w+", "UTF-8") elif mode == "rw": self.__init__(path, "r") - self.__dict__["_stream_"] = codecs.open(path, "w+", "UTF-8") + self._stream_ = codecs.open(path, "w+", "UTF-8") else: raise ValueError("invalid mode: %r" % mode) for key, value in kwargs.items(): - self[key] = value + self._table_[key] = value def close(self): - if self.__dict__["_stream_"]: - self.__dict__["_stream_"].truncate(0) - self.__dict__["_stream_"].write(str(self)) - self.__dict__["_stream_"].close() + """Close the underlying stream and write data into the file.""" + if self._stream_: + self._stream_.truncate(0) + self._stream_.write(str(self)) + self._stream_.close() def __enter__(self):