addon-sdk/source/python-lib/mozrunner/__init__.py

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 # This Source Code Form is subject to the terms of the Mozilla Public
     2 # License, v. 2.0. If a copy of the MPL was not distributed with this
     3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
     5 import os
     6 import sys
     7 import copy
     8 import tempfile
     9 import signal
    10 import commands
    11 import zipfile
    12 import optparse
    13 import killableprocess
    14 import subprocess
    15 import platform
    16 import shutil
    17 from StringIO import StringIO
    18 from xml.dom import minidom
    20 from distutils import dir_util
    21 from time import sleep
    23 # conditional (version-dependent) imports
    24 try:
    25     import simplejson
    26 except ImportError:
    27     import json as simplejson
    29 import logging
    30 logger = logging.getLogger(__name__)
    32 # Use dir_util for copy/rm operations because shutil is all kinds of broken
    33 copytree = dir_util.copy_tree
    34 rmtree = dir_util.remove_tree
    36 def findInPath(fileName, path=os.environ['PATH']):
    37     dirs = path.split(os.pathsep)
    38     for dir in dirs:
    39         if os.path.isfile(os.path.join(dir, fileName)):
    40             return os.path.join(dir, fileName)
    41         if os.name == 'nt' or sys.platform == 'cygwin':
    42             if os.path.isfile(os.path.join(dir, fileName + ".exe")):
    43                 return os.path.join(dir, fileName + ".exe")
    44     return None
    46 stdout = sys.stdout
    47 stderr = sys.stderr
    48 stdin = sys.stdin
    50 def run_command(cmd, env=None, **kwargs):
    51     """Run the given command in killable process."""
    52     killable_kwargs = {'stdout':stdout ,'stderr':stderr, 'stdin':stdin}
    53     killable_kwargs.update(kwargs)
    55     if sys.platform != "win32":
    56         return killableprocess.Popen(cmd, preexec_fn=lambda : os.setpgid(0, 0),
    57                                      env=env, **killable_kwargs)
    58     else:
    59         return killableprocess.Popen(cmd, env=env, **killable_kwargs)
    61 def getoutput(l):
    62     tmp = tempfile.mktemp()
    63     x = open(tmp, 'w')
    64     subprocess.call(l, stdout=x, stderr=x)
    65     x.close(); x = open(tmp, 'r')
    66     r = x.read() ; x.close()
    67     os.remove(tmp)
    68     return r
    70 def get_pids(name, minimun_pid=0):
    71     """Get all the pids matching name, exclude any pids below minimum_pid."""
    72     if os.name == 'nt' or sys.platform == 'cygwin':
    73         import wpk
    75         pids = wpk.get_pids(name)
    77     else:
    78         data = getoutput(['ps', 'ax']).splitlines()
    79         pids = [int(line.split()[0]) for line in data if line.find(name) is not -1]
    81     matching_pids = [m for m in pids if m > minimun_pid]
    82     return matching_pids
    84 def makedirs(name):
    86     head, tail = os.path.split(name)
    87     if not tail:
    88         head, tail = os.path.split(head)
    89     if head and tail and not os.path.exists(head):
    90         try:
    91             makedirs(head)
    92         except OSError, e:
    93             pass
    94         if tail == os.curdir:           # xxx/newdir/. exists if xxx/newdir exists
    95             return
    96     try:
    97         os.mkdir(name)
    98     except:
    99         pass
   101 # addon_details() copied from mozprofile
   102 def addon_details(install_rdf_fh):
   103     """
   104     returns a dictionary of details about the addon
   105     - addon_path : path to the addon directory
   106     Returns:
   107     {'id':      u'rainbow@colors.org', # id of the addon
   108      'version': u'1.4',                # version of the addon
   109      'name':    u'Rainbow',            # name of the addon
   110      'unpack': # whether to unpack the addon
   111     """
   113     details = {
   114         'id': None,
   115         'unpack': False,
   116         'name': None,
   117         'version': None
   118     }
   120     def get_namespace_id(doc, url):
   121         attributes = doc.documentElement.attributes
   122         namespace = ""
   123         for i in range(attributes.length):
   124             if attributes.item(i).value == url:
   125                 if ":" in attributes.item(i).name:
   126                     # If the namespace is not the default one remove 'xlmns:'
   127                     namespace = attributes.item(i).name.split(':')[1] + ":"
   128                     break
   129         return namespace
   131     def get_text(element):
   132         """Retrieve the text value of a given node"""
   133         rc = []
   134         for node in element.childNodes:
   135             if node.nodeType == node.TEXT_NODE:
   136                 rc.append(node.data)
   137         return ''.join(rc).strip()
   139     doc = minidom.parse(install_rdf_fh)
   141     # Get the namespaces abbreviations
   142     em = get_namespace_id(doc, "http://www.mozilla.org/2004/em-rdf#")
   143     rdf = get_namespace_id(doc, "http://www.w3.org/1999/02/22-rdf-syntax-ns#")
   145     description = doc.getElementsByTagName(rdf + "Description").item(0)
   146     for node in description.childNodes:
   147         # Remove the namespace prefix from the tag for comparison
   148         entry = node.nodeName.replace(em, "")
   149         if entry in details.keys():
   150             details.update({ entry: get_text(node) })
   152     # turn unpack into a true/false value
   153     if isinstance(details['unpack'], basestring):
   154         details['unpack'] = details['unpack'].lower() == 'true'
   156     return details
   158 class Profile(object):
   159     """Handles all operations regarding profile. Created new profiles, installs extensions,
   160     sets preferences and handles cleanup."""
   162     def __init__(self, binary=None, profile=None, addons=None,
   163                  preferences=None):
   165         self.binary = binary
   167         self.create_new = not(bool(profile))
   168         if profile:
   169             self.profile = profile
   170         else:
   171             self.profile = self.create_new_profile(self.binary)
   173         self.addons_installed = []
   174         self.addons = addons or []
   176         ### set preferences from class preferences
   177         preferences = preferences or {}
   178         if hasattr(self.__class__, 'preferences'):
   179             self.preferences = self.__class__.preferences.copy()
   180         else:
   181             self.preferences = {}
   182         self.preferences.update(preferences)
   184         for addon in self.addons:
   185             self.install_addon(addon)
   187         self.set_preferences(self.preferences)
   189     def create_new_profile(self, binary):
   190         """Create a new clean profile in tmp which is a simple empty folder"""
   191         profile = tempfile.mkdtemp(suffix='.mozrunner')
   192         return profile
   194     def unpack_addon(self, xpi_zipfile, addon_path):
   195         for name in xpi_zipfile.namelist():
   196             if name.endswith('/'):
   197                 makedirs(os.path.join(addon_path, name))
   198             else:
   199                 if not os.path.isdir(os.path.dirname(os.path.join(addon_path, name))):
   200                     makedirs(os.path.dirname(os.path.join(addon_path, name)))
   201                 data = xpi_zipfile.read(name)
   202                 f = open(os.path.join(addon_path, name), 'wb')
   203                 f.write(data) ; f.close()
   204                 zi = xpi_zipfile.getinfo(name)
   205                 os.chmod(os.path.join(addon_path,name), (zi.external_attr>>16))
   207     def install_addon(self, path):
   208         """Installs the given addon or directory of addons in the profile."""
   210         extensions_path = os.path.join(self.profile, 'extensions')
   211         if not os.path.exists(extensions_path):
   212             os.makedirs(extensions_path)
   214         addons = [path]
   215         if not path.endswith('.xpi') and not os.path.exists(os.path.join(path, 'install.rdf')):
   216             addons = [os.path.join(path, x) for x in os.listdir(path)]
   218         for addon in addons:
   219             if addon.endswith('.xpi'):
   220                 xpi_zipfile = zipfile.ZipFile(addon, "r")
   221                 details = addon_details(StringIO(xpi_zipfile.read('install.rdf')))
   222                 addon_path = os.path.join(extensions_path, details["id"])
   223                 if details.get("unpack", True):
   224                     self.unpack_addon(xpi_zipfile, addon_path)
   225                     self.addons_installed.append(addon_path)
   226                 else:
   227                     shutil.copy(addon, addon_path + '.xpi')
   228             else:
   229                 # it's already unpacked, but we need to extract the id so we
   230                 # can copy it
   231                 details = addon_details(open(os.path.join(addon, "install.rdf"), "rb"))
   232                 addon_path = os.path.join(extensions_path, details["id"])
   233                 shutil.copytree(addon, addon_path, symlinks=True)
   235     def set_preferences(self, preferences):
   236         """Adds preferences dict to profile preferences"""
   237         prefs_file = os.path.join(self.profile, 'user.js')
   238         # Ensure that the file exists first otherwise create an empty file
   239         if os.path.isfile(prefs_file):
   240             f = open(prefs_file, 'a+')
   241         else:
   242             f = open(prefs_file, 'w')
   244         f.write('\n#MozRunner Prefs Start\n')
   246         pref_lines = ['user_pref(%s, %s);' %
   247                       (simplejson.dumps(k), simplejson.dumps(v) ) for k, v in
   248                        preferences.items()]
   249         for line in pref_lines:
   250             f.write(line+'\n')
   251         f.write('#MozRunner Prefs End\n')
   252         f.flush() ; f.close()
   254     def pop_preferences(self):
   255         """
   256         pop the last set of preferences added
   257         returns True if popped
   258         """
   260         # our magic markers
   261         delimeters = ('#MozRunner Prefs Start', '#MozRunner Prefs End')
   263         lines = file(os.path.join(self.profile, 'user.js')).read().splitlines()
   264         def last_index(_list, value):
   265             """
   266             returns the last index of an item;
   267             this should actually be part of python code but it isn't
   268             """
   269             for index in reversed(range(len(_list))):
   270                 if _list[index] == value:
   271                     return index
   272         s = last_index(lines, delimeters[0])
   273         e = last_index(lines, delimeters[1])
   275         # ensure both markers are found
   276         if s is None:
   277             assert e is None, '%s found without %s' % (delimeters[1], delimeters[0])
   278             return False # no preferences found
   279         elif e is None:
   280             assert e is None, '%s found without %s' % (delimeters[0], delimeters[1])
   282         # ensure the markers are in the proper order
   283         assert e > s, '%s found at %s, while %s found at %s' (delimeter[1], e, delimeter[0], s)
   285         # write the prefs
   286         cleaned_prefs = '\n'.join(lines[:s] + lines[e+1:])
   287         f = file(os.path.join(self.profile, 'user.js'), 'w')
   288         f.write(cleaned_prefs)
   289         f.close()
   290         return True
   292     def clean_preferences(self):
   293         """Removed preferences added by mozrunner."""
   294         while True:
   295             if not self.pop_preferences():
   296                 break
   298     def clean_addons(self):
   299         """Cleans up addons in the profile."""
   300         for addon in self.addons_installed:
   301             if os.path.isdir(addon):
   302                 rmtree(addon)
   304     def cleanup(self):
   305         """Cleanup operations on the profile."""
   306         def oncleanup_error(function, path, excinfo):
   307             #TODO: How should we handle this?
   308             print "Error Cleaning up: " + str(excinfo[1])
   309         if self.create_new:
   310             shutil.rmtree(self.profile, False, oncleanup_error)
   311         else:
   312             self.clean_preferences()
   313             self.clean_addons()
   315 class FirefoxProfile(Profile):
   316     """Specialized Profile subclass for Firefox"""
   317     preferences = {# Don't automatically update the application
   318                    'app.update.enabled' : False,
   319                    # Don't restore the last open set of tabs if the browser has crashed
   320                    'browser.sessionstore.resume_from_crash': False,
   321                    # Don't check for the default web browser
   322                    'browser.shell.checkDefaultBrowser' : False,
   323                    # Don't warn on exit when multiple tabs are open
   324                    'browser.tabs.warnOnClose' : False,
   325                    # Don't warn when exiting the browser
   326                    'browser.warnOnQuit': False,
   327                    # Only install add-ons from the profile and the app folder
   328                    'extensions.enabledScopes' : 5,
   329                    # Don't automatically update add-ons
   330                    'extensions.update.enabled'    : False,
   331                    # Don't open a dialog to show available add-on updates
   332                    'extensions.update.notifyUser' : False,
   333                    }
   335     # The possible names of application bundles on Mac OS X, in order of
   336     # preference from most to least preferred.
   337     # Note: Nightly is obsolete, as it has been renamed to FirefoxNightly,
   338     # but it will still be present if users update an older nightly build
   339     # via the app update service.
   340     bundle_names = ['Firefox', 'FirefoxNightly', 'Nightly']
   342     # The possible names of binaries, in order of preference from most to least
   343     # preferred.
   344     @property
   345     def names(self):
   346         if sys.platform == 'darwin':
   347             return ['firefox', 'nightly', 'shiretoko']
   348         if (sys.platform == 'linux2') or (sys.platform in ('sunos5', 'solaris')):
   349             return ['firefox', 'mozilla-firefox', 'iceweasel']
   350         if os.name == 'nt' or sys.platform == 'cygwin':
   351             return ['firefox']
   353 class ThunderbirdProfile(Profile):
   354     preferences = {'extensions.update.enabled'    : False,
   355                    'extensions.update.notifyUser' : False,
   356                    'browser.shell.checkDefaultBrowser' : False,
   357                    'browser.tabs.warnOnClose' : False,
   358                    'browser.warnOnQuit': False,
   359                    'browser.sessionstore.resume_from_crash': False,
   360                    }
   362     # The possible names of application bundles on Mac OS X, in order of
   363     # preference from most to least preferred.
   364     bundle_names = ["Thunderbird", "Shredder"]
   366     # The possible names of binaries, in order of preference from most to least
   367     # preferred.
   368     names = ["thunderbird", "shredder"]
   371 class Runner(object):
   372     """Handles all running operations. Finds bins, runs and kills the process."""
   374     def __init__(self, binary=None, profile=None, cmdargs=[], env=None,
   375                  kp_kwargs={}):
   376         if binary is None:
   377             self.binary = self.find_binary()
   378         elif sys.platform == 'darwin' and binary.find('Contents/MacOS/') == -1:
   379             self.binary = os.path.join(binary, 'Contents/MacOS/%s-bin' % self.names[0])
   380         else:
   381             self.binary = binary
   383         if not os.path.exists(self.binary):
   384             raise Exception("Binary path does not exist "+self.binary)
   386         if sys.platform == 'linux2' and self.binary.endswith('-bin'):
   387             dirname = os.path.dirname(self.binary)
   388             if os.environ.get('LD_LIBRARY_PATH', None):
   389                 os.environ['LD_LIBRARY_PATH'] = '%s:%s' % (os.environ['LD_LIBRARY_PATH'], dirname)
   390             else:
   391                 os.environ['LD_LIBRARY_PATH'] = dirname
   393         # Disable the crash reporter by default
   394         os.environ['MOZ_CRASHREPORTER_NO_REPORT'] = '1'
   396         self.profile = profile
   398         self.cmdargs = cmdargs
   399         if env is None:
   400             self.env = copy.copy(os.environ)
   401             self.env.update({'MOZ_NO_REMOTE':"1",})
   402         else:
   403             self.env = env
   404         self.kp_kwargs = kp_kwargs or {}
   406     def find_binary(self):
   407         """Finds the binary for self.names if one was not provided."""
   408         binary = None
   409         if sys.platform in ('linux2', 'sunos5', 'solaris') \
   410                 or sys.platform.startswith('freebsd'):
   411             for name in reversed(self.names):
   412                 binary = findInPath(name)
   413         elif os.name == 'nt' or sys.platform == 'cygwin':
   415             # find the default executable from the windows registry
   416             try:
   417                 import _winreg
   418             except ImportError:
   419                 pass
   420             else:
   421                 sam_flags = [0]
   422                 # KEY_WOW64_32KEY etc only appeared in 2.6+, but that's OK as
   423                 # only 2.6+ has functioning 64bit builds.
   424                 if hasattr(_winreg, "KEY_WOW64_32KEY"):
   425                     if "64 bit" in sys.version:
   426                         # a 64bit Python should also look in the 32bit registry
   427                         sam_flags.append(_winreg.KEY_WOW64_32KEY)
   428                     else:
   429                         # possibly a 32bit Python on 64bit Windows, so look in
   430                         # the 64bit registry incase there is a 64bit app.
   431                         sam_flags.append(_winreg.KEY_WOW64_64KEY)
   432                 for sam_flag in sam_flags:
   433                     try:
   434                         # assumes self.app_name is defined, as it should be for
   435                         # implementors
   436                         keyname = r"Software\Mozilla\Mozilla %s" % self.app_name
   437                         sam = _winreg.KEY_READ | sam_flag
   438                         app_key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, keyname, 0, sam)
   439                         version, _type = _winreg.QueryValueEx(app_key, "CurrentVersion")
   440                         version_key = _winreg.OpenKey(app_key, version + r"\Main")
   441                         path, _ = _winreg.QueryValueEx(version_key, "PathToExe")
   442                         return path
   443                     except _winreg.error:
   444                         pass
   446             # search for the binary in the path            
   447             for name in reversed(self.names):
   448                 binary = findInPath(name)
   449                 if sys.platform == 'cygwin':
   450                     program_files = os.environ['PROGRAMFILES']
   451                 else:
   452                     program_files = os.environ['ProgramFiles']
   454                 if binary is None:
   455                     for bin in [(program_files, 'Mozilla Firefox', 'firefox.exe'),
   456                                 (os.environ.get("ProgramFiles(x86)"),'Mozilla Firefox', 'firefox.exe'),
   457                                 (program_files, 'Nightly', 'firefox.exe'),
   458                                 (os.environ.get("ProgramFiles(x86)"),'Nightly', 'firefox.exe'),
   459                                 (program_files, 'Aurora', 'firefox.exe'),
   460                                 (os.environ.get("ProgramFiles(x86)"),'Aurora', 'firefox.exe')
   461                                 ]:
   462                         path = os.path.join(*bin)
   463                         if os.path.isfile(path):
   464                             binary = path
   465                             break
   466         elif sys.platform == 'darwin':
   467             for bundle_name in self.bundle_names:
   468                 # Look for the application bundle in the user's home directory
   469                 # or the system-wide /Applications directory.  If we don't find
   470                 # it in one of those locations, we move on to the next possible
   471                 # bundle name.
   472                 appdir = os.path.join("~/Applications/%s.app" % bundle_name)
   473                 if not os.path.isdir(appdir):
   474                     appdir = "/Applications/%s.app" % bundle_name
   475                 if not os.path.isdir(appdir):
   476                     continue
   478                 # Look for a binary with any of the possible binary names
   479                 # inside the application bundle.
   480                 for binname in self.names:
   481                     binpath = os.path.join(appdir,
   482                                            "Contents/MacOS/%s-bin" % binname)
   483                     if (os.path.isfile(binpath)):
   484                         binary = binpath
   485                         break
   487                 if binary:
   488                     break
   490         if binary is None:
   491             raise Exception('Mozrunner could not locate your binary, you will need to set it.')
   492         return binary
   494     @property
   495     def command(self):
   496         """Returns the command list to run."""
   497         cmd = [self.binary, '-profile', self.profile.profile]
   498         # On i386 OS X machines, i386+x86_64 universal binaries need to be told
   499         # to run as i386 binaries.  If we're not running a i386+x86_64 universal
   500         # binary, then this command modification is harmless.
   501         if sys.platform == 'darwin':
   502             if hasattr(platform, 'architecture') and platform.architecture()[0] == '32bit':
   503                 cmd = ['arch', '-i386'] + cmd
   504         return cmd
   506     def get_repositoryInfo(self):
   507         """Read repository information from application.ini and platform.ini."""
   508         import ConfigParser
   510         config = ConfigParser.RawConfigParser()
   511         dirname = os.path.dirname(self.binary)
   512         repository = { }
   514         for entry in [['application', 'App'], ['platform', 'Build']]:
   515             (file, section) = entry
   516             config.read(os.path.join(dirname, '%s.ini' % file))
   518             for entry in [['SourceRepository', 'repository'], ['SourceStamp', 'changeset']]:
   519                 (key, id) = entry
   521                 try:
   522                     repository['%s_%s' % (file, id)] = config.get(section, key);
   523                 except:
   524                     repository['%s_%s' % (file, id)] = None
   526         return repository
   528     def start(self):
   529         """Run self.command in the proper environment."""
   530         if self.profile is None:
   531             self.profile = self.profile_class()
   532         self.process_handler = run_command(self.command+self.cmdargs, self.env, **self.kp_kwargs)
   534     def wait(self, timeout=None):
   535         """Wait for the browser to exit."""
   536         self.process_handler.wait(timeout=timeout)
   538         if sys.platform != 'win32':
   539             for name in self.names:
   540                 for pid in get_pids(name, self.process_handler.pid):
   541                     self.process_handler.pid = pid
   542                     self.process_handler.wait(timeout=timeout)
   544     def kill(self, kill_signal=signal.SIGTERM):
   545         """Kill the browser"""
   546         if sys.platform != 'win32':
   547             self.process_handler.kill()
   548             for name in self.names:
   549                 for pid in get_pids(name, self.process_handler.pid):
   550                     self.process_handler.pid = pid
   551                     self.process_handler.kill()
   552         else:
   553             try:
   554                 self.process_handler.kill(group=True)
   555                 # On windows, it sometimes behooves one to wait for dust to settle
   556                 # after killing processes.  Let's try that.
   557                 # TODO: Bug 640047 is invesitgating the correct way to handle this case
   558                 self.process_handler.wait(timeout=10)
   559             except Exception, e:
   560                 logger.error('Cannot kill process, '+type(e).__name__+' '+e.message)
   562     def stop(self):
   563         self.kill()
   565 class FirefoxRunner(Runner):
   566     """Specialized Runner subclass for running Firefox."""
   568     app_name = 'Firefox'
   569     profile_class = FirefoxProfile
   571     # The possible names of application bundles on Mac OS X, in order of
   572     # preference from most to least preferred.
   573     # Note: Nightly is obsolete, as it has been renamed to FirefoxNightly,
   574     # but it will still be present if users update an older nightly build
   575     # only via the app update service.
   576     bundle_names = ['Firefox', 'FirefoxNightly', 'Nightly']
   578     @property
   579     def names(self):
   580         if sys.platform == 'darwin':
   581             return ['firefox', 'nightly', 'shiretoko']
   582         if sys.platform in ('linux2', 'sunos5', 'solaris') \
   583                 or sys.platform.startswith('freebsd'):
   584             return ['firefox', 'mozilla-firefox', 'iceweasel']
   585         if os.name == 'nt' or sys.platform == 'cygwin':
   586             return ['firefox']
   588 class ThunderbirdRunner(Runner):
   589     """Specialized Runner subclass for running Thunderbird"""
   591     app_name = 'Thunderbird'
   592     profile_class = ThunderbirdProfile
   594     # The possible names of application bundles on Mac OS X, in order of
   595     # preference from most to least preferred.
   596     bundle_names = ["Thunderbird", "Shredder"]
   598     # The possible names of binaries, in order of preference from most to least
   599     # preferred.
   600     names = ["thunderbird", "shredder"]
   602 class CLI(object):
   603     """Command line interface."""
   605     runner_class = FirefoxRunner
   606     profile_class = FirefoxProfile
   607     module = "mozrunner"
   609     parser_options = {("-b", "--binary",): dict(dest="binary", help="Binary path.",
   610                                                 metavar=None, default=None),
   611                       ('-p', "--profile",): dict(dest="profile", help="Profile path.",
   612                                                  metavar=None, default=None),
   613                       ('-a', "--addons",): dict(dest="addons", 
   614                                                 help="Addons paths to install.",
   615                                                 metavar=None, default=None),
   616                       ("--info",): dict(dest="info", default=False,
   617                                         action="store_true",
   618                                         help="Print module information")
   619                      }
   621     def __init__(self):
   622         """ Setup command line parser and parse arguments """
   623         self.metadata = self.get_metadata_from_egg()
   624         self.parser = optparse.OptionParser(version="%prog " + self.metadata["Version"])
   625         for names, opts in self.parser_options.items():
   626             self.parser.add_option(*names, **opts)
   627         (self.options, self.args) = self.parser.parse_args()
   629         if self.options.info:
   630             self.print_metadata()
   631             sys.exit(0)
   633         # XXX should use action='append' instead of rolling our own
   634         try:
   635             self.addons = self.options.addons.split(',')
   636         except:
   637             self.addons = []
   639     def get_metadata_from_egg(self):
   640         import pkg_resources
   641         ret = {}
   642         dist = pkg_resources.get_distribution(self.module)
   643         if dist.has_metadata("PKG-INFO"):
   644             for line in dist.get_metadata_lines("PKG-INFO"):
   645                 key, value = line.split(':', 1)
   646                 ret[key] = value
   647         if dist.has_metadata("requires.txt"):
   648             ret["Dependencies"] = "\n" + dist.get_metadata("requires.txt")    
   649         return ret
   651     def print_metadata(self, data=("Name", "Version", "Summary", "Home-page", 
   652                                    "Author", "Author-email", "License", "Platform", "Dependencies")):
   653         for key in data:
   654             if key in self.metadata:
   655                 print key + ": " + self.metadata[key]
   657     def create_runner(self):
   658         """ Get the runner object """
   659         runner = self.get_runner(binary=self.options.binary)
   660         profile = self.get_profile(binary=runner.binary,
   661                                    profile=self.options.profile,
   662                                    addons=self.addons)
   663         runner.profile = profile
   664         return runner
   666     def get_runner(self, binary=None, profile=None):
   667         """Returns the runner instance for the given command line binary argument
   668         the profile instance returned from self.get_profile()."""
   669         return self.runner_class(binary, profile)
   671     def get_profile(self, binary=None, profile=None, addons=None, preferences=None):
   672         """Returns the profile instance for the given command line arguments."""
   673         addons = addons or []
   674         preferences = preferences or {}
   675         return self.profile_class(binary, profile, addons, preferences)
   677     def run(self):
   678         runner = self.create_runner()
   679         self.start(runner)
   680         runner.profile.cleanup()
   682     def start(self, runner):
   683         """Starts the runner and waits for Firefox to exitor Keyboard Interrupt.
   684         Shoule be overwritten to provide custom running of the runner instance."""
   685         runner.start()
   686         print 'Started:', ' '.join(runner.command)
   687         try:
   688             runner.wait()
   689         except KeyboardInterrupt:
   690             runner.stop()
   693 def cli():
   694     CLI().run()

mercurial