diff options
-rw-r--r-- | epydoc.conf | 1 | ||||
-rw-r--r-- | portato/eix/__init__.py | 37 | ||||
-rw-r--r-- | portato/eix/exceptions.py | 16 | ||||
-rw-r--r-- | portato/eix/parser.py | 252 |
4 files changed, 300 insertions, 6 deletions
diff --git a/epydoc.conf b/epydoc.conf index 62b6c73..0e81aee 100644 --- a/epydoc.conf +++ b/epydoc.conf @@ -27,7 +27,6 @@ sourcecode: yes include-log: no frames: yes css: white -redundant-details: yes # # Project diff --git a/portato/eix/__init__.py b/portato/eix/__init__.py index 977c9e2..aed5855 100644 --- a/portato/eix/__init__.py +++ b/portato/eix/__init__.py @@ -10,7 +10,12 @@ # # Written by René 'Necoro' Neumann <necoro@necoro.net> +""" +A module to parse the eix-cache files. +""" + from __future__ import absolute_import, with_statement +__docformat__ = "restructuredtext" from . import parser from .exceptions import UnsupportedVersionError @@ -18,10 +23,37 @@ from .exceptions import UnsupportedVersionError from ..helper import debug class EixReader(object): + """ + The main class to use to have access to the eix-cache. + + Note that the file used internally stays open during the whole operation. + So please call `close()` when you are finished. + + :CVariables: + + supported_versions : int[] + The list of versions of the eix-cache, which are supported by this reader. + + :IVariables: + + file : file + The eix cache file. + + header : `parser.header` + The header of the eix cache. + + categories : `parser.category` [] + The list of categories. + """ + supported_versions = (28,) def __init__ (self, filename): - self.filename = filename + """ + :param filename: Path to the cache file + :type filename: string + """ + self.file = open(filename, "r") try: @@ -41,5 +73,8 @@ class EixReader(object): raise def close (self): + """ + Closes the cache file. + """ self.file.close() debug("EixReader closed.") diff --git a/portato/eix/exceptions.py b/portato/eix/exceptions.py index fd72dcf..8145af4 100644 --- a/portato/eix/exceptions.py +++ b/portato/eix/exceptions.py @@ -10,20 +10,36 @@ # # Written by René 'Necoro' Neumann <necoro@necoro.net> +""" +Different exceptions used in the eix module. +""" + from __future__ import absolute_import, with_statement +__docformat__ = "restructuredtext" class EixError (Exception): + """ + The base class for all exceptions of this module. + + :ivar message: The error message + """ message = _("Unknown error.") def __str__ (self): return self.message class EndOfFileException (EixError): + """ + Denotes the unexpected EOF. + """ def __init__ (self, filename): self.message = _("End of file reached though it was not expected: '%s'") % filename class UnsupportedVersionError (EixError): + """ + The version of the cache file found is not supported. + """ def __init__ (self, version): self.message = _("Version '%s' is not supported.") % version diff --git a/portato/eix/parser.py b/portato/eix/parser.py index 6e12973..2a6658b 100644 --- a/portato/eix/parser.py +++ b/portato/eix/parser.py @@ -10,7 +10,15 @@ # # Written by René 'Necoro' Neumann <necoro@necoro.net> +""" +The cache file supports different types of data. +In this module (nearly) all of these types have a corresponding function. + +For the exact way all the functions work, have a look at the eix format description. +""" + from __future__ import absolute_import, with_statement +__docformat__ = "restructuredtext" import os import struct @@ -19,7 +27,29 @@ from functools import partial from ..helper import debug from .exceptions import EndOfFileException +# +# Helper +# + def _get_bytes (file, length, expect_list = False): + """ + Return a number of bytes. + + :Parameters: + + file : file + The file to read from. + + length : int + The number of bytes to read. + + expect_list : bool + In case ``length`` is 1, only a single byte is returned. If ``expect_list`` is true, then a list is also returned in this case. + + :rtype: int or int[] + :raises EndOfFileException: if EOF is reached during execution + """ + s = file.read(length) if len(s) != length: @@ -30,7 +60,25 @@ def _get_bytes (file, length, expect_list = False): else: return struct.unpack("%sB" % length, s) +# +# Base Types +# + def number (file, skip = False): + """ + Returns a number. + + :Parameters: + + file : file + The file to read from. + + skip : bool + Do not return the actual value, but just skip to the next datum. + + :rtype: int + """ + n = _get_bytes(file, 1) if n < 0xFF: @@ -63,6 +111,27 @@ def number (file, skip = False): return value def vector (file, get_type, skip = False, nelems = None): + """ + Returns a vector of elements. + + :Parameters: + + file : file + The file to read from. + + get_type : function(file, bool) + The function determining type of the elements. + + skip : bool + Do not return the actual value, but just skip to the next datum. + + nelems : int + Normally the eix-Vector has the number of elements as the first argument. + If for some reason this is not the case, you can pass it in here. + + :rtype: list + """ + if nelems is None: nelems = number(file) @@ -73,12 +142,42 @@ def vector (file, get_type, skip = False, nelems = None): return [get_type(file) for i in range(nelems)] def typed_vector(type, nelems = None): + """ + Shortcut to create a function for a special type of vector. + + :Parameters: + + type : function(file, bool) + The function determining type of the elements. + + nelems : int + Normally the eix-Vector has the number of elements as the first argument. + If for some reason this is not the case, you can pass it in here. + Do not return the actual value, but just skip to the next datum. + + :rtype: function(file, bool) + :see: `vector` + """ + if nelems is None: return partial(vector, get_type = type) else: return partial(vector, get_type = type, nelems = nelems) def string (file, skip = False): + """ + Returns a string. + + :Parameters: + + file : file + The file to read from. + + skip : bool + Do not return the actual value, but just skip to the next datum. + + :rtype: str + """ nelems = number(file) if skip: @@ -92,10 +191,29 @@ def string (file, skip = False): return s +# +# Complex Types +# + class LazyElement (object): + """ + This class models a value in the cache, which is only read on access. + + If not accessed directly, only the position inside the file is stored. + """ __slots__ = ("file", "get_type", "_value", "pos") def __init__ (self, get_type, file): + """ + :Parameters: + + get_type : function(file, bool) + The function determining type of the elements. + + file : file + The file to read from. + """ + self.file = file self.get_type = get_type self._value = None @@ -105,6 +223,10 @@ class LazyElement (object): @property def value (self): + """ + The value of the element. + """ + if self._value is None: old_pos = self.file.tell() self.file.seek(self.pos, os.SEEK_SET) @@ -114,20 +236,86 @@ class LazyElement (object): return self._value def __call__ (self): + """ + Convenience function. Also returns the value. + """ return self.value class overlay (object): + """ + Represents an overlay object. + + :IVariables: + + path : `LazyElement` <string> + The path to the overlay + + label : `LazyElement` <string> + The label/name of the overlay + """ __slots__ = ("path", "label") def __init__ (self, file, skip = False): + """ + :Parameters: + + file : file + The file to read from. + + skip : bool + Do not return the actual value, but just skip to the next datum. + """ + self.path = LazyElement(string, file) self.label = LazyElement(string, file) class header (object): + """ + Represents the header of the cache. + + :IVariables: + + version : `LazyElement` <int> + The version of the cache file. + + ncats : `LazyElement` <int> + The number of categories. + + overlays : `LazyElement` <`overlay` []> + The list of overlays. + + provide : `LazyElement` <string[]> + A list of "PROVIDE" values. + + licenses : `LazyElement` <string[]> + The list of licenses. + + keywords : `LazyElement` <string[]> + The list of keywords. + + useflags : `LazyElement` <string[]> + The list of useflags. + + slots : `LazyElement` <string[]> + The list of slots different from "0". + + sets : `LazyElement` <string[]> + The names of world sets are the names (without leading @) of the world sets stored in /var/lib/portage/world_sets. + If SAVE_WORLD=false, the list is empty. + """ __slots__ = ("version", "ncats", "overlays", "provide", "licenses", "keywords", "useflags", "slots", "sets") def __init__ (self, file, skip = False): + """ + :Parameters: + + file : file + The file to read from. + + skip : bool + Do not return the actual value, but just skip to the next datum. + """ def LE (t): return LazyElement(t, file) @@ -142,14 +330,50 @@ class header (object): self.sets = LE(typed_vector(string)) class package (object): - __slots__ = ("offset","name", "description", - "provide", "homepage", "license", "useflags") + """ + The representation of one package. + + Currently, version information is not parsed and stored. + So you can gain general infos only. + + :IVariables: + + name : `LazyElement` <string> + The name of the package. + + description : `LazyElement` <string> + Description of the package. + + homepage : `LazyElement` <string> + The homepage of the package. + + provide : `LazyElement` <int[]> + The indices of `header.provide` representing the PROVIDE value of the package. + + license : `LazyElement` <int> + The index of `header.licenses` representing the license of the package. + + useflags : `LazyElement` <int[]> + The indices of `header.useflags` representing the IUSE value of the package. + """ + + __slots__ = ("name", "description", "provide", + "homepage", "license", "useflags") def __init__ (self, file, skip = False): + """ + :Parameters: + + file : file + The file to read from. + + skip : bool + Do not return the actual value, but just skip to the next datum. + """ def LE (t): return LazyElement(t, file) - self.offset = number(file) + self._offset = number(file) after_offset = file.tell() @@ -162,11 +386,31 @@ class package (object): # self.versions = LE(typed_vector(version)) # for the moment just skip the versions - file.seek(self.offset - (file.tell() - after_offset), os.SEEK_CUR) + file.seek(self._offset - (file.tell() - after_offset), os.SEEK_CUR) class category (object): + """ + Represents a whole category. + + :IVariables: + + name : `LazyElement` <string> + The category name. + + packages : `LazyElement` <`package` []> + All the packages of the category. + """ __slots__ = ("name", "packages") def __init__ (self, file, skip = False): + """ + :Parameters: + + file : file + The file to read from. + + skip : bool + Do not return the actual value, but just skip to the next datum. + """ self.name = LazyElement(string, file) self.packages = LazyElement(typed_vector(package), file) |