summaryrefslogtreecommitdiff
path: root/portato/eix
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--portato/eix/__init__.py37
-rw-r--r--portato/eix/exceptions.py16
-rw-r--r--portato/eix/parser.py252
3 files changed, 300 insertions, 5 deletions
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)