PKF jGmfencepy/fencepy.conf.default# parameters for the requirements plugin [requirements] # if set to true, fencepy will read in 'requirements.txt' and install the contents # during environment setup enabled = true # parameters for the sublime plugin [sublime] # if set to true, fencepy will configure a sublime text project to use the # managed virtual environment for linting # project file location is configurable below enabled = true # if set, fencepy will look in this directory for a sublime-project file with the # same name as the project directory project-dir = # parameters for the ps1 plugin [ps1] # if set to true, fencepy will modify the activate scripts to include a # custom ps1 string enabled = true # parameters for the shellfuncs plugin [shellfuncs] # if set to true, fencepy will configure "aliases" for common functions in a shell-specific # way if an appropriate environment is detected (such as and oh-my-zsh plugin) enabled = true PKF jGT>rSKKfencepy/helpers.py""" fencepy.helpers Shared/generic functions """ import os import fnmatch import platform import psutil import subprocess import sys import funcy from contextlib import contextmanager def pseudo_merge_dict(dto, dfrom): """Recursively merge dict objects, overwriting any non-dict values""" # a quick type check if not (type(dto) == dict and type(dfrom) == dict): raise ValueError('non-dict passed into _psuedo_merge_dict') # do the work for k, v in dfrom.items(): if k not in dto.keys(): dto[k] = v # recurse on further dicts elif type(dfrom[k]) == dict: pseudo_merge_dict(dto[k], dfrom[k]) # everything else can just be overwritten else: dto[k] = dfrom[k] def locate_subdirs(pattern, root): """Get a list of all subdirectories contained underneath a root""" ret = [] for path, dirs, files in os.walk(os.path.abspath(root)): for subdir in fnmatch.filter(dirs, pattern): ret.append(os.path.join(path, subdir)) return ret def getoutputoserror(cmd): """Similar behavior to commands.getstatusoutput for python 3 and windows support""" p = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT) p.wait() output = p.communicate()[0].decode() if p.returncode: raise OSError(p.returncode, '{0}: {1}'.format(cmd, output)) return output def getpybindir(): """Get the appropriate subdirectory for binaries depending on system""" if platform.system() == 'Windows': return 'Scripts' return 'bin' def findpybin(name, start): """For windows compatibility""" # normalize to the root of the environment rootpath = os.path.dirname(start) if os.path.isfile(start) else start if os.path.basename(rootpath) in ('bin', 'Scripts'): rootpath = os.path.dirname(rootpath) # special case for Ubuntu (and other?) packaged python instances if rootpath == '/usr': binpath = os.path.join('/usr/local/bin', name) if os.path.exists(binpath): return binpath # we're in a linux-based virtual environment if 'bin' in os.listdir(rootpath): binpath = os.path.join(rootpath, 'bin', name) if os.path.exists(binpath): return binpath # we're in a windows virtual environment elif 'Scripts' in os.listdir(rootpath): binpath = os.path.join(rootpath, 'Scripts', '{0}.exe'.format(name)) if os.path.exists(binpath): return binpath # we could be in a brew environment on osx try: output = getoutputoserror('brew config') new_start = funcy.re_find(r'HOMEBREW_PREFIX:\s+([/\w]+)', output) if new_start != start: return findpybin(name, new_start) except OSError: pass raise IOError('could not find {0} relative to {1}'.format(name, start)) @contextmanager def redirected(out=sys.stdout, err=sys.stderr): """Temporarily redirect stdout and/or stderr""" saved = sys.stdout, sys.stderr sys.stdout, sys.stderr = out, err try: yield finally: sys.stdout, sys.stderr = saved def pyversionstr(): """Return a pyXY version string for the python version""" return 'py{0}'.format(''.join([str(x) for x in sys.version_info[:2]])) def str2bool(value): """Convert various acceptable string values into a bool""" if type(value) == str or (sys.version_info[0] == 2 and type(value) == unicode): if value.lower() in ('true', 't', 'yes', 'y', '1'): return True elif value.lower() in ('false', 'f', 'no', 'n', '0'): return False raise ValueError('{0} is not an acceptable boolean value'.format(value)) def get_shell(): """Get the name of the running shell according to psutil""" return psutil.Process(psutil.Process(os.getpid()).ppid()).name() PK *H6fencepy/plugins.py""" fencepy.plugins Plugins for use during environment creation """ import json import os import sys import textwrap from .helpers import pseudo_merge_dict, locate_subdirs, getoutputoserror, findpybin, getpybindir, pyversionstr # set up logging import logging l = logging.getLogger('') PLUGINS = ['requirements', 'sublime', 'ps1', 'shellfuncs'] def _install_requirements(args): """Install requirements out of requirements.txt, if it exists""" # break out various args for convenience vdir = args['--virtualenv-dir'] pdir = args['--dir'] # install requirements, if they exist rtxt = os.path.join(pdir, 'requirements.txt') if os.path.exists(rtxt): l.info('loading requirements from {0}'.format(rtxt)) try: output = getoutputoserror('{0} install -r {1}'.format(findpybin('pip', vdir), rtxt)) l.debug(''.ljust(40, '=')) l.debug(output) l.debug(''.ljust(40, '=')) except OSError as e: l.error(str(e)) return 1 l.info('finished installing requirements') return 0 def _install_sublime(args): """Set up sublime linter to use environment""" # break out various args for convenience vdir = args['--virtualenv-dir'] pdir = args['--dir'] sdir = args['plugins']['sublime']['project-dir'] # set up the sublime linter, if appropriate scfg = None # first, check the sublime project directory (if supplied) if sdir: guess = os.path.join(sdir, '{0}.sublime-project'.format(os.path.basename(pdir))) if os.path.exists(guess): scfg = guess # try the local directory if not scfg: for filename in os.listdir(pdir): if filename.endswith('.sublime-project'): scfg = os.path.join(pdir, filename) break # if we found a file, go to work if scfg: l.debug('configuring sublime linter in file {0}'.format(scfg)) cfg_dict = json.load(open(scfg)) dict_data = { 'SublimeLinter': { 'paths': {'linux': [os.path.join(vdir, getpybindir())]}, 'python_paths': {'linux': locate_subdirs('site-packages', vdir)} }, 'settings': {'python_interpreter': findpybin('python', vdir)} } pseudo_merge_dict(cfg_dict, dict_data) json.dump(cfg_dict, open(scfg, 'w'), indent=4, separators=(', ', ': '), sort_keys=True) l.info('successfully configured sublime linter') return 0 def _install_ps1(args): """Change the PS1 environment name in activate scripts""" ps1str = '-'.join((os.path.basename(args['--dir']), pyversionstr())) vdir = args['--virtualenv-dir'] mods = { 'activate': { 'from': '`basename \\"$VIRTUAL_ENV\\"`', 'to': ps1str }, 'activate.csh': { 'from': '`basename "$VIRTUAL_ENV"`', 'to': ps1str }, 'activate.fish': { 'from': '(basename "$VIRTUAL_ENV")', 'to': ps1str }, 'activate.bat': { 'from': '({0})'.format(os.path.basename(vdir)), 'to': '({0})'.format(ps1str) }, 'activate.ps1': { 'from': '$(split-path $env:VIRTUAL_ENV -leaf)', 'to': ps1str } } subdirs = ('bin', 'Scrips') for filename, trans in mods.items(): for subdir in subdirs: filepath = os.path.join(vdir, subdir, filename) if os.path.exists(filepath): text = open(filepath, 'r').read() if trans['from'] in text: # workaround for issue that throws UnicodeDecodeError in windows if filename == 'activate.ps1' and sys.getdefaultencoding() == 'ascii': text = text.decode('utf-8', 'ignore') text = text.replace(trans['from'], trans['to']) text = text.encode('ascii', 'ignore') else: text = text.replace(trans['from'], trans['to']) open(filepath, 'w').write(text) return 0 def _install_shellfuncs(args): """Set up some functions for zsh users""" # for oh-my-zsh users target_file = '~/.oh-my-zsh/custom/fencepy.zsh' if os.path.exists(os.path.expanduser(os.path.dirname(target_file))): l.info('(re)configuring oh-my-zsh functions') open(os.path.expanduser(target_file), 'w').write(textwrap.dedent('''fpadd() { fencepy create } fpnew() { fencepy create } fpsrc() { source `fencepy activate` } fpup() { fencepy update } fpdel() { fencepy erase } ''')) return 0 def install(plugin, args): """Wrapper around running a plugin install method directly""" # break out the conf and make sure we're enabled if not args['plugins'][plugin]['enabled']: return 0 return globals()['_install_{0}'.format(plugin)](args) PKp*HkG..fencepy/main.py""" fencepy.main Main CLI logic """ import docopt import os import shutil import sys import psutil from funcy import memoize from . import plugins from .helpers import getoutputoserror, findpybin, str2bool, pyversionstr, get_shell try: from ConfigParser import SafeConfigParser except ImportError: from configparser import SafeConfigParser from logging.handlers import RotatingFileHandler import logging as l __version__ = '0.7.0' DOCOPT = """ fencepy -- Standardized fencing off of python virtual environments on a per-project basis Usage: fencepy create [options] fencepy activate [options] fencepy update [options] fencepy erase [options] fencepy nuke [options] fencepy genconfig fencepy help fencepy version Options: -v --verbose Print/log more verbose output -q --quiet Silence all console output -s --silent Silence ALL output, including log output (except "activate") -C FILE --config-file=FILE Config file to use [default: ~/.fencepy/fencepy.conf] -P LIST --plugins=LIST Comma-separated list of plugins to apply (only "create") -S DIR --sublime-project-dir=DIR Search in DIR for .sublime-project files Path Overrides: -d DIR --dir=DIR Link the fenced environment to DIR instead of the CWD -D DIR --virtualenv-dir=DIR Use DIR as the root directory for the virtual environment -F DIR --fencepy-root=DIR Use DIR as the root of the fencepy tree [default: ~/.fencepy] -G --no-git Don't treat the working directory as a git repository """ @memoize def _get_parsed_config_file(filepath): """Return a SafeConfigParser loaded with the data from a config file at filepath""" ret = SafeConfigParser() ret.read(filepath) return ret @memoize def _get_default_config_file(): """Return the path to fencepy's default config file""" return os.path.join(os.path.dirname(os.path.abspath(__file__)), 'fencepy.conf.default') @memoize def _get_default_config_parsed(): """Return a SafeConfigParser loaded with the default config""" return _get_parsed_config_file(_get_default_config_file()) def _items_to_dict(items): """Return a dict of items taken from a section of a SafeConfigParser""" return dict((key, value) for key, value in items) def _fill_in_plugins_config(args, config=None): """Add a plugins config structure to args""" # fill in plugins config, starting with the default fencepy conf allplugins = True args['plugins'] = {} for plugin in plugins.PLUGINS: # the default config will have a complete list of necessary parameters -- no need to reinvent the wheel args['plugins'][plugin] = _items_to_dict(_get_default_config_parsed().items(plugin)) args['plugins'][plugin]['enabled'] = False # override with anything that comes from the passed in config file if config is not None and config.has_section(plugin): allplugins = False args['plugins'][plugin] = _items_to_dict(config.items(plugin)) args['plugins'][plugin]['enabled'] = str2bool(args['plugins'][plugin]['enabled']) # the config file can be overridden by the command line if args['--plugins']: args['plugins'][plugin]['enabled'] = True if plugin in args['--plugins'].split(',') else False # if no plugins are enabled based on input, then all plugins are enabled (default behavior) if args['plugins'][plugin]['enabled']: allplugins = False # default enabling of plugins if allplugins: for plugin in plugins.PLUGINS: args['plugins'][plugin]['enabled'] = True # add in any stuff directly from the command line if args['--sublime-project-dir']: args['plugins']['sublime']['project-dir'] = args['--sublime-project-dir'] return args @memoize def _get_virtualenv_root(fencepy_root): """Return the path to fencepy's virtualenv subdirectory""" return os.path.join(fencepy_root, 'virtualenvs') def _get_args(): """Do all parsing and processing for command-line arguments""" args = docopt.docopt(DOCOPT) # set up the root directory args['--fencepy-root'] = os.path.expanduser(args['--fencepy-root']) if not os.path.exists(args['--fencepy-root']): os.mkdir(args['--fencepy-root']) # set up logging if not args['--silent']: f = l.Formatter('%(asctime)s [%(levelname)s] %(module)s: %(message)s') h = RotatingFileHandler(os.path.join(args['--fencepy-root'], 'fencepy.log')) h.setFormatter(f) l.getLogger('').addHandler(h) if not (args['--silent'] or args['--quiet']): f = l.Formatter('[%(levelname)s] %(message)s') h = l.StreamHandler() h.setFormatter(f) l.getLogger('').addHandler(h) if args['--verbose']: l.getLogger('').setLevel(l.DEBUG) l.getLogger('sh').setLevel(l.INFO) else: l.getLogger('').setLevel(l.INFO) l.getLogger('sh').setLevel(l.ERROR) # we need to do some work to get the root directory we care about here if not args['--dir']: args['--dir'] = os.getcwd() if not args['--no-git']: try: output = getoutputoserror('git rev-parse --show-toplevel') args['--dir'] = output.strip() except OSError: l.debug("tried to handle {0} as a git repository but it isn't one".format(args['--dir'])) # reset the virtualenv root, if necessary if not args['--virtualenv-dir']: venv_root = _get_virtualenv_root(args['--fencepy-root']) # if we're one directory below the root, this logic needs to work differently parent = os.path.dirname(args['--dir']) if parent in ('/', os.path.splitdrive(parent)[0]): args['--virtualenv-dir'] = os.path.join(venv_root, os.path.basename(args['--dir'])) else: # need the realpath here because in some circumstances windows paths get passed # with a '/' and others see it coming in as a '\' tokens = os.path.dirname(os.path.realpath(args['--dir'])).split(os.path.sep) tokens.reverse() if tokens[-1] == '': tokens = tokens[:-1] prjpart = '.'.join([os.path.basename(args['--dir']), '.'.join([d[0] for d in tokens])]) args['--virtualenv-dir'] = os.path.join(venv_root, '-'.join((prjpart, pyversionstr()))) # only populate the parser if there's a valid file config = None readconf = True if args['--config-file'] == '~/.fencepy/fencepy.conf': args['--config-file'] = os.path.join(args['--fencepy-root'], 'fencepy.conf') if not os.path.exists(args['--config-file']): readconf = False elif not os.path.exists(args['--config-file']): raise IOError('specified config file {0} does not exist'.format(args['--config-file'])) if readconf: config = _get_parsed_config_file(args['--config-file']) # fill in the plugins config _fill_in_plugins_config(args, config) return args def _activate(args): """Print out the path to the appropriate activate script""" # break out the virtual environment directory for convenience vdir = args['--virtualenv-dir'] # make sure the directory exists if not os.path.exists(vdir): l.error('virtual environment does not exist, please execute fencepy create') return 1 # unix-based shells shell = get_shell() if shell == 'fish': apath = os.path.join(vdir, 'bin', 'activate.fish') elif shell == 'csh': apath = os.path.join(vdir, 'bin', 'activate.csh') elif shell.endswith('sh'): apath = os.path.join(vdir, 'bin', 'activate') # windows -- get-help will always raise the OSError, but we can still use it for this else: try: getoutputoserror('get-help') except OSError as e: if 'not recognized' in str(e): apath = os.path.join(vdir, 'Scripts', 'activate.bat') else: apath = os.path.join(vdir, 'Scripts', 'activate.ps1') # i don't think it's possible to set the calling environment, # so we'll just print the path to the script print(apath) return 0 def _plugins(args): """Execute the plugin routines required by command line arguments""" # plugins retval = 0 for plugin in plugins.PLUGINS: if plugins.install(plugin, args) == 1: retval = 1 return retval def _create(args): """Create a virtualenv for the current project""" # break out various args for convenience vdir = args['--virtualenv-dir'] pdir = args['--dir'] # p for project # make sure the directory doesn't already exist if os.path.exists(vdir): l.error('virtual environment already exists, quitting') return 1 # also make sure the project dir does exist if not os.path.exists(pdir): l.error('{0} does not exist, quitting'.format(pdir)) return 1 # make sure the virtualenv root exists if not os.path.exists(os.path.dirname(vdir)): os.makedirs(os.path.dirname(vdir)) # go ahead and create the environment virtualenv = findpybin('virtualenv', sys.executable) try: l.info('creating {0}'.format(args['--virtualenv-dir'])) output = getoutputoserror('{0} -p {1} {2}'.format(virtualenv, sys.executable, vdir)) l.debug(''.ljust(40, '=')) l.debug(output) l.debug(''.ljust(40, '=')) except OSError as e: l.error(str(e)) return 1 # finish up with the plugins l.info('using plugins: {0}'.format(', '.join([x for x in plugins.PLUGINS if args['plugins'][x]['enabled']]))) return _plugins(args) def _update(args): """Just run the plugins again""" return _plugins(args) def _erase(args): """Remove the virtualenv associated with this project""" # break out various args for convenience vdir = args['--virtualenv-dir'] # make sure the directory exists if not os.path.exists(vdir): l.error('virtual environment does not exist, quitting') return 1 # go ahead and create the environment shutil.rmtree(vdir) l.info('environment erased successfully') return 0 def _nuke(args): """Remove ALL fencepy virtualenvs""" # make sure the user really wants to do this answer = input('You are about to blow away everything fencepy owns. Are you sure? [y/N]') if answer.lower() not in ['y', 'yes']: print('Quitting') return 0 # blow it away venv_root = _get_virtualenv_root(args['--fencepy-root']) if os.path.exists(venv_root): shutil.rmtree(venv_root) return 0 def _genconfig(args): """Generate a default config file in the fencepy root directory""" fdir = args['--fencepy-root'] cfile = os.path.join(fdir, 'fencepy.conf') if os.path.exists(cfile): l.info('backing up {0}'.format(cfile)) i = 0 while True: bak = '{0}.bak.{1}'.format(cfile, i) if not os.path.exists(bak): shutil.move(cfile, bak) break l.info('generating {0}'.format(cfile)) shutil.copy(_get_default_config_file(), cfile) return 0 def fence(): """Main entry point""" args = _get_args() # override default help functionality if args['help']: print(DOCOPT) return 0 elif args['version']: print('{0} v{1} [{2}]'.format( os.path.abspath(psutil.Process(os.getpid()).cmdline()[1]), __version__, pyversionstr() )) return 0 # do a main action for mode in ['activate', 'create', 'update', 'erase', 'nuke', 'genconfig']: if args[mode]: l.debug('{0}ing environment with args: {1}'.format(mode[:-1], args)) return globals()['_{0}'.format(mode)](args) PKF jG}Ңfencepy/__init__.py""" fencepy Create a virtual environment tied to a particular directory and shortcuts to its activation. Includes special processing if the directory is part of a git repository. Also includes convenience configuration for users of sublime text """ from .main import fence PK$*H^- 'fencepy-0.7.0.dist-info/DESCRIPTION.rstUNKNOWN PK$*HdGG(fencepy-0.7.0.dist-info/entry_points.txt[console_scripts] fencepy = fencepy:fence fencepy-2.7 = fencepy:fence PK$*HZ%fencepy-0.7.0.dist-info/metadata.json{"classifiers": ["Development Status :: 4 - Beta", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Topic :: Software Development"], "download_url": "https://github.com/ajk8/fencepy/tarball/0.7.0", "extensions": {"python.commands": {"wrap_console": {"fencepy": "fencepy:fence", "fencepy-2.7": "fencepy:fence"}}, "python.details": {"contacts": [{"email": "kaufman.blue@gmail.com", "name": "Adam Kaufman", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/ajk8/fencepy"}}, "python.exports": {"console_scripts": {"fencepy": "fencepy:fence", "fencepy-2.7": "fencepy:fence"}}}, "extras": [], "generator": "bdist_wheel (0.26.0)", "keywords": ["virtualenv", "development"], "license": "MIT", "metadata_version": "2.0", "name": "fencepy", "run_requires": [{"requires": ["docopt (>=0.6.2)", "funcy (>=1.5)", "psutil (>=2.2.1)", "virtualenv (>=12.0.7)"]}], "summary": "Standardized fencing off of python virtual environments on a per-project basis", "version": "0.7.0"}PK$*H`2e%fencepy-0.7.0.dist-info/top_level.txtfencepy PK$*H''\\fencepy-0.7.0.dist-info/WHEELWheel-Version: 1.0 Generator: bdist_wheel (0.26.0) Root-Is-Purelib: true Tag: py2-none-any PK$*Hҩ fencepy-0.7.0.dist-info/METADATAMetadata-Version: 2.0 Name: fencepy Version: 0.7.0 Summary: Standardized fencing off of python virtual environments on a per-project basis Home-page: https://github.com/ajk8/fencepy Author: Adam Kaufman Author-email: kaufman.blue@gmail.com License: MIT Download-URL: https://github.com/ajk8/fencepy/tarball/0.7.0 Keywords: virtualenv development Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Topic :: Software Development Requires-Dist: docopt (>=0.6.2) Requires-Dist: funcy (>=1.5) Requires-Dist: psutil (>=2.2.1) Requires-Dist: virtualenv (>=12.0.7) UNKNOWN PK$*H\^ fencepy-0.7.0.dist-info/RECORDfencepy/__init__.py,sha256=3b95PR3EDyzofLIIfid3mRW9u8Tw0oVk8gmpwH-ltGU,276 fencepy/fencepy.conf.default,sha256=P69E4uh5zzCHuJpZoe9NHaVMNfxEZNeXGmFpwKumup8,932 fencepy/helpers.py,sha256=iIFrbSIDVDiOJ8gQTQXiQSte5ALFf-P7ZdWtp5hja_Q,3915 fencepy/main.py,sha256=x1h5kRdq0UnVmeJnMuvRBPPFMULHDtXNHbmEEBIpyQU,12012 fencepy/plugins.py,sha256=p4D_Qi1TFgNwH6WOIjuZIl_DwLT59nvtdht2U6SKOeE,5266 fencepy-0.7.0.dist-info/DESCRIPTION.rst,sha256=OCTuuN6LcWulhHS3d5rfjdsQtW22n7HENFRh6jC6ego,10 fencepy-0.7.0.dist-info/METADATA,sha256=O2q1Jzug8T2Q5IJwosh55a3n0Jg875AbqaJKxlCwvgA,937 fencepy-0.7.0.dist-info/RECORD,, fencepy-0.7.0.dist-info/WHEEL,sha256=JTb7YztR8fkPg6aSjc571Q4eiVHCwmUDlX8PhuuqIIE,92 fencepy-0.7.0.dist-info/entry_points.txt,sha256=IJwlmnHZPIr1O9rZHC8eGmZBPwmpbjzwyhoG72q3FzA,71 fencepy-0.7.0.dist-info/metadata.json,sha256=PKNL0s6TnNsbO1U00w47pP2_DhiZX5XBj5zWMtnxyRA,1264 fencepy-0.7.0.dist-info/top_level.txt,sha256=Jk4K9SZrxfE7yzN441LOO4cHLQUXDbUu_EX5pIXtojg,8 PKF jGmfencepy/fencepy.conf.defaultPKF jGT>rSKKfencepy/helpers.pyPK *H6Yfencepy/plugins.pyPKp*HkG..(fencepy/main.pyPKF jG}Ң4Wfencepy/__init__.pyPK$*H^- 'yXfencepy-0.7.0.dist-info/DESCRIPTION.rstPK$*HdGG(Xfencepy-0.7.0.dist-info/entry_points.txtPK$*HZ%UYfencepy-0.7.0.dist-info/metadata.jsonPK$*H`2e%^fencepy-0.7.0.dist-info/top_level.txtPK$*H''\\^fencepy-0.7.0.dist-info/WHEELPK$*Hҩ j_fencepy-0.7.0.dist-info/METADATAPK$*H\^ Qcfencepy-0.7.0.dist-info/RECORDPK ~Zg