diff --git a/.gitignore b/.gitignore index a291e32..b053649 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ Changelog.patool* /doc/README.md /testresults.txt /tests/__pycache__ +/_Patool_configdata.py diff --git a/Makefile b/Makefile index 8dc2858..f8863a8 100644 --- a/Makefile +++ b/Makefile @@ -105,7 +105,10 @@ clean: find . -name \*.pyo -delete rm -rf build dist -test: +localbuild: + $(PYTHON) setup.py build + +test: localbuild $(PYTHON) -m pytest $(PYTESTOPTS) $(TESTOPTS) $(TESTS) doc/$(LAPPNAME).txt: doc/$(LAPPNAME).1 @@ -135,5 +138,6 @@ changelog: # closes issues mentioned in the changelog entries. github-changelog $(DRYRUN) $(GITUSER) $(GITREPO) doc/changelog.txt -.PHONY: changelog update-copyright deb test clean count pyflakes check app +.PHONY: changelog update-copyright deb test clean count pyflakes check .PHONY: releasecheck release upload sign dist all tag register homepage +.PHONY: localbuild doccheck diff --git a/patool b/patool index 52102e9..340d948 100755 --- a/patool +++ b/patool @@ -22,8 +22,8 @@ import sys import argparse import pydoc import patoolib -from patoolib.util import log_error, log_internal_error, PatoolError, App - +from patoolib.util import log_error, log_internal_error, PatoolError +from patoolib.configuration import App def run_extract(args): """Extract files from archive(s).""" diff --git a/patoolib/configuration.py b/patoolib/configuration.py new file mode 100644 index 0000000..e32f2a1 --- /dev/null +++ b/patoolib/configuration.py @@ -0,0 +1,16 @@ +# Copyright (C) 2013 Bastian Kleineidam +""" +Define basic configuration data like version or application name. +""" +import _Patool_configdata as configdata + +Version = configdata.version +ReleaseDate = configdata.release_date +AppName = configdata.name +App = AppName+u" "+Version +Author = configdata.author +Maintainer = configdata.maintainer +Copyright = u"Copyright (C) 2004-2008 " + Author +Url = configdata.url +SupportUrl = Url + u"issues" +Email = configdata.author_email diff --git a/patoolib/util.py b/patoolib/util.py index 902ac38..a4ea99f 100644 --- a/patoolib/util.py +++ b/patoolib/util.py @@ -23,6 +23,7 @@ import mimetypes import tempfile import time import traceback +from . import configuration try: from shutil import which except ImportError: @@ -79,10 +80,6 @@ except ImportError: return None -AppName = "patool" -App = "%s 1.1" % AppName -SupportUrl = "https://github.com/wummel/patool/issues" - # internal MIME database mimedb = None @@ -471,7 +468,7 @@ at %(url)s and include at least the information below: Not disclosing some of the information below due to privacy reasons is ok. I will try to help you nonetheless, but you have to give me something I can work with ;) . -""" % dict(app=AppName, url=SupportUrl), file=out) +""" % dict(app=configuration.AppName, url=configuration.SupportUrl), file=out) if etype is None: etype = sys.exc_info()[0] if evalue is None: @@ -483,7 +480,7 @@ I can work with ;) . print_app_info(out=out) print_locale_info(out=out) print(os.linesep, - "******** %s internal error, over and out ********" % AppName, file=out) + "******** %s internal error, over and out ********" % configuration.AppName, file=out) def print_env_info(key, out=sys.stderr): """If given environment key is defined, print it out.""" @@ -501,7 +498,7 @@ def print_locale_info(out=sys.stderr): def print_app_info(out=sys.stderr): """Print system and application info (output defaults to stderr).""" print("System info:", file=out) - print(App, file=out) + print(configuration.App, file=out) print("Python %(version)s on %(platform)s" % {"version": sys.version, "platform": sys.platform}, file=out) stime = strtime(time.time()) diff --git a/setup.py b/setup.py index 9a7b44a..8b515db 100644 --- a/setup.py +++ b/setup.py @@ -22,6 +22,7 @@ import sys if not hasattr(sys, "version_info") or sys.version_info < (2, 7, 0, "final", 0): raise SystemExit("This program requires Python 2.7 or later.") import os +import re import shutil import glob import subprocess @@ -29,7 +30,6 @@ try: from cx_Freeze import setup, Executable except ImportError: from distutils.core import setup -from distutils.command.register import register try: # py2exe monkey-patches the distutils.core.Distribution class # So we need to import it before importing the Distribution class @@ -44,7 +44,10 @@ try: except ImportError: from distutils.core import Distribution executables = None +from distutils.command.register import register +from distutils.command.install_lib import install_lib from distutils import util +from distutils.file_util import write_file AppName = "Patool" AppVersion = "1.1" @@ -74,6 +77,36 @@ MSVCP90Version = '9.0.21022.8' MSVCP90Token = '1fc8b3b9a1e18e3b' +def normpath (path): + """Norm a path name to platform specific notation.""" + return os.path.normpath(path) + + +def cnormpath (path): + """Norm a path name to platform specific notation and make it absolute.""" + path = normpath(path) + if os.name == 'nt': + # replace slashes with backslashes + path = path.replace("/", "\\") + if not os.path.isabs(path): + path = normpath(os.path.join(sys.prefix, path)) + return path + + +release_ro = re.compile(r"\(released (.+)\)") +def get_release_date (): + """Parse and return relase date as string from doc/changelog.txt.""" + fname = os.path.join("doc", "changelog.txt") + release_date = "unknown" + with open(fname) as fd: + # the release date is on the first line + line = fd.readline() + mo = release_ro.search(line) + if mo: + release_date = mo.groups(1) + return release_date + + data_files = [] if os.name == 'nt': data_files.append(('share', ['doc/patool.txt'])) @@ -121,6 +154,63 @@ if 'py2exe' in sys.argv[1:]: add_msvc_files(data_files) +class MyInstallLib (install_lib, object): + """Custom library installation.""" + + def install (self): + """Install the generated config file.""" + outs = super(MyInstallLib, self).install() + infile = self.create_conf_file() + outfile = os.path.join(self.install_dir, os.path.basename(infile)) + self.copy_file(infile, outfile) + outs.append(outfile) + return outs + + def create_conf_file (self): + """Create configuration file.""" + cmd_obj = self.distribution.get_command_obj("install") + cmd_obj.ensure_finalized() + # we have to write a configuration file because we need the + # directory (and other stuff like author, url, ...) + # all paths are made absolute by cnormpath() + data = [] + for d in ['purelib', 'platlib', 'lib', 'headers', 'scripts', 'data']: + attr = 'install_%s' % d + if cmd_obj.root: + # cut off root path prefix + cutoff = len(cmd_obj.root) + # don't strip the path separator + if cmd_obj.root.endswith(os.sep): + cutoff -= 1 + val = getattr(cmd_obj, attr)[cutoff:] + else: + val = getattr(cmd_obj, attr) + if attr == 'install_data': + cdir = os.path.join(val, "share", "dosage") + data.append('config_dir = %r' % cnormpath(cdir)) + elif attr == 'install_lib': + if cmd_obj.root: + _drive, tail = os.path.splitdrive(val) + if tail.startswith(os.sep): + tail = tail[1:] + self.install_lib = os.path.join(cmd_obj.root, tail) + else: + self.install_lib = val + data.append("%s = %r" % (attr, cnormpath(val))) + self.distribution.create_conf_file(data, directory=self.install_lib) + return self.get_conf_output() + + def get_conf_output (self): + """Get filename for distribution configuration file.""" + return self.distribution.get_conf_filename(self.install_lib) + + def get_outputs (self): + """Add the generated config file to the list of outputs.""" + outs = super(MyInstallLib, self).get_outputs() + outs.append(self.get_conf_output()) + return outs + + class MyDistribution (Distribution, object): """Custom distribution class generating config file.""" @@ -129,6 +219,43 @@ class MyDistribution (Distribution, object): super(MyDistribution, self).__init__(attrs) self.console = ['patool'] + def run_commands (self): + """Generate config file and run commands.""" + cwd = os.getcwd() + data = [] + data.append('config_dir = %r' % os.path.join(cwd, "config")) + data.append("install_data = %r" % cwd) + data.append("install_scripts = %r" % cwd) + self.create_conf_file(data) + super(MyDistribution, self).run_commands() + + def get_conf_filename (self, directory): + """Get name for config file.""" + return os.path.join(directory, "_%s_configdata.py" % self.get_name()) + + def create_conf_file (self, data, directory=None): + """Create local config file from given data (list of lines) in + the directory (or current directory if not given).""" + data.insert(0, "# this file is automatically created by setup.py") + data.insert(0, "# -*- coding: iso-8859-1 -*-") + if directory is None: + directory = os.getcwd() + filename = self.get_conf_filename(directory) + # add metadata + metanames = ("name", "version", "author", "author_email", + "maintainer", "maintainer_email", "url", + "license", "description", "long_description", + "keywords", "platforms", "fullname", "contact", + "contact_email") + for name in metanames: + method = "get_" + name + val = getattr(self.metadata, method)() + data.append("%s = %r" % (name, val)) + data.append('release_date = "%s"' % get_release_date()) + # write the config file + util.execute(write_file, (filename, data), + "creating %s" % filename, self.verbose >= 1, self.dry_run) + class InnoScript: """Class to generate INNO script.""" @@ -343,6 +470,7 @@ installed. ], distclass = MyDistribution, cmdclass = { + 'install_lib': MyInstallLib, 'py2exe': MyPy2exe, 'register': MyRegister, },