]> Savannah Git Hosting - gnulib.git/commitdiff
module: hand-written properties; miscellaneous m4/makefile methods
authorDmitry Selyutin <ghostmansd@gmail.com>
Sat, 26 Aug 2017 19:57:37 +0000 (22:57 +0300)
committerDmitry Selyutin <ghostmansd@gmail.com>
Sat, 26 Aug 2017 19:57:37 +0000 (22:57 +0300)
pygnulib/module.py

index 8ad34988855cad9438212ca0418d9a91b349ed4d..86bde5e10fefea2c3144bdf8071b6d28915e228b 100644 (file)
@@ -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):