text: import urllib.request
  import urllib.error
  import pydyn.patcher as patcher
  import re
  import shutil
  import tempfile
  import tarfile
  import sys
  import pickle
  import subprocess
  import json
  
  from pkg_resources import Requirement
  from pkg_resources import parse_version
  from pkg_resources import working_set
  
  REP_URL = 'https://pypi.python.org/simple/'
  META_URL = 'http://www.red-dove.com/pypi/projects/'
  REMWEIGHT = '-100'
  NEWWEIGHT = '+20'
  FAKEWEIGHT = '+9001'  # fake metamodule
  CACHE = dict()
  
  
  class OPBTranslator:
      """An Class to transalte Module-Metadata to OPB and back"""
  
      def __init__(self, name, version='', new_meta=False):
          self.name = name.lower()
          self.version = version
          self.reqdict = dict()
          self.vardict = dict()
          self.new_meta = new_meta
  
      def addDependency(self, name, version=''):
          self.name = name
          self.version = version
  
      def generateMetadata(self):
          """Generates a complete Dictionary of all (recursive)
          Dependencies of name.
  
          Returns a dictionary of the format {(name, version): [Requirement]}
          The translator makes some assumptions, i.e. all dependencys of "name"
          have to be fulfilled strictly.
          """
          if not self.new_meta:
              data = json.loads(open('meta.json', 'r').read())
  
          if self.new_meta:
              versionslist = list(versionsFromOnline(self.name))
          else:
              versionslist = versionsFromMeta(self.name, data)
          newver = newest(versionslist)
          self.version = newver
          if self.new_meta:
              reqs = list(dependenciesForOnline(self.name.lower(), self.version))
          else:
              reqs = list(dependenciesFor(self.name.lower(), self.version, data))
          self.reqdict.update({(self.name.lower(), self.version): reqs})
          todo = reqs
          strict = dict()
          for i in todo:
              strict.update({i.key: i})  # Have to be fullfilled at all times
  
          for req in todo:
              if self.new_meta:
                  vlist = list(versionsFromOnline(req.key))
              else:
                  vlist = versionsFromMeta(req.key, data)
              for version in vlist:
                  if version in req:  # only use if linktup fullfills requirement
                      if self.new_meta:
                          templist = list(dependenciesForOnline(req.key, version))
                      else:
                          templist = list(dependenciesFor(req.key, version, data))
                      for item in templist:
                          if item not in todo:
                              todo.append(item)
  
                      # update only if the requirement meets the strict requirements
                      if req.key in strict:
                          if version in strict[req.key]:
                              self.reqdict.update({(req.key, version): templist})
                      else:
                          self.reqdict.update({(req.key, version): templist})
  
      def generateOPB(self, working_set=working_set, forCheck=False,
                      checkOpts=('', ''), solvertest=False):
          """Generate a opb Representation of a requirement-dict.
  
          The Requirement-dictionary gets parsed and a opb Instance of the
          Installation-Problem gets created. The resulting opb String
          should be parsable by all PBO-Solver, who comply to the
          DIMACS Format.
  
          forCheck has to be True only if yo use this method from
          checkFutureDependency() in the api.
          checkOpts is a tuple that holds name and version of the module
          to which you add dependencies.
          """
  
          retstr = ''
          minstring = 'min: '
          fakenames = set()  # dictionary for fakepackages
          conflicts = set()
          symtable = dict()
          symcounter = 1
          if not forCheck:
              for p in working_set:
                  symtable.update({(p.key, p.version): 'x'+str(symcounter)})
                  symcounter += 1
                  minstring += REMWEIGHT+' '+symtable[(p.key, p.version)]+' '
          for key, reqlist in self.reqdict.items():
              if key not in symtable:
                  symtable.update({key: 'x'+str(symcounter)})
                  symcounter += 1
              minstring += NEWWEIGHT+' '+symtable[key]+' '
              if forCheck:
                  for req in reqlist:
                      fakenames.add(req.key)
          if forCheck:
              for item in fakenames:
                  symtable.update({(item.lower(), '0.0-fake'): 'x'+str(symcounter)})
                  minstring += FAKEWEIGHT+' x'+str(symcounter)+' '
                  symcounter += 1
          minstring += ';\n'
          retstr += minstring  # minimization function
          retstr += '+1 '+symtable[(self.name.lower(), self.version)]+' >= 1;\n'
          # module you want to install
          if forCheck or solvertest:
              if isinstance(checkOpts, tuple):
                  retstr += '+1 '+symtable[(checkOpts[0].lower(), checkOpts[1])]+' >= 1;\n'
              else:
                  for item in checkOpts:
                      retstr += '+1 '+symtable[(item[0].lower(), item[1])]+' >= 1;\n'
          for key, val in symtable.items():
              confstr = ''
              templist = []
              templist.append(val)
              for key2, val2 in symtable.items():
                  if key[0] == key2[0] and key[1] != key2[1]:
                      templist.append(val2)
              fset = frozenset(templist)
              conflicts.add(fset)
          for item in conflicts:
              newset = set(item)
              for var in newset:
                  confstr += '-1 '+var+' '
              confstr += '>= -1;\n'
          retstr += confstr  # conflicts
  
          # update reqdict with working_set
          if not forCheck:
              for p in working_set:
                  self.reqdict.update({(p.key, p.version): p.requires()})
          for key, reqlist in self.reqdict.items():
              for req in reqlist:
                  depstr = '-1 '+symtable[key]+' '
                  switch = False
                  for entry, sym in symtable.items():
                      if entry[0] == req.key and entry[1] in req:
                          depstr += '+1 '+sym+' '
                          switch = True
                  if forCheck:
                      depstr += '+1 '+symtable[(req.key, '0.0-fake')]+' '
                  depstr += '>= 0;\n'
                  if switch:
                      retstr += depstr  # dependencies
          for key, val in symtable.items():
              self.vardict.update({val: key})
          connumber = len(retstr.splitlines())-1
          retstr = "* #variable= "+str(len(symtable))+" #constraint= "+str(connumber)+"\n"+retstr
          return retstr
  
      def parseSolverOutput(self, output):
          """Parse the output of an opb-Solver.
  
          The output of the solver has to be in DIMACS format.
          The output of this method are two lists which indicate
          which modules are to be installed and which are not.
          """
  
          exp = re.search(r'^v .*$', output, flags=re.MULTILINE)
          solvablestr = re.search(r'^s.*', output, flags=re.MULTILINE)
          solvable = False
          if solvablestr:
              temp = solvablestr.group(0).split()
              if (temp[1] == 'OPTIMUM' and temp[2] == 'FOUND') or temp[1] == 'SATISFIABLE':
                  solvable = True
              elif temp[1] == 'UNSATISFIABLE':
                  solvable = False
          if exp:
              variables = exp.group(0).split()
          else:
              variables = ''
          install = [self.vardict[var] for var in variables if var.startswith('x')]
          uninstall = [self.vardict[var[1:]] for var in variables if var.startswith('-')]
          return install, uninstall, solvable
  
      def getFutureState(self, inst):
          """Give back a graph for drawing methods."""
  
          graph = dict()
          for i in inst:
              reqs = []
              for x in self.reqdict[i]:
                  for j in inst:
                      if x.key == j[0]:
                          reqs.append(j)
                          break
              graph.update({i: reqs})
          return graph
  
  if sys.version_info[0] == 3 and sys.version_info[1] == 2 and sys.version_info[2] < 3:
      urllib.request = patcher  # module bugged in 3.2.0 to 3.2.2
  
  
  def versionsFromMeta(name, data):
      if name in data:
          for version in data[name].keys():
              yield version
  
  
  def dependenciesFor(name, version, data):
      if name in data and version in data[name]:
          for item in data[name][version]:
              yield Requirement.parse(item)
  
  def dependenciesForOnline(name, version):
      """Experimental dependency Extraction from online source"""
      try:
          handle = urllib.request.urlopen(META_URL+name[0]+'/'+name+'/package-'+version+'.json')
          data = json.loads(handle.read().decode('utf-8'))
      except urllib.error.HTTPError:
          data = dict()
      if 'requirements' in data:
          if 'install' in data['requirements']:
              for item in data['requirements']['install']:
                  yield Requirement.parse(item.replace("(", "").replace(")", ""))
  
  def versionsFromOnline(name):
      try:
          handle = urllib.request.urlopen(META_URL+name[0]+'/'+name+'/project.json')
          data = json.loads(handle.read().decode('utf-8'))
      except urllib.error.HTTPError:
          data = dict()
      tempset = set()
      for package in data['files']:
          tempset.add(package['version'])
      return tempset
  
  
  
  def parseURL(name):
      """Provides a linklist for a module.
  
      name has to be the name of a module hosted on PyPi.
      This function parses all links on PyPi for this module and returns
      a list of tuples (link to module as .tar.gz, version).
      """
  
      resp = urllib.request.urlopen(REP_URL+name+'/')
      data = resp.read()
      text = data.decode('utf-8')
      rx = r'<a href=\"([\.\.|https?].*?packages.*/.*%s-(.*)\.tar\.gz.*?)\"' % name
      linkiter = re.findall(rx, text, re.IGNORECASE)
      for link, version in linkiter:
          if link.startswith('http'):
              yield (link, version)
          else:
              yield (REP_URL+name+'/'+link, version)
  
  
  def downloadPackage(link):
      """Downloads a module.
  
      Module gets saved as .tar.gz in /tmp and its path gets returned.
      The file has to be unlinked manually. (use os.unlink())
      """
  
      print('---- Beginning Download of '+link)
      temp = tempfile.NamedTemporaryFile(delete=False)
      try:
          with urllib.request.urlopen(link) as response, open(temp.name, 'wb') as out_file:
              shutil.copyfileobj(response, out_file)
      except urllib.error.HTTPError:
          print("Downloadlink probably dead")
          temp.name = None
      print('#### finished download')
      return temp.name
  
  # =========== Deprecated ===============
  # def getDependencies(paths, name, version):
  #     """Parses Dependencies from setup.py of tarred module.
  
  #     paths is the path to the module as .tar.gz. name and version
  #     are the name and version of the module, that has to be parsed.
  #     Parses only if the install_requires String lists all the dependencies.
  #     Returns a List of [Requirement] objects.
  #     """
  
  #     try:
  #         tarball = tarfile.open(paths, mode='r')
  #     except tarfile.ReadError:
  #         print('Error: Not a tarfile, ignore it')
  #     else:
  #         try:
  #             setupfile = tarball.getmember(tarball.next().name+'/setup.py')
  #         except KeyError:
  #             print('Error: No setup.py in tarball')
  #         else:
  #             f = tarball.extractfile(setupfile)
  #             content = f.read().decode('utf-8')
  #             rawstring = re.findall(r'install_requires=\[(.*?)\]', content, flags=re.DOTALL)
  
  #             if rawstring:
  #                 deplist = re.findall(r'"(.*?)"', rawstring[0])
  #                 deplist.extend(re.findall(r"'(.*?)'", rawstring[0]))
  #             else:
  #                 deplist = []
  #             reqlist = []
  #             for req in deplist:
  #                 reqlist.append(Requirement.parse(req))
  #             f.close()
  #         return reqlist
  
  
  def newest(linklist):
      """Returns the newest module in a list of versions."""
  
      newest_version = ''
      for version in linklist:
          if parse_version(version) > parse_version(newest_version):
              newest_version = version
      return newest_version
  
  
  def installRecommendation(install, uninstall, working_set=working_set, tuples=False):
      """Human Readable advice on which modules have to be installed on
      current Working Set.
      """
      installList = []
      for i in install:
          is_in = False
          for p in working_set:
              if i[0] == p.key and i[1] == p.version:
                  is_in = True
                  break
          if not is_in:
              if not tuples:
                  print('~~ Install: '+i[0]+' version '+i[1])
              else:
                  installList.append((i[0], i[1]))
      for u in uninstall:
          is_in = False
          for p in working_set:
              if u[0] == p.key and u[1] == p.version:
                  is_in = True
                  break
          if is_in:
              if not tuples:
                  print('~~ Uninstall: '+u[0]+' version '+u[1])
      return installList
  
  
  def loadCache(path='pydyn.cache'):
      try:
          global CACHE
          CACHE = pickle.load(open(path, 'rb'))
      except IOError:
          print('Warning: Could not load Cache')
  
  
  def saveCache(path='pydyn.cache'):
      try:
          pickle.dump(CACHE, open(path, 'wb'))
          print('Dumping cache')
      except IOError:
          print('Warning: Could not save Cache')
  
  
  def parseCheckOutput(install):
      """Parses the preparsed solver output with respect to fakevariables"""
  
      retstr = '### Keep the following dependencies:\n'
      inconStr = ''
      consistent = True
      for item in install:
          if item[1] != '0.0-fake':
              retstr += item[0]+' version '+item[1]+'\n'
          else:
              consistent = False
              inconStr += item[0]+'\n'
      if not consistent:
          retstr += '### WARNING: The dependencies can only be kept consistent without the following packages:\n'
          retstr += inconStr
      return retstr
  
  
  def callSolver(inputfile, solver='./wbo/wbo', options=[]):
      """Call a solver with an opb instance.
  
      Returns output in DIMACS format.
      """
      optionstr = ' '
      for i in options:
          optionstr += i+' '
      return subprocess.getoutput(solver+' '+inputfile+optionstr)
  