home *** CD-ROM | disk | FTP | other *** search
/ PC Welt 2006 November (DVD) / PCWELT_11_2006.ISO / casper / filesystem.squashfs / usr / bin / pycentral < prev    next >
Encoding:
Text File  |  2006-08-15  |  49.4 KB  |  1,326 lines

  1. #! /usr/bin/python
  2.  
  3. import fnmatch, glob, os, re, sys, time
  4. from optparse import OptionParser
  5.  
  6. sys.path[0:0] = ['/usr/share/python', '/usr/share/pycentral-data']
  7. import pyversions
  8.  
  9. try:
  10.     SetType = set
  11. except NameError:
  12.     import sets
  13.     SetType = sets.Set
  14.     set = sets.Set
  15.  
  16. program = os.path.basename(sys.argv[0])
  17.  
  18. shared_base = '/usr/share/pycentral/'
  19. req_pycentral_version = '0.5'
  20.  
  21. def samefs(path1, path2):
  22.     if not (os.path.exists(path1) and os.path.exists(path2)):
  23.         return False
  24.     while path1 != os.path.dirname(path1):
  25.         if os.path.ismount(path1):
  26.             break
  27.         path1 = os.path.dirname(path1)
  28.     while path2 != os.path.dirname(path2):
  29.         if os.path.ismount(path2):
  30.             break
  31.         path2 = os.path.dirname(path2)
  32.     return path1 == path2
  33.  
  34. def version2depends(vinfo):
  35.     if isinstance(vinfo, set):
  36.         vinfo = list(vinfo)
  37.     if isinstance(vinfo, list):
  38.         vinfo = vinfo[:]
  39.         vinfo.sort()
  40.         nv = [int(s) for s in vinfo[-1].split('.')]
  41.         deps = 'python (>= %s), python (<< %d.%d)' % (vinfo[0], nv[0], nv[1]+1)
  42.     elif vinfo in ('all', 'current'):
  43.         supported = [d[6:] for d in pyversions.supported_versions()
  44.                      if re.match(r'python\d\.\d', d)]
  45.         supported.sort()
  46.         deps = 'python (>= %s)' % supported[0]
  47.     elif vinfo == 'current_ext':
  48.         cv = pyversions.default_version(version_only=True)
  49.         nv = [int(s) for s in cv.split('.')]
  50.         deps = 'python (>= %s), python (<< %d.%d)' % (cv, nv[0], nv[1]+1)
  51.     else:
  52.         raise ValueError, 'unknown version info %s' % vinfo
  53.     return deps + ', python-central (>= %s)' % req_pycentral_version
  54.         
  55.  
  56. def read_dpkg_status():
  57.         """Read the dpkg status file, return a list of packages depending
  58.         on python-central and having a Python-Version information field."""
  59.         packages = []
  60.         rx = re.compile(r'\bpython-central\b')
  61.         pkgname = version = None
  62.         depends = ''
  63.         for line in file('/var/lib/dpkg/status'):
  64.             if line.startswith('Package:'):
  65.                 if version != None:
  66.                     if 'python-support' in depends:
  67.                         continue
  68.                     if rx.search(depends):
  69.                         packages.append((pkgname, version))
  70.                 version = None
  71.                 pkgname = line.split(':', 1)[1].strip()
  72.             elif line.startswith('Python-Version:'):
  73.                 version = line.split(':', 1)[1].strip()
  74.             elif line.startswith('Depends:'):
  75.                 depends = line.split(':', 1)[1].strip()
  76.         if version != None:
  77.             if rx.search(depends):
  78.                 packages.append((pkgname, version))
  79.         return packages
  80.  
  81.  
  82. class PyCentralError(Exception):
  83.     """Python Central Exception"""
  84.     pass
  85.  
  86. class PyCentralVersionMissingError(PyCentralError):
  87.     """Python Central Version Missing Exception"""
  88.     pass
  89.  
  90. class PythonRuntime:
  91.     def __init__(self, name, version, interp, prefix):
  92.         self.name = name
  93.         self.version = version
  94.         if name.startswith('python'):
  95.             self.short_name = name[6:]
  96.         else:
  97.             self.short_name = name
  98.         self.interp = interp
  99.         if prefix.endswith('/'):
  100.             self.prefix = prefix
  101.         else:
  102.             self.prefix = prefix + '/'
  103.  
  104.     def byte_compile_dirs(self, dirs, bc_option, exclude=None):
  105.         """call compileall.py -x <exclude regexp> <dirs> according
  106.         to bc_options"""
  107.         
  108.         logging.debug('\tbyte-compile directories')
  109.         errors = False
  110.         cmd = [self.interp, self.prefix + '/compileall.py', '-q']
  111.         if exclude:
  112.             cmd.extend(['-x', exclude])
  113.         cmd.extend(dirs)
  114.         for opt in ('standard', 'optimize'):
  115.             if not opt in bc_option:
  116.                 continue
  117.             if opt == 'optimize':
  118.                 cmd[1:1] = ['-O']
  119.             rv = os.spawnv(os.P_WAIT, self.interp, cmd[1:])
  120.             if rv:
  121.                 raise PyCentralError
  122.  
  123.     def byte_compile(self, files, bc_option, exclude=None):
  124.         errors = False
  125.         if exclude:
  126.             rx = re.compile(exclude)
  127.             files2 = []
  128.             for fn in files:
  129.                 mo = rx.search(fn)
  130.                 if mo:
  131.                     continue
  132.                 files2.append(fn)
  133.         else:
  134.             files2 = files
  135.         if not files2:
  136.             logging.info('\tno files to byte-compile')
  137.             return
  138.         logging.debug('\tbyte-compile files (%d/%d) %s' \
  139.                       % (len(files), len(files2), self.name))
  140.         debug_files = files2[:min(2, len(files2))]
  141.         if len(files2) > 2:
  142.             debug_files.append('...')
  143.         logging.debug('\t    %s' % debug_files)
  144.         cmd = [self.interp, '/usr/bin/py_compilefiles', '-q', '-']
  145.         for opt in ('standard', 'optimize'):
  146.             if not opt in bc_option:
  147.                 continue
  148.             if opt == 'optimize':
  149.                 cmd[1:1] = ['-O']
  150.             try:
  151.                 import subprocess
  152.                 p = subprocess.Popen(cmd, bufsize=1,
  153.                                      shell=False, stdin=subprocess.PIPE)
  154.                 fd = p.stdin
  155.             except ImportError:
  156.                 p = None
  157.                 fd = os.popen(' '.join(cmd), 'w')
  158.             for fn in files2:
  159.                 fd.write(fn + '\n')
  160.             rv = fd.close()
  161.             if p:
  162.                 p.wait()
  163.                 errors = p.returncode != 0
  164.             else:
  165.                 errors = rv != None
  166.             if errors:
  167.                 raise PyCentralError, 'error byte-compiling files (%d)' % len(files2)
  168.  
  169.     def remove_byte_code(self, files):
  170.         errors = False
  171.         logging.debug('\tremove byte-code files (%d)' % (len(files)))
  172.         for ext in ('c', 'o'):
  173.             for fn in files:
  174.                 fnc = fn + ext
  175.                 if os.path.exists(fnc):
  176.                     try:
  177.                         os.unlink(fnc)
  178.                     except OSError, e:
  179.                         print "Sorry", e
  180.                         errors = True
  181.         if errors:
  182.             raise PyCentralError, 'error removing the byte-code files'
  183.  
  184.  
  185. installed_runtimes = None
  186. default_runtime = None
  187.  
  188. def get_installed_runtimes():
  189.     global installed_runtimes
  190.     global default_runtime
  191.  
  192.     if not installed_runtimes:
  193.         import glob
  194.         installed_runtimes = []
  195.         default_version = pyversions.default_version(version_only=True)
  196.         supported = pyversions.supported_versions()
  197.         for interp in glob.glob('/usr/bin/python[0-9].[0-9]'):
  198.             if not os.path.basename(interp) in supported:
  199.                 continue
  200.             version = interp[-3:]
  201.             rt = PythonRuntime('python' + version,
  202.                                version,
  203.                                '/usr/bin/python' + version,
  204.                                '/usr/lib/python' + version)
  205.             installed_runtimes.append(rt)
  206.             if version == default_version:
  207.                 default_runtime = rt
  208.     return installed_runtimes
  209.  
  210. def get_default_runtime():
  211.     get_installed_runtimes()
  212.     return default_runtime
  213.  
  214. def get_runtime_for_version(version):
  215.     if version == 'current':
  216.         return get_default_runtime()
  217.     for rt in get_installed_runtimes():
  218.         if rt.version == version:
  219.             return rt
  220.     return None
  221.     
  222. debian_config = None
  223. def get_debian_config():
  224.     global debian_config
  225.     if debian_config is not None:
  226.         return debian_config
  227.  
  228.     from ConfigParser import SafeConfigParser
  229.     config = SafeConfigParser()
  230.     fn = '/etc/python/debian_config'
  231.     if os.path.exists(fn):
  232.         try:
  233.             config.readfp(open(fn))
  234.         except Error:
  235.             logging.error("error reading config file `%s'" % fn)
  236.             sys.exit(1)
  237.     # checks
  238.     if not config.has_option('DEFAULT', 'byte-compile'):
  239.         config.set('DEFAULT', 'byte-compile', 'standard')
  240.     bc_option = config.get('DEFAULT', 'byte-compile')
  241.     bc_values = set([v.strip() for v in bc_option.split(',')])
  242.     bc_unknown = bc_values - set(['standard', 'optimize'])
  243.     if bc_unknown:
  244.         sys.stderr.write("%s: `%s': unknown values `%s'"
  245.                          " in `byte-compile option'\n"
  246.                          % (program, fn, ', '.join(list(bc_unknown))))
  247.         sys.exit(1)
  248.     config.set('DEFAULT', 'byte-compile', ', '.join(bc_values))
  249.     if config.has_option('DEFAULT', 'overwrite-local'):
  250.         val = config.get('DEFAULT', 'overwrite-local').strip().lower()
  251.         overwrite_local = val in ('yes', '1', 'true')
  252.     else:
  253.         overwrite_local = False
  254.     config.set('DEFAULT', 'overwrite-local', overwrite_local and '1' or '0')
  255.     debian_config = config
  256.     return debian_config
  257.  
  258. class DebPackage:
  259.     def __init__(self, kind, name,
  260.                  oldstyle=False, default_runtime=None,
  261.                  pkgdir=None, parse_versions=True):
  262.         self.kind = kind
  263.         self.name = name
  264.         self.version_field = None
  265.         self.oldstyle = oldstyle
  266.         self.parse_versions = parse_versions
  267.         self.default_runtime = default_runtime
  268.         self.shared_prefix = shared_base + name + '/'
  269.         self.pkgdir = pkgdir
  270.         self.has_shared_extension = {}
  271.         self.has_private_extension = False
  272.         self.has_shared_module = {}
  273.         self.has_private_module = False
  274.         if pkgdir:
  275.             self.read_control()
  276.         else:
  277.             self.read_pyfiles()
  278.             #self.print_info()
  279.  
  280.     def read_pyfiles(self):
  281.         self.shared_files = []
  282.         self.pylib_files = {}
  283.         self.private_files = []
  284.         self.omitted_files = []
  285.         self.pysupport_files = []
  286.         if self.pkgdir:
  287.             lines = []
  288.             for root, dirs, files in os.walk(self.pkgdir):
  289.                 if root.endswith('DEBIAN'):
  290.                     continue
  291.                 if root != self.pkgdir:
  292.                     d = root[len(self.pkgdir):]
  293.                     lines.append(d)
  294.                 for name in files:
  295.                     lines.append(os.path.join(d, name))
  296.         else:
  297.             #lines = [s[:-1] for s in file('/var/lib/dpkg/info/%s.list' % self.name).readlines()]
  298.             cmd = ['/usr/bin/dpkg-query', '-L', self.name]
  299.             try:
  300.                 import subprocess
  301.                 p = subprocess.Popen(cmd, bufsize=1,
  302.                                      shell=False, stdout=subprocess.PIPE)
  303.                 fd = p.stdout
  304.             except ImportError:
  305.                 fd = os.popen(' '.join(cmd))
  306.             lines = [s[:-1] for s in fd.readlines()]
  307.  
  308.         for line in lines:
  309.             fn = line
  310.             if fn.startswith(self.shared_prefix):
  311.                 # keep _all_ files and directories
  312.                 self.shared_files.append(fn)
  313.                 if fn.endswith('.py'):
  314.                     self.has_shared_module['all'] = True
  315.                 continue
  316.             elif fn.startswith('/usr/share/python-support') \
  317.                      or fn.startswith('/usr/lib/python-support'):
  318.                 self.pysupport_files.append(fn)
  319.                 continue
  320.             elif not fn.endswith('.py') and not fn.endswith('.so'):
  321.                 continue
  322.             elif fn.startswith('/etc/') or fn.startswith('/usr/share/doc/'):
  323.                 # omit files in /etc and /usr/share/doc
  324.                 self.omitted_files.append(fn)
  325.                 continue
  326.             elif re.search(r'/s?bin/', fn):
  327.                 # omit files located in directories
  328.                 self.omitted_files.append(fn)
  329.                 continue
  330.             elif fn.startswith('/usr/lib/site-python/'):
  331.                 version = pyversions.default_version(version_only=True)
  332.                 self.pylib_files.setdefault(version, []).append(fn)
  333.                 continue
  334.             elif re.match(r'/usr/lib/python\d\.\d/', fn):
  335.                 version = fn[15:18]
  336.                 if fn.endswith('.so'):
  337.                     self.has_shared_extension[version] = True
  338.                 if fn.endswith('.py'):
  339.                     self.has_shared_module[version] = True
  340.                     self.pylib_files.setdefault(version, []).append(fn)
  341.                 continue
  342.             else:
  343.                 self.private_files.append(fn)
  344.                 if fn.endswith('.py'):
  345.                     self.has_private_module = True
  346.  
  347.     def print_info(self, fd=sys.stdout):
  348.         fd.write('Package: %s\n' % self.name)
  349.         fd.write('    shared files  :%4d\n' % len(self.shared_files))
  350.         fd.write('    private files :%4d\n' % len(self.private_files))
  351.         for ver, files in self.pylib_files.items():
  352.             fd.write('    pylib%s files:%4d\n' % (ver, len(files)))
  353.  
  354.     def read_control(self):
  355.         """read the debian/control file, extract the XS-Python-Version
  356.         field; check that XB-Python-Version exists for the package."""
  357.         if not os.path.exists('debian/control'):
  358.             raise PyCentralError, "debian/control not found"
  359.         self.version_field = None
  360.         self.sversion_field = None
  361.         try:
  362.             section = None
  363.             for line in file('debian/control'):
  364.                 line = line.strip()
  365.                 if line == '':
  366.                     section = None
  367.                 elif line.startswith('Source:'):
  368.                     section = 'Source'
  369.                 elif line.startswith('Package: ' + self.name):
  370.                     section = self.name
  371.                 elif line.startswith('XS-Python-Version:'):
  372.                     if section != 'Source':
  373.                         raise PyCentralError, \
  374.                               'attribute XS-Python-Version not in Source section'
  375.                     self.sversion_field = line.split(':', 1)[1].strip()
  376.                 elif line.startswith('XB-Python-Version:'):
  377.                     if section == self.name:
  378.                         self.version_field = line.split(':', 1)[1].strip()
  379.         except:
  380.             pass
  381.         if self.version_field == None:
  382.             raise PyCentralVersionMissingError, \
  383.                   'missing XB-Python-Version attribute in package %s' % self.name
  384.         if self.sversion_field == None:
  385.             raise PyCentralError, 'missing XS-Python-Version attribute'
  386.         if self.parse_versions:
  387.             self.sversion_info = parse_versions(self.sversion_field)
  388.         else:
  389.             self.sversion_info = 'all' # dummy
  390.         self.has_private_extension = self.sversion_info == 'current_ext'
  391.  
  392.     def move_files(self):
  393.         """move files from the installation directory to the pycentral location"""
  394.         import shutil
  395.  
  396.         dsttop = self.pkgdir + shared_base + self.name
  397.         try:
  398.             os.makedirs(dsttop)
  399.         except OSError:
  400.             pass
  401.         for pversion in pyversions.supported_versions():
  402.             srctop = os.path.join(self.pkgdir, 'usr/lib', pversion)
  403.             for root, dirs, files in os.walk(srctop):
  404.                 if root == srctop:
  405.                     d = '.'
  406.                 else:
  407.                     d = root[len(srctop)+1:]
  408.                 for name in dirs:
  409.                     src = os.path.join(root, name)
  410.                     dst = os.path.join(dsttop, d, name)
  411.                     try:
  412.                         os.mkdir(dst)
  413.                         shutil.copymode(src, dst)
  414.                     except OSError:
  415.                         pass
  416.                 for name in files:
  417.                     src = os.path.join(root, name)
  418.                     dst = os.path.join(dsttop, d, name)
  419.                     if re.search(r'\.so(\.\d+)*?$', name):
  420.                         continue
  421.                     if name.endswith('.pyc') or name.endswith('.pyo'):
  422.                         os.unlink(src)
  423.                         continue
  424.                     # TODO: if dst already exists, make sure, src == dst
  425.                     os.rename(src, dst)
  426.             # remove empty dirs in /usr/lib
  427.             for root, dirs, files in os.walk(self.pkgdir + '/usr/lib', topdown=False):
  428.                 try:
  429.                     os.rmdir(root)
  430.                 except OSError:
  431.                     pass
  432.         # remove empty dirs in /usr/share/pycentral
  433.         for root, dirs, files in os.walk(self.pkgdir + shared_base, topdown=False):
  434.             try:
  435.                 os.rmdir(root)
  436.             except OSError:
  437.                 pass
  438.  
  439.     def gen_substvars(self):
  440.         supported = [d[6:] for d in pyversions.supported_versions()
  441.                      if re.match(r'python\d\.\d', d)]
  442.         versions = ''
  443.         prversions = ''
  444.         self.depends = None
  445.         if len(self.has_shared_module) or len(self.has_shared_extension):
  446.             # shared modules / extensions
  447.             if len(self.has_shared_extension):
  448.                 versions = self.has_shared_extension.keys()
  449.             else:
  450.                 if self.sversion_info in ('current', 'current_ext'):
  451.                     versions = 'current'
  452.                 elif self.sversion_info == 'all':
  453.                     versions = 'all'
  454.                     prversions = supported
  455.                 else:
  456.                     versions = self.sversion_field
  457.                     prversions = list(self.sversion_info.intersection(supported))
  458.                     self.depends = version2depends(self.sversion_info)
  459.         elif self.has_private_module or self.has_private_extension:
  460.             if self.sversion_info == 'all':
  461.                 versions = 'current'
  462.             elif self.sversion_info == 'current':
  463.                 versions = 'current'
  464.             elif self.sversion_info == 'current_ext':
  465.                 versions = [pyversions.default_version(version_only=True)]
  466.             elif isinstance(self.sversion_info, list) or isinstance(self.sversion_info, set):
  467.                 # exact version info required, no enumeration, no relops
  468.                 if len(self.sversion_info) != 1 or not re.match(r'\d\.\d', self.sversion_info[0]):
  469.                     raise PyCentralError, 'no exact version for package with private modules'
  470.                 versions = [list(self.sversion_info)[0]]
  471.             else:
  472.                 raise PyCentralError, 'version error for package with private modules'
  473.         else:
  474.             # just "copy" it from the source field
  475.             if self.sversion_info == 'current':
  476.                 versions = 'current'
  477.             elif self.sversion_info == 'current_ext':
  478.                 versions = [pyversions.default_version(version_only=True)]
  479.             elif self.sversion_info == 'all':
  480.                 versions = 'all'
  481.                 prversions = supported
  482.             else:
  483.                 versions = self.sversion_field
  484.                 prversions = list(self.sversion_info.intersection(supported))
  485.                 self.depends = version2depends(self.sversion_info)
  486.  
  487.         if (len(self.has_shared_module) or len(self.has_shared_extension)) \
  488.            and self.has_private_module or self.has_private_extension:
  489.             # use case? use the information for the shared stuff
  490.             pass
  491.         if versions == '':
  492.             raise PyCentralError, 'unable to determine Python-Version attribute'
  493.         if isinstance(versions, list) or isinstance(versions, set):
  494.             self.version_field = ', '.join(versions)
  495.         else:
  496.             self.version_field = versions
  497.         if not self.depends:
  498.             self.depends = version2depends(versions)
  499.         if self.name.startswith('python-'):
  500.             if prversions == '':
  501.                 prversions = versions
  502.             self.provides = ', '.join([self.name.replace('python-', 'python%s-' % ver)
  503.                                        for ver in prversions])
  504.     
  505.     def read_version_info(self):
  506.         """Read the Python-Version information field"""
  507.         if self.version_field:
  508.             return
  509.         cmd = ['/usr/bin/dpkg-query', '-s', self.name]
  510.         try:
  511.             import subprocess
  512.             p = subprocess.Popen(cmd, bufsize=1,
  513.                                  shell=False, stdout=subprocess.PIPE)
  514.             fd = p.stdout
  515.         except ImportError:
  516.             fd = os.popen(' '.join(cmd))
  517.             
  518.         for line in fd:
  519.             if line.startswith('Python-Version:'):
  520.                 self.version_field = line.split(':', 1)[1].strip()
  521.                 break
  522.         fd.close()
  523.         if not self.version_field:
  524.             raise PyCentralError, "package has no field Python-Version"
  525.         if self.parse_versions:
  526.             self.version_info = pyversions.parse_versions(self.version_field)
  527.  
  528.     def set_default_runtime_from_version_info(self):
  529.         versions = list(pyversions.requested_versions(self.version_field, version_only=True))
  530.         if not versions:
  531.             raise PyCentralError, "no matching runtime for `%s'" % self.version_field
  532.         if len(versions) == 1:
  533.             self.default_runtime = get_runtime_for_version(versions[0])
  534.         elif pyversions.default_version(version_only=True) in versions:
  535.             self.default_runtime = get_default_runtime()
  536.         else:
  537.             self.default_runtime = get_runtime_for_version(versions[0])
  538.  
  539.     def byte_compile(self, bc_option, exclude_regex):
  540.         """byte compiles all files not handled by pycentral"""
  541.  
  542.         assert self.oldstyle
  543.  
  544.         logging.debug("    byte-compile %s" % self.name)
  545.         for version, files in self.pylib_files.items():
  546.             logging.debug("bc for v%s (%d files)" % (version, len(files)))
  547.             rt = get_runtime_for_version(version)
  548.             rt.byte_compile(files, bc_option, exclude_regex)
  549.         if self.private_files:
  550.             logging.debug("bc private (%d files)" %
  551.                           (self.default_runtime.version, len(self.private_files)))
  552.             rt = self.default_runtime
  553.             rt.byte_compile(self.private_files, bc_option, exclude_regex)
  554.  
  555.     def remove_bytecode(self):
  556.         """remove all byte-compiled files not handled by pycentral"""
  557.         
  558.         assert self.oldstyle
  559.  
  560.         logging.debug("    remove byte-code for %s" % self.name)
  561.         pyfiles = []
  562.         for files in self.pylib_files.values():
  563.             pyfiles.extend(files)
  564.         pyfiles.extend(self.private_files)
  565.  
  566.         errors = False
  567.         for ext in ('c', 'o'):
  568.             for fn in pyfiles:
  569.                 fnc = fn + ext
  570.                 if os.path.exists(fnc):
  571.                     try:
  572.                         os.unlink(fnc)
  573.                     except OSError, e:
  574.                         print "Sorry", e
  575.                         errors = True
  576.         if errors:
  577.             raise PyCentralError
  578.  
  579.     def link_shared_files(self, rt):
  580.         #if samefs(rt.prefix, self.shared_files[0]):
  581.         #    link_cmd = os.link
  582.         #else:
  583.         #    link_cmd = os.symlink
  584.         logging.debug("\tlink shared files %s/%s" % (rt.name, self.name))
  585.         link_cmd = os.symlink
  586.         ppos = len(self.shared_prefix)
  587.         linked_files = []
  588.         try:
  589.             for fn in self.shared_files:
  590.                 fn2 = rt.prefix + fn[ppos:]
  591.                 if os.path.isdir(fn):
  592.                     if os.path.isdir(fn2):
  593.                         continue
  594.                     os.makedirs(fn2)
  595.                     linked_files.append(fn2)
  596.                 else:
  597.                     if os.path.exists(fn2):
  598.                         msg = "already exists: %s" % fn2
  599.                         if os.path.islink(fn2):
  600.                             link = os.readlink(fn2)
  601.                             if link == fn:
  602.                                 linked_files.append(fn2)
  603.                                 continue
  604.                             msg = msg + " -> %s" % link
  605.                         conf = get_debian_config()
  606.                         if conf.get('DEFAULT', 'overwrite-local') == '1':
  607.                             print "warning:", msg
  608.                             os.unlink(fn2)
  609.                         else:
  610.                             raise PyCentralError, msg
  611.                     link_cmd(fn, fn2)
  612.                     linked_files.append(fn2)
  613.         except PyCentralError, msg:
  614.             raise
  615.         except Exception, msg:
  616.             print msg
  617.             # FIXME: undo
  618.             linked_files.reverse()
  619.             return []
  620.         else:
  621.             return linked_files
  622.  
  623.     def unlink_shared_files(self, rt):
  624.         logging.debug('\tunlink_shared_files %s/%s' % (rt.name, self.name))
  625.         ppos = len(self.shared_prefix)
  626.         shared_files = self.shared_files[:]
  627.         shared_files.reverse()
  628.         for fn in shared_files:
  629.             fn2 = rt.prefix + fn[ppos:]
  630.             if os.path.isdir(fn2):
  631.                 try:
  632.                     os.removedirs(fn2)
  633.                 except OSError:
  634.                     pass
  635.             else:
  636.                 if os.path.exists(fn2):
  637.                     os.unlink(fn2)
  638.  
  639.  
  640.     def install(self, runtimes, bc_option, exclude_regex,
  641.                 byte_compile_default=True):
  642.         logging.debug('\tinstall package %s' % self.name)
  643.         # install shared .py files
  644.         if self.shared_files:
  645.             for rt in runtimes:
  646.                 linked_files = self.link_shared_files(rt)
  647.                 rt.byte_compile(linked_files, bc_option, exclude_regex)
  648.         # byte compile files inside prefix
  649.         if self.pylib_files:
  650.             for pyver, files in self.pylib_files.items():
  651.                 rt = get_runtime_for_version(pyver)
  652.                 if rt in runtimes:
  653.                     rt.byte_compile(files, bc_option, exclude_regex)
  654.         # byte compile with the default runtime for the package
  655.         if byte_compile_default:
  656.             if self.private_files:
  657.                 self.default_runtime.byte_compile(self.private_files,
  658.                                                   bc_option, exclude_regex)
  659.  
  660.     def remove(self, runtimes, remove_script_files=True):
  661.         logging.debug('\tremove package %s' % self.name)
  662.         ppos = len(self.shared_prefix)
  663.         # remove shared .py files
  664.         if self.shared_files:
  665.             for rt in runtimes:
  666.                 linked_files = [ rt.prefix + fn[ppos:]
  667.                                  for fn in self.shared_files
  668.                                  if fn[-3:] == '.py']
  669.                 #print self.shared_files
  670.                 #print linked_files
  671.                 rt.remove_byte_code(linked_files)
  672.                 self.unlink_shared_files(rt)
  673.         # remove byte compiled files inside prefix
  674.         if self.pylib_files:
  675.             for pyver, files in self.pylib_files.items():
  676.                 rt = get_runtime_for_version(pyver)
  677.                 if rt in runtimes:
  678.                     rt.remove_byte_code(files)
  679.         # remove byte code for script files
  680.         if remove_script_files:
  681.             if self.private_files:
  682.                 default_runtime.remove_byte_code(self.private_files)
  683.  
  684.     def update_bytecode_files(self, runtimes, rt_default, bc_option):
  685.         # byte-compile with default python version
  686.         logging.debug('\tupdate byte-code for %s' % self.name)
  687.         ppos = len(self.shared_prefix)
  688.         exclude_regex = None
  689.         # update shared .py files
  690.         if self.shared_files:
  691.             for rt in runtimes:
  692.                 if rt == rt_default:
  693.                     linked_files = self.link_shared_files(rt)
  694.                     rt.byte_compile(linked_files, bc_option, exclude_regex)
  695.                 else:
  696.                     linked_files = [ rt.prefix + fn[ppos:]
  697.                                      for fn in self.shared_files
  698.                                      if fn[-3:] == '.py']
  699.                     rt.remove_byte_code(linked_files)
  700.                     self.unlink_shared_files(rt)
  701.         # byte compile with the default runtime for the package
  702.         if self.private_files:
  703.             self.default_runtime.byte_compile(self.private_files,
  704.                                               bc_option, exclude_regex)
  705.  
  706. known_actions = {}
  707. def register_action(action_class):
  708.     known_actions[action_class.name] = action_class
  709.  
  710. class Action:
  711.     _option_parser = None
  712.     name = None
  713.     help = ""
  714.     usage = "<options>"
  715.     def __init__(self):
  716.         self.errors_occured = 0
  717.         parser = self.get_option_parser()
  718.         parser.set_usage(
  719.             'usage: %s [<options> ...] %s %s' % (program, self.name, self.usage))
  720.  
  721.     def get_option_parser(self):
  722.         if not self._option_parser:
  723.             p = OptionParser()
  724.             self._option_parser = p
  725.         return self._option_parser
  726.  
  727.     def info(self, msg, stream=sys.stderr):
  728.         logging.info('%s %s: %s' % (program, self.name, msg))
  729.  
  730.     def warn(self, msg, stream=sys.stderr):
  731.         logging.warn('%s %s: %s' % (program, self.name, msg))
  732.  
  733.     def error(self, msg, stream=sys.stderr, go_on=False):
  734.         logging.error('%s %s: %s' % (program, self.name, msg))
  735.         self.errors_occured += 1
  736.         if not go_on:
  737.             sys.exit(1)
  738.  
  739.     def parse_args(self, arguments):
  740.         self.options, self.args = self._option_parser.parse_args(arguments)
  741.         return self.options, self.args
  742.  
  743.     def check_args(self, global_options):
  744.         return self.errors_occured
  745.  
  746.     def run(self, global_opts):
  747.         pass
  748.  
  749.  
  750. class ActionByteCompile(Action):
  751.     """byte compile the *.py files in <package> using the the
  752.     default python version (or use the version specified with -v.
  753.     Any additional directory arguments are ignored (only files
  754.     found in the package are byte compiled. Files in
  755.     /usr/lib/pythonX.Y are compiled with the matching python version.
  756.  
  757.     bccompile is a replacement for the current byte compilation
  758.     generated by the dh_python debhelper script.
  759.     """
  760.     name = 'bccompile'
  761.     help = 'byte compile .py files in a package'
  762.     usage = '[<options>] <package> [<dir> ...]'
  763.  
  764.     def get_option_parser(self):
  765.         if not self._option_parser:
  766.             p = OptionParser()
  767.             p.add_option('-x', '--exclude',
  768.                          help="skip files matching the regular expression",
  769.                          default=None, action='store', dest='exclude')
  770.             p.add_option('-V', '--version',
  771.                          help="byte compile using this python version",
  772.                          default='current', action='store', dest='version')
  773.             self._option_parser = p
  774.         return self._option_parser
  775.  
  776.     def check_args(self, global_options):
  777.         if len(self.args) < 1:
  778.             self._option_parser.print_help()
  779.             sys.exit(1)
  780.         self.pkgname = self.args[0]
  781.         self.runtime = get_runtime_for_version(self.options.version)
  782.         if not self.runtime:
  783.             self.error("unknown runtime version %s" % self.options.version)
  784.  
  785.         if not os.path.exists('/var/lib/dpkg/info/%s.list' % self.pkgname):
  786.             self.error("package %s is not installed" % self.pkgname)
  787.         self.pkg = DebPackage('package', self.pkgname, oldstyle=True,
  788.                               default_runtime=self.runtime)
  789.  
  790.         try:
  791.             self.pkg.read_version_info()
  792.         except PyCentralError:
  793.             self.warn('package with `Python-Version information should'
  794.                       ' use pkginstall instead of bccompile')
  795.             #self.version_field = 'current'
  796.         return self.errors_occured
  797.  
  798.     def run(self, global_options):
  799.         logging.debug('bccompile %s' % self.pkgname)
  800.         config = get_debian_config()
  801.         bc_option = config.get('DEFAULT', 'byte-compile')
  802.  
  803.         # called with directories as arguments
  804.         if 0 and self.directories:
  805.             try:
  806.                 for version, dirs in self.pylib_dirs.items():
  807.                     rt = get_runtime_for_version(version)
  808.                     rt.byte_compile_dirs(dirs, bc_option, self.options.exclude)
  809.                 if self.private_dirs:
  810.                     version = pkg.version_field
  811.                     if version == 'current':
  812.                         version = pyversions.default_version(version_only=True)
  813.                     rt = get_runtime_for_version(version)
  814.                     rt.byte_compile_dirs(private_dirs, bc_option, self.options.exclude)
  815.             except PyCentralError:
  816.                 self.error("error byte-compiling package `%s'" % self.pkgname)
  817.             return
  818.  
  819.         try:
  820.             self.pkg.byte_compile(bc_option, self.options.exclude)
  821.         except PyCentralError:
  822.             self.error("error byte-compiling package `%s'" % self.pkgname)
  823.  
  824. register_action(ActionByteCompile)
  825.  
  826. class ActionPkgInstall(Action):
  827.     name = 'pkginstall'
  828.     help = 'make a package available for all supported runtimes'
  829.     usage = '[<options>] <package>'
  830.     
  831.     def get_option_parser(self):
  832.         if not self._option_parser:
  833.             p = OptionParser()
  834.             p.add_option('-x', '--exclude',
  835.                          help="skip files matching the regular expression",
  836.                          default=None, action='store', dest='exclude')
  837.             self._option_parser = p
  838.         return self._option_parser
  839.  
  840.     def check_args(self, global_options):
  841.         if len(self.args) != 1:
  842.             self._option_parser.print_help()
  843.             sys.exit(1)
  844.         self.pkgname = self.args[0]
  845.         if not os.path.exists('/var/lib/dpkg/info/%s.list' % self.pkgname):
  846.             self.error("package %s is not installed" % self.pkgname)
  847.         return self.errors_occured
  848.  
  849.     def run(self, global_options):
  850.         runtimes = get_installed_runtimes()
  851.         config = get_debian_config()
  852.         bc_option = config.get('DEFAULT', 'byte-compile')
  853.         pkg = DebPackage('package', self.args[0], oldstyle=False)
  854.         pkg.read_version_info()
  855.         pkg.set_default_runtime_from_version_info()
  856.  
  857.         requested = pyversions.requested_versions(pkg.version_field, version_only=True)
  858.         used_runtimes = [rt for rt in runtimes if rt.short_name in requested]
  859.         logging.debug('\tavail=%s, pkg=%s, install=%s'
  860.                       % ([rt.short_name for rt in runtimes],
  861.                          pkg.version_field,
  862.                          [rt.short_name for rt in used_runtimes]))
  863.         try:
  864.             pkg.install(used_runtimes, bc_option,
  865.                         self.options.exclude, byte_compile_default=True)
  866.         except PyCentralError, msg:
  867.             self.error(msg)
  868.             
  869.  
  870. register_action(ActionPkgInstall)
  871.  
  872.  
  873. class ActionBCRemove(Action):
  874.     """remove the byte-compiled files in <package>.
  875.  
  876.     bccompile is a replacement for the current byte compilation
  877.     generated by the dh_python debhelper script.
  878.     """
  879.     name = 'bcremove'
  880.     help = 'remove the byte compiled .py files'
  881.     usage = '<package>'
  882.  
  883.     def check_args(self, global_options):
  884.         if len(self.args) != 1:
  885.             self._option_parser.print_help()
  886.             sys.exit(1)
  887.         self.pkgname = self.args[0]
  888.         if not os.path.exists('/var/lib/dpkg/info/%s.list' % self.pkgname):
  889.             self.error("package %s is not installed" % self.pkgname)
  890.         return self.errors_occured
  891.  
  892.     def run(self, global_options):
  893.         pkg = DebPackage('package', self.args[0], oldstyle=True)
  894.         try:
  895.             pkg.remove_bytecode()
  896.         except PyCentralError, msg:
  897.             self.error(msg)
  898.  
  899. register_action(ActionBCRemove)
  900.  
  901.  
  902. class ActionPkgRemove(Action):
  903.     """
  904.     """
  905.     name = 'pkgremove'
  906.     help = 'remove a package installed for all supported runtimes'
  907.     usage = '<package>'
  908.  
  909.     def check_args(self, global_options):
  910.         if len(self.args) != 1:
  911.             self._option_parser.print_help()
  912.             sys.exit(1)
  913.         self.pkgname = self.args[0]
  914.         if not os.path.exists('/var/lib/dpkg/info/%s.list' % self.pkgname):
  915.             self.error("package %s is not installed" % self.pkgname)
  916.         return self.errors_occured
  917.  
  918.     def run(self, global_options):
  919.         runtimes = get_installed_runtimes()
  920.         pkg = DebPackage('package', self.args[0], oldstyle=False)
  921.         try:
  922.             pkg.remove(runtimes, remove_script_files=True)
  923.         except PyCentralError, msg:
  924.             self.error(msg)
  925.  
  926. register_action(ActionPkgRemove)
  927.  
  928.  
  929. class ActionRuntimeInstall(Action):
  930.     name = 'rtinstall'
  931.     help = 'make installed packages available for this runtime'
  932.  
  933.     def check_args(self, global_options):
  934.         if len(self.args) != 1:
  935.             self._option_parser.print_help()
  936.             sys.exit(1)
  937.         self.rtname = self.args[0]
  938.         if self.rtname[-8:] == '-minimal':
  939.             self.rtname = self.rtname[:-8]
  940.         self.runtime = None
  941.         for rt in get_installed_runtimes():
  942.             if rt.name == self.rtname:
  943.                 self.runtime = rt
  944.                 break
  945.         if not self.runtime:
  946.             self.error('installed runtime %s not found' % self.rtname)
  947.         return self.errors_occured
  948.  
  949.     def run(self, global_options):
  950.         packages = [(p, v) for p, v in read_dpkg_status()
  951.                     if not p in (self.rtname, self.rtname+'-minimal')]
  952.         needed_packages = []
  953.         for pkgname, vstring in packages:
  954.             requested = list(pyversions.requested_versions(vstring, version_only=True))
  955.             if self.runtime.short_name in requested:
  956.                 needed_packages.append((pkgname, vstring, requested))
  957.         logging.info('\t%d packages with Python-Version info installed, %d for %s'
  958.                      % (len(packages), len(needed_packages), self.rtname))
  959.  
  960.         # XXX not needed for an upgrade of a runtime
  961.         byte_compile_for_default = (self.runtime == default_runtime)
  962.  
  963.         bc_option = get_debian_config().get('DEFAULT', 'byte-compile')
  964.         for pkgname, vstring, vinfo in needed_packages:
  965.             try:
  966.                 pkg = DebPackage('package', pkgname, oldstyle=False)
  967.                 pkg.read_version_info()
  968.                 pkg.set_default_runtime_from_version_info()
  969.                 logging.info('\tsupport %s for %s' % (pkgname, self.rtname))
  970.                 pkg.install([self.runtime], bc_option, None, byte_compile_for_default)
  971.             except PyCentralError, msg:
  972.                 self.error('package %s: %s' % (pkgname, msg))
  973.  
  974. register_action(ActionRuntimeInstall)
  975.  
  976. class ActionRuntimeRemove(Action):
  977.     name = 'rtremove'
  978.     help = 'remove packages installed for this runtime'
  979.  
  980.     def check_args(self, global_options):
  981.         if len(self.args) != 1:
  982.             self._option_parser.print_help()
  983.             sys.exit(1)
  984.         self.rtname = self.args[0]
  985.         if self.rtname[-8:] == '-minimal':
  986.             self.rtname = self.rtname[:-8]
  987.         self.runtime = None
  988.         for rt in get_installed_runtimes():
  989.             if rt.name == self.rtname:
  990.                 self.runtime = rt
  991.                 break
  992.         if not self.runtime:
  993.             self.error('installed runtime %s not found' % self.rtname)
  994.         return self.errors_occured
  995.  
  996.     def run(self, global_options):
  997.         packages = [(p, v) for p, v in read_dpkg_status()
  998.                     if not p in (self.rtname, self.rtname+'-minimal')]
  999.         needed_packages = []
  1000.         for pkgname, vstring in packages:
  1001.             requested = pyversions.requested_versions(vstring, version_only=True)
  1002.             if self.runtime.short_name in requested:
  1003.                 needed_packages.append((pkgname, vstring, requested))
  1004.         logging.info('\t%d pycentral supported packages installed, %d for %s'
  1005.                      % (len(packages), len(needed_packages), self.rtname))
  1006.         try:
  1007.             for pkgname, vstring, vinfo in needed_packages:
  1008.                 pkg = DebPackage('package', pkgname)
  1009.                 logging.info('\trtremove: remove package %s for %s' % (pkgname, self.rtname))
  1010.                 pkg.remove([self.runtime], remove_script_files=False)
  1011.         except PyCentralError, msg:
  1012.             self.error(msg)
  1013.  
  1014. register_action(ActionRuntimeRemove)
  1015.  
  1016.  
  1017. class ActionUpdateDefault(Action):
  1018.     name = 'updatedefault'
  1019.     help = 'update the default python version'
  1020.     usage = '<old runtime> <new runtime>'
  1021.     
  1022.     def check_args(self, global_options):
  1023.         if len(self.args) != 2:
  1024.             self._option_parser.print_help()
  1025.             sys.exit(1)
  1026.         self.oldrtname = self.args[0]
  1027.         self.rtname = self.args[1]
  1028.         packages = read_dpkg_status()
  1029.         self.needed_packages = []
  1030.         for pkgname, vstring in packages:
  1031.             if vstring.find('current') == -1:
  1032.                 continue
  1033.             try:
  1034.                 versions = pyversions.requested_versions(vstring, version_only=True)
  1035.             except ValueError:
  1036.                 self.error("package %s is not ready to be updated for %s"
  1037.                            % (pkgname, self.runtime.name))
  1038.                 continue
  1039.             pkg = DebPackage('package', pkgname)
  1040.             self.needed_packages.append(pkg)
  1041.         return self.errors_occured
  1042.  
  1043.     def run(self, global_options):
  1044.         logging.info('\tupdate default: update %d packages for %s'
  1045.                      % (len(self.needed_packages), self.rtname))
  1046.         runtimes = get_installed_runtimes()
  1047.         default_rt = get_default_runtime()
  1048.         bc_option = get_debian_config().get('DEFAULT', 'byte-compile')
  1049.         try:
  1050.             for pkg in self.needed_packages:
  1051.                 pkg.read_version_info()
  1052.                 pkg.set_default_runtime_from_version_info()
  1053.                 if pkg.shared_files or pkg.private_files:
  1054.                     pkg.update_bytecode_files(runtimes, default_rt, bc_option)
  1055.         except PyCentralError, msg:
  1056.             self.error(msg)
  1057.  
  1058. register_action(ActionUpdateDefault)
  1059.  
  1060.  
  1061. class ActionShowDefault(Action):
  1062.     name = 'showdefault'
  1063.     help = 'Show default python version number'
  1064.  
  1065.     def check_args(self, global_options):
  1066.         if len(self.args) != 0:
  1067.             self._option_parser.print_help()
  1068.             sys.exit(1)
  1069.         return self.errors_occured
  1070.  
  1071.     def run(self, global_options):
  1072.         print pyversions.default_version(version_only=True)
  1073.         sys.stderr.write("pycentral showdefault is deprecated, use `pyversions -vd'\n")
  1074.  
  1075. register_action(ActionShowDefault)
  1076.  
  1077.  
  1078. class ActionShowVersions(Action):
  1079.     name = 'showversions'
  1080.     help = 'Show version numbers of supported python versions'
  1081.  
  1082.     def check_args(self, global_options):
  1083.         if len(self.args) != 0:
  1084.             self._option_parser.print_help()
  1085.             sys.exit(1)
  1086.  
  1087.         return self.errors_occured
  1088.  
  1089.     def run(self, global_options):
  1090.         supported = pyversions.supported_versions()
  1091.         versions = [d[6:] for d in supported if re.match(r'python\d\.\d', d)]
  1092.         print ' '.join(versions)
  1093.         sys.stderr.write("pycentral showversions is deprecated, use `pyversions -vs'\n")
  1094.  
  1095. register_action(ActionShowVersions)
  1096.  
  1097. class ActionShowSupported(Action):
  1098.     name = 'showsupported'
  1099.     help = 'Show the supported python versions'
  1100.  
  1101.     def check_args(self, global_options):
  1102.         if len(self.args) != 0:
  1103.             self._option_parser.print_help()
  1104.             sys.exit(1)
  1105.         return self.errors_occured
  1106.  
  1107.     def run(self, global_options):
  1108.         supported = pyversions.supported_versions()
  1109.         print ' '.join(supported)
  1110.         sys.stderr.write("pycentral showsupported is deprecated, use `pyversions -s'\n")
  1111.  
  1112. register_action(ActionShowSupported)
  1113.  
  1114.  
  1115. class ActionPyCentralDir(Action):
  1116.     name = 'pycentraldir'
  1117.     help = 'Show the pycentral installation directory for the package'
  1118.     usage = '<package>'
  1119.  
  1120.     def check_args(self, global_options):
  1121.         if len(self.args) != 1:
  1122.             self._option_parser.print_help()
  1123.             sys.exit(1)
  1124.         self.pkgname = self.args[0]
  1125.         return self.errors_occured
  1126.  
  1127.     def run(self, global_options):
  1128.         print os.path.join(shared_base, self.pkgname)
  1129.  
  1130. register_action(ActionPyCentralDir)
  1131.  
  1132. class ActionDebhelper(Action):
  1133.     name = 'debhelper'
  1134.     help = 'move files to pycentral location, variable substitutions'
  1135.     usage = '[-p|--provides] [--no-move] <package> [<package directory>]'
  1136.  
  1137.     def get_option_parser(self):
  1138.         if not self._option_parser:
  1139.             envvar = os.environ.get('DH_PYCENTRAL', '')
  1140.             substvars_default = 'no'
  1141.             if 'substvars=file' in envvar:
  1142.                 substvars_default = 'file'
  1143.             if 'substvars=stdout' in envvar:
  1144.                 substvars_default = 'stdout'
  1145.  
  1146.             p = OptionParser()
  1147.             p.add_option('-p', '--provides',
  1148.                          help="generate substitution for python:Provides",
  1149.                          default='add-provides' in envvar, action='store_true', dest='provides')
  1150.             p.add_option('--no-move', '--nomove',
  1151.                          help="do not move files to pycentral location",
  1152.                          default='no-move' in envvar or 'nomove' in envvar, action='store_true', dest='nomove')
  1153.             p.add_option('--stdout',
  1154.                          help="just print substitution variables to stdout",
  1155.                          default='stdout' in envvar, action='store_true', dest='stdout')
  1156.             p.add_option('--substvars',
  1157.                          help="where to print substitution vars (no, file, stdout)",
  1158.                          default=substvars_default, dest='substvars')
  1159.             p.add_option('--no-act', '--dry-run',
  1160.                          help="dry run",
  1161.                          default=('dry-run' in envvar) or ('no-act' in envvar),
  1162.                          action='store_true', dest='dryrun')
  1163.             self._option_parser = p
  1164.         return self._option_parser
  1165.  
  1166.     def check_args(self, global_options):
  1167.         if not len(self.args) in (1, 2):
  1168.             self._option_parser.print_help()
  1169.             sys.exit(1)
  1170.         if 'file' in self.options.substvars:
  1171.             self.options.substvars = 'file'
  1172.         if 'stdout' in self.options.substvars:
  1173.             self.options.substvars = 'stdout'
  1174.         return self.errors_occured
  1175.  
  1176.     def run(self, global_options):
  1177.         if len(self.args) < 2:
  1178.             pkgdir = 'debian/' + self.args[0]
  1179.         else:
  1180.             pkgdir = self.args[1]
  1181.         try:
  1182.             pkg = DebPackage('package', self.args[0], pkgdir=pkgdir,
  1183.                              parse_versions=self.options.substvars!='no')
  1184.             if not self.options.nomove:
  1185.                 pkg.move_files()
  1186.             pkg.read_pyfiles()
  1187.             if self.options.substvars!='no':
  1188.                 pkg.gen_substvars()
  1189.         except PyCentralVersionMissingError, msg:
  1190.             self.warn(msg)
  1191.             return
  1192.         except PyCentralError, msg:
  1193.             self.error(msg)
  1194.  
  1195.         out = None
  1196.         if self.options.stdout or self.options.substvars == 'stdout':
  1197.             out = sys.stdout
  1198.         elif self.options.substvars == 'file':
  1199.             out = file('debian/%s.substvars' % pkg.name, 'a+')
  1200.         if out:
  1201.             out.write('python:Versions=%s\n' % pkg.version_field)
  1202.             out.write('python:Depends=%s\n' % pkg.depends)
  1203.             out.write('python:Provides=%s\n' % pkg.provides)
  1204.  
  1205.  
  1206. register_action(ActionDebhelper)
  1207.  
  1208. # match a string with the list of available actions
  1209. def action_matches(action, actions):
  1210.     prog = re.compile('[^-]*?-'.join(action.split('-')))
  1211.     return [a for a in actions if prog.match(a)]
  1212.  
  1213. def usage(stream, msg=None):
  1214.     print >>stream, msg
  1215.     print >>stream, "use `%s help' for help on actions and arguments" % program
  1216.     print >>stream
  1217.     sys.exit(1)
  1218.  
  1219. # parse command line arguments
  1220. def parse_options(args):
  1221.     shortusage = 'usage: %s [<option> ...] <action> <pkgname>' % program
  1222.     parser = OptionParser(usage=shortusage)
  1223.     parser.disable_interspersed_args()
  1224.  
  1225.     # setup the parsers object
  1226.     parser.remove_option('-h')
  1227.     parser.add_option('-h', '--help',
  1228.                       help='help screen',
  1229.                       action='store_true', dest='help')
  1230.     parser.add_option('-v', '--verbose',
  1231.                       help='verbose mode',
  1232.                       action='store_true', dest='verbose')
  1233.  
  1234.     global_options, args = parser.parse_args()
  1235.     # Print the help screen and exit
  1236.     if len(args) == 0 or global_options.help:
  1237.         parser.print_help()
  1238.         print "\nactions:"
  1239.         action_names = known_actions.keys()
  1240.         action_names.sort()
  1241.         for n in action_names:
  1242.             print "  %-21s %s" % (n, known_actions[n].help)
  1243.         print ""
  1244.         sys.exit(1)
  1245.  
  1246.     # check if the specified action really exists
  1247.     action_name = args[0]
  1248.     del args[0]
  1249.     matching_actions = action_matches(action_name, known_actions.keys())
  1250.     if len(matching_actions) == 0:
  1251.         usage(sys.stderr, "unknown action `%s'" % action_name)
  1252.     elif len(matching_actions) > 1:
  1253.         usage(sys.stderr,
  1254.               "ambiguous action `%s', matching actions: %s"
  1255.               % (action_name, strlist(matching_actions)))
  1256.     else:
  1257.         action_name = matching_actions[0]
  1258.  
  1259.     # instantiate an object for the action and parse the remaining arguments
  1260.     action = known_actions[action_name]()
  1261.     action_options, action_names = action.parse_args(args)
  1262.  
  1263.     return global_options, action
  1264.  
  1265. class Logging:
  1266.     DEBUG, INFO, WARN, ERROR = range(4)
  1267.     def __init__(self, level=WARN):
  1268.         self.fd = None
  1269.         self.level = level
  1270.         try:
  1271.             self.fd = file('/var/log/pycentral.log', 'a+')
  1272.         except IOError:
  1273.             self.fd = None
  1274.     def msg(self, level, s):
  1275.         if level < self.level:
  1276.             return
  1277.         d = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
  1278.         if self.fd:
  1279.             self.fd.write('%s %s %s\n' % (d, level, s))
  1280.             self.fd.flush()
  1281.         sys.stdout.write('pycentral: %s\n' % (s))
  1282.         sys.stdout.flush()
  1283.     def info(self, s):
  1284.         self.msg(self.INFO, s)
  1285.     def warn(self, s):
  1286.         self.msg(self.WARN, s)
  1287.     def error(self, s):
  1288.         self.msg(self.ERROR, s)
  1289.         sys.stderr.write('%s\n' % s)
  1290.     def debug(self, s):
  1291.         self.msg(self.DEBUG, s)
  1292.  
  1293. def setup_logging(loglevel=Logging.WARN, verbose=False):
  1294.     levels = ['debug', 'info', 'warn', 'error']
  1295.     env_level = os.environ.get('PYCENTRAL', 'warn').lower()
  1296.     for i in range(len(levels)):
  1297.         if env_level.find(levels[i]) != -1:
  1298.             loglevel = i
  1299.     if verbose:
  1300.         loglevel = Logging.DEBUG
  1301.     global logging
  1302.     logging = Logging(loglevel)
  1303.  
  1304. def main():
  1305.     global_options, action = parse_options(sys.argv[1:])
  1306.  
  1307.     # setup logging stuff
  1308.     setup_logging(Logging.WARN, global_options.verbose)
  1309.     if action.name == 'debhelper' or action.name.startswith('show'):
  1310.         pass
  1311.     else:
  1312.         logging.debug('pycentral ' + ' '.join(sys.argv[1:]))
  1313.     
  1314.     # check the arguments according to the action called
  1315.     if action.check_args(global_options):
  1316.         sys.exit(1)
  1317.  
  1318.     # run the action and exit
  1319.     rv = action.run(global_options)
  1320.     sys.exit(rv)
  1321.  
  1322.  
  1323. # call the main routine
  1324. if __name__ == '__main__':
  1325.     main()
  1326.