├── .gitignore ├── AUTHORS ├── CHANGES ├── LICENSE ├── MANIFEST.in ├── README.rst ├── distribute_setup.py ├── docs ├── Makefile ├── _themes │ ├── nature2 │ │ ├── layout.html │ │ ├── static │ │ │ └── nature2.css │ │ └── theme.conf │ └── pyg │ │ ├── static │ │ └── pyg.css_t │ │ └── theme.conf ├── changes.rst ├── cmdline.rst ├── cmdline │ ├── inst_uninst.rst │ ├── minor_cmd.rst │ ├── req_bund.rst │ └── upgr_down_shell.rst ├── conf.py ├── config_files.rst ├── features.rst ├── index.rst ├── make.bat └── quickstart.rst ├── get-pyg.py ├── pyg ├── __init__.py ├── core.py ├── freeze.py ├── inst.py ├── locations.py ├── log.py ├── pack.py ├── parser │ ├── __init__.py │ ├── _opts.py │ ├── formatter.py │ ├── parser.py │ └── shell.py ├── req.py ├── scripts.py ├── utils.py ├── vcs │ ├── __init__.py │ ├── base.py │ ├── gist.py │ └── vcs.py └── web.py ├── setup.py └── tests ├── README.txt ├── features ├── check.feature ├── help.feature ├── install.feature ├── remove.feature ├── search.feature ├── steps.py └── test_errors.feature ├── pypi_cache_server.py ├── run.py └── test26.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.komodoproject 3 | *~ 4 | *.egg 5 | *.tar.gz 6 | docs/_build 7 | build/ 8 | dist/ 9 | pyg.egg-info/ 10 | temp/ 11 | .cache/ 12 | *.nja 13 | .ninjaproject 14 | .ropeproject 15 | tests/.cache -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Michele Lacchia (aka rubik, python, Python, Pythoner, x-reynik-x) 2 | Fabien Devaux (aka fdev31) -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | CHANGELOG 2 | ========= 3 | 4 | 1.0 (planning) 5 | -------------- 6 | 7 | + #69: Write tests. We haven't chosen the test framework to use yet. 8 | + #70: Upgrade :class:`pyg.req.Requirement` to honor setuptools extras. 9 | 10 | 0.8 (in development) 11 | -------------------- 12 | 13 | Bugs fixed 14 | ++++++++++ 15 | 16 | Currently none. 17 | 18 | Features added 19 | ++++++++++++++ 20 | 21 | + #79: Allow requirements with multiple version specification: ``pyg==0.4,==0.8``. 22 | + #80: Add a new method: :meth:`pyg.log.Logger.ask`. 23 | + #82: Allow comments in Pyg's configuration files. 24 | + #83: Added support for environment variables like :envvar:`PYG_CONFIG_FILE` and :envvar:`PYG_HOME`. 25 | + #96: Added an ``argv=`` keyword argument to :func:`pyg.main` function. 26 | 27 | TODO 28 | ++++ 29 | 30 | + #78: Allow installing packages from *dev* version: ``pyg install "pyg==dev"``. 31 | + #81: Read :file:`~/.pypirc` file. 32 | + #95: Add TAB autocompletion for Unix shells. 33 | + #97: Optimize uninstaller file deletion. 34 | 35 | 0.7.1 (July 25, 2011) 36 | --------------------- 37 | 38 | + Fixed a serious bug in the :command:`pack` command. 39 | 40 | 0.7 (July 16, 2011) 41 | ------------------- 42 | 43 | Bugs fixed 44 | ++++++++++ 45 | 46 | + #59: Solved :class:`pyg.web.LinkFinder` issue once and for all. 47 | + #62: ``pyg bundle`` failed when it did not find links on PyPI. Thanks to Fabien Devaux. 48 | + #63: Pyg could not install packages which use distutils instead of setuptools. Thanks to Fabien Devaux. 49 | + #64: Fixed ``pyg install --user``. 50 | + #66: ``pyg install -d {dir}`` could be ineffective. 51 | + #73: Sometimes the :command:`search` did not find anything (even if the package really exists). 52 | + #74: Fixed VCS install error. 53 | + #94: Error when bundling same packages. 54 | 55 | Features added 56 | ++++++++++++++ 57 | 58 | + #57: Created an installer, like Pip's ``get-pip.py``. You can grab it here: https://raw.github.com/rubik/pyg/master/get-pyg.py 59 | + #58: Added a ``pygx.y`` program (where ``x.y`` is Python current version), in addition to ``pyg``. 60 | + #68: Now you can install local directories in development mode with the :option:`install -e` option. 61 | + #72: Default argparse help is incomplete: wrote Pyg's own HelpFormatter. 62 | + #72b: Added colored output. 63 | + #76: Allow installing eggs which requires a different Python installation (added an :option:`install --force-egg-install` option). 64 | + #77: Improved uninstaller's file-detection. 65 | + #90: Now it is possible to create Packs: see :ref:`packs`. 66 | + #91: Replaced :command:`pyg freeze` with :command:`pyg site`. 67 | + #92: Now it is possible to bundle local packages. 68 | 69 | 0.6 (May 15, 2011) 70 | ------------------ 71 | 72 | + #40: Now Pyg can install packages from URL even if the URL does not end with a file-extension. 73 | + #44: Added a :option:`install --ignore` option. 74 | + #45: Replaced :attr:`pkgtools.WorkingSet` with :attr:`pkg_resources.working_set` in :class:`pyg.inst.Updater`. 75 | + #46: Added two new global command-line option: :option:`-d, --debug` and :option:`--verbose`. 76 | + #47: The Updater is now faster and searches links on ``http://pypi.python.org/simple`` too. 77 | + #48: Implement a special uninstallation system for :class:`pyg.inst.Updater`. 78 | + #53: Show download progress. 79 | + #55: You can install packages from Github Gists! 80 | 81 | 0.5.2 (May 05, 2011) 82 | -------------------- 83 | 84 | + Fixed package installation from VCS. 85 | 86 | 0.5.1 (May 05, 2011) 87 | -------------------- 88 | 89 | + Fixed :file:`setup.py`: added the ``zip-safe`` flag. 90 | 91 | 0.5 (May 05, 2011) 92 | -------------------- 93 | 94 | + #29: Create bundles from requirements files. 95 | + #30: Keep track of why requirements are needed. 96 | + #31: Replace :command:`uninstall` and :command:`rm` with a new :command:`remove` command. 97 | + #32: Follow links in :file:`dependency_links.txt` file. 98 | + #33: Fix option :option:`--index-url`. 99 | + #34: ``pyg remove yourself``. 100 | + #36: Add ``virtualenv`` support. 101 | + #38: Add :option:`-A, --upgrade-all` option for the :command:`install` command. 102 | + #39: Add :option:`-e, --exclude` option for the :command:`bundle` command. 103 | 104 | 0.4.1 (May 01, 2011) 105 | -------------------- 106 | 107 | + Fixed an issue with the :mod:`subprocess` module. 108 | 109 | 0.4 (May 01, 2011) 110 | -------------------- 111 | 112 | + #19: Added :class:`pyg.inst.Bundler`: now Pyg can create bundles! 113 | + #20: Installation from binaries (on Windows). 114 | + #22: Support a config file somewhere. 115 | + #25: Link following: when a package does not have any file on PyPI, Pyg have to follow links (e.g. package's home page, etc.) to find download links. 116 | + #26: Added Pyg Shell. 117 | 118 | 0.3.2 (Apr 29, 2011) 119 | -------------------- 120 | 121 | + Fixed :file:`setup.py`: didn't create Eggs properly. 122 | 123 | 0.3.1 (Apr 22, 2011) 124 | -------------------- 125 | 126 | + Fixed :file:`setup.py`: Setuptools didn't save requirements correctly. 127 | 128 | 0.3 (Apr 18, 2011) 129 | ------------------ 130 | 131 | + #9: Added the :command:`update` command. 132 | + #11: Added VCS support. 133 | + #12: Command-line options in requirement file are allowed. 134 | + #16: Added the :option:`--no-scripts` and :option:`--no-data` options to the `install` command. 135 | + #17: Added the possibility to install packages from directories. 136 | + #23: Comments in requirement file are allowed. 137 | + Added :class:`pyg.inst.Updater`. 138 | + Added a new file for the command-line options parser. 139 | 140 | 0.1.2 (Mar 26, 2011) 141 | -------------------- 142 | 143 | + #6: Added a :command:`download` command. 144 | + Added several options to the command-line parser. 145 | + Fixed some strange behavior of :meth:`pyg.req.Requirement.install`. 146 | 147 | 0.1.1 148 | ----- 149 | 150 | + #2: :class:`pyg.inst.Installer` now download dependencies. 151 | + #4: Make :class:`pyg.types.Egg` installing entry points. 152 | + #5: Fixed :class:`pyg.inst.Uninstaller`. 153 | 154 | 0.1 (Mar 24, 2011) 155 | ------------------ 156 | 157 | + First release. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2011 by Michele Lacchia 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include AUTHORS 3 | include CHANGES 4 | include LICENSE 5 | include TODO 6 | 7 | recursive-include pyg * 8 | prune pyg/__pycache__ 9 | 10 | recursive-include docs * 11 | prune docs/_build 12 | 13 | recursive-include tests * 14 | prune tests/.cache 15 | 16 | prune pyg/*.pyc 17 | prune pyg/vcs/*.pyc 18 | prune pyg/parser/*.pyc -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | WARNING - Important notice 2 | ========================== 3 | 4 | This project is now **DEAD**. 5 | It was born with the intent to provide a better (IMHO) alternative to Pip. 6 | Since now Pip has got a lot of great improvements, this is not needed anymore. 7 | 8 | So the advice here is to use Pip instead. Pyg has not been maintained for over 9 | 2 years now. It may well be broken in many places. The code is old and would 10 | need a refactoring, but there is no need anymore. 11 | 12 | 13 | Pyg 14 | === 15 | 16 | Pyg is a Python Package Manager that is meant to be an alternative to easy_install. 17 | 18 | Installation 19 | ----------- 20 | 21 | To install Pyg it takes only one simple command:: 22 | 23 | $ pip install pyg 24 | 25 | or if you must:: 26 | 27 | $ easy_install pyg 28 | 29 | And then you should no more need them! 30 | 31 | Installing from source 32 | ---------------------- 33 | 34 | Also, you can download the source and after unpacking the archive:: 35 | 36 | $ python setup.py install 37 | 38 | 39 | **Note:** You may need root privileges to install. 40 | 41 | Documentation 42 | ------------ 43 | 44 | If you need further information you can check the documentation at: http://pyg-installer.co.nr 45 | 46 | Building the documentation 47 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 48 | 49 | You can build the documentation locally. In order to build the html documentation you need to install `Sphinx`_. Simply run:: 50 | 51 | $ pyg install sphinx 52 | 53 | Again, you may need root privileges. 54 | Now you can build the docs. If you haven't already downloaded the source download it and open your terminal:: 55 | 56 | $ cd docs 57 | $ make html 58 | 59 | The docs are now in ``_build/html/`` 60 | 61 | 62 | .. _Sphinx: http://sphinx.pocoo.org 63 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | 15 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest 16 | 17 | help: 18 | @echo "Please use \`make ' where is one of" 19 | @echo " html to make standalone HTML files" 20 | @echo " dirhtml to make HTML files named index.html in directories" 21 | @echo " singlehtml to make a single large HTML file" 22 | @echo " pickle to make pickle files" 23 | @echo " json to make JSON files" 24 | @echo " htmlhelp to make HTML files and a HTML help project" 25 | @echo " qthelp to make HTML files and a qthelp project" 26 | @echo " devhelp to make HTML files and a Devhelp project" 27 | @echo " epub to make an epub" 28 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 29 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 30 | @echo " text to make text files" 31 | @echo " man to make manual pages" 32 | @echo " changes to make an overview of all changed/added/deprecated items" 33 | @echo " linkcheck to check all external links for integrity" 34 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 35 | 36 | clean: 37 | -rm -rf $(BUILDDIR)/* 38 | 39 | html: 40 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 41 | @echo 42 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 43 | 44 | dirhtml: 45 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 48 | 49 | singlehtml: 50 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 51 | @echo 52 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 53 | 54 | pickle: 55 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 56 | @echo 57 | @echo "Build finished; now you can process the pickle files." 58 | 59 | json: 60 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 61 | @echo 62 | @echo "Build finished; now you can process the JSON files." 63 | 64 | htmlhelp: 65 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 66 | @echo 67 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 68 | ".hhp project file in $(BUILDDIR)/htmlhelp." 69 | 70 | qthelp: 71 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 72 | @echo 73 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 74 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 75 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pyg.qhcp" 76 | @echo "To view the help file:" 77 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pyg.qhc" 78 | 79 | devhelp: 80 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 81 | @echo 82 | @echo "Build finished." 83 | @echo "To view the help file:" 84 | @echo "# mkdir -p $$HOME/.local/share/devhelp/pyg" 85 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pyg" 86 | @echo "# devhelp" 87 | 88 | epub: 89 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 90 | @echo 91 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 92 | 93 | latex: 94 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 95 | @echo 96 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 97 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 98 | "(use \`make latexpdf' here to do that automatically)." 99 | 100 | latexpdf: 101 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 102 | @echo "Running LaTeX files through pdflatex..." 103 | make -C $(BUILDDIR)/latex all-pdf 104 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 105 | 106 | text: 107 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 108 | @echo 109 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 110 | 111 | man: 112 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 113 | @echo 114 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 115 | 116 | changes: 117 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 118 | @echo 119 | @echo "The overview file is in $(BUILDDIR)/changes." 120 | 121 | linkcheck: 122 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 123 | @echo 124 | @echo "Link check complete; look for any errors in the above output " \ 125 | "or in $(BUILDDIR)/linkcheck/output.txt." 126 | 127 | doctest: 128 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 129 | @echo "Testing of doctests in the sources finished, look at the " \ 130 | "results in $(BUILDDIR)/doctest/output.txt." 131 | -------------------------------------------------------------------------------- /docs/_themes/nature2/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "!layout.html" %} 2 | 3 | {%- block extrahead %} 4 | {{ super() }} 5 | 10 | {% endblock %} 11 | 12 | {% block footer %} 13 | {{ super() }} 14 | 45 | {% endblock %} -------------------------------------------------------------------------------- /docs/_themes/nature2/static/nature2.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Sphinx stylesheet -- default theme 3 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | * 5 | * Modified by Michele Lacchia from Distribute theme. 6 | */ 7 | 8 | @import url("basic.css"); 9 | 10 | /* -- page layout ----------------------------------------------------------- */ 11 | 12 | body { 13 | font-family: Arial, sans-serif; 14 | font-size: 105%; 15 | background-color: #111111; 16 | color: #555555; 17 | margin: 0; 18 | padding: 0; 19 | } 20 | 21 | div.documentwrapper { 22 | float: left; 23 | width: 100%; 24 | } 25 | 26 | div.bodywrapper { 27 | margin: 0 0 0 300px; 28 | } 29 | 30 | hr{ 31 | border: 1px solid #B1B4B6; 32 | } 33 | 34 | div.document { 35 | background-color: #fafafa; 36 | } 37 | 38 | div.body { 39 | background-color: #ffffff; 40 | color: #3E4349; 41 | padding: 1em 30px 30px 30px; 42 | font-size: 0.9em; 43 | } 44 | 45 | div.footer { 46 | color: #555; 47 | width: 100%; 48 | padding: 13px 0; 49 | text-align: center; 50 | font-size: 75%; 51 | } 52 | 53 | div.footer a { 54 | color: #444444; 55 | } 56 | 57 | div.related { 58 | background-color: #6BA81E; 59 | line-height: 36px; 60 | color: #ffffff; 61 | text-shadow: 0px 1px 0 #444444; 62 | font-size: 1.1em; 63 | } 64 | 65 | div.related a { 66 | color: #E2F3CC; 67 | } 68 | 69 | div.related .right { 70 | font-size: 0.9em; 71 | } 72 | 73 | div.sphinxsidebar { 74 | font-size: 0.9em; 75 | line-height: 1.5em; 76 | width: 300px; 77 | } 78 | 79 | div.sphinxsidebarwrapper{ 80 | padding: 20px 0; 81 | } 82 | 83 | div.sphinxsidebar h3, 84 | div.sphinxsidebar h4 { 85 | font-family: Arial, sans-serif; 86 | color: #222222; 87 | font-size: 1.2em; 88 | font-weight: bold; 89 | margin: 0; 90 | padding: 5px 10px; 91 | text-shadow: 1px 1px 0 white 92 | } 93 | 94 | div.sphinxsidebar h3 a { 95 | color: #444444; 96 | } 97 | 98 | div.sphinxsidebar p { 99 | color: #888888; 100 | padding: 5px 20px; 101 | margin: 0.5em 0px; 102 | } 103 | 104 | div.sphinxsidebar p.topless { 105 | } 106 | 107 | div.sphinxsidebar ul { 108 | margin: 10px 10px 10px 20px; 109 | padding: 0; 110 | color: #000000; 111 | } 112 | 113 | div.sphinxsidebar a { 114 | color: #444444; 115 | } 116 | 117 | div.sphinxsidebar a:hover { 118 | color: #E32E00; 119 | } 120 | 121 | div.sphinxsidebar input { 122 | border: 1px solid #cccccc; 123 | font-family: sans-serif; 124 | font-size: 1.1em; 125 | padding: 0.15em 0.3em; 126 | } 127 | 128 | div.sphinxsidebar input[type=text]{ 129 | margin-left: 20px; 130 | } 131 | 132 | /* -- body styles ----------------------------------------------------------- */ 133 | 134 | a { 135 | color: #005B81; 136 | text-decoration: none; 137 | } 138 | 139 | a:hover { 140 | color: #E32E00; 141 | } 142 | 143 | div.body h1, 144 | div.body h2, 145 | div.body h3, 146 | div.body h4, 147 | div.body h5, 148 | div.body h6 { 149 | font-family: Arial, sans-serif; 150 | font-weight: normal; 151 | color: #212224; 152 | margin: 30px 0px 10px 0px; 153 | padding: 5px 0 5px 0px; 154 | text-shadow: 0px 1px 0 white; 155 | border-bottom: 1px solid #C8D5E3; 156 | } 157 | 158 | div.body h1 { margin-top: 0; font-size: 200%; } 159 | div.body h2 { font-size: 150%; } 160 | div.body h3 { font-size: 120%; } 161 | div.body h4 { font-size: 110%; } 162 | div.body h5 { font-size: 100%; } 163 | div.body h6 { font-size: 100%; } 164 | 165 | a.headerlink { 166 | color: #c60f0f; 167 | font-size: 0.8em; 168 | padding: 0 4px 0 4px; 169 | text-decoration: none; 170 | } 171 | 172 | a.headerlink:hover { 173 | background-color: #c60f0f; 174 | color: white; 175 | } 176 | 177 | div.body p, div.body dd, div.body li { 178 | line-height: 1.8em; 179 | } 180 | 181 | div.admonition p.admonition-title + p { 182 | display: inline; 183 | } 184 | 185 | div.highlight{ 186 | background-color: white; 187 | } 188 | 189 | div.note { 190 | background-color: #eeeeee; 191 | border: 1px solid #cccccc; 192 | } 193 | 194 | div.seealso { 195 | background-color: #ffffcc; 196 | border: 1px solid #ffff66; 197 | } 198 | 199 | div.topic { 200 | background-color: #fafafa; 201 | border-width: 0; 202 | } 203 | 204 | div.warning { 205 | background-color: #ffe4e4; 206 | border: 1px solid #ff6666; 207 | } 208 | 209 | p.admonition-title { 210 | display: inline; 211 | } 212 | 213 | p.admonition-title:after { 214 | content: ":"; 215 | } 216 | 217 | pre { 218 | padding: 10px; 219 | background-color: #fafafa; 220 | color: #222222; 221 | line-height: 1.5em; 222 | font-size: 1.1em; 223 | margin: 1.5em 0 1.5em 0; 224 | -webkit-box-shadow: 0px 0px 4px #d8d8d8; 225 | -moz-box-shadow: 0px 0px 4px #d8d8d8; 226 | box-shadow: 0px 0px 4px #d8d8d8; 227 | } 228 | 229 | tt { 230 | color: #222222; 231 | padding: 1px 2px; 232 | font-size: 1.2em; 233 | font-family: monospace; 234 | } 235 | 236 | #table-of-contents ul { 237 | padding-left: 2em; 238 | } -------------------------------------------------------------------------------- /docs/_themes/nature2/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = nature2.css -------------------------------------------------------------------------------- /docs/_themes/pyg/static/pyg.css_t: -------------------------------------------------------------------------------- 1 | /* 2 | * pyg.css_t 3 | * ~~~~~~~~~~~~~~ 4 | * 5 | * :copyright: Copyright 2010 by Michele Lacchia. 6 | * :license: MIT License. 7 | */ 8 | 9 | {% set page_width = '1100px' %} 10 | {% set sidebar_width = '285px' %} 11 | {% set font_family = "'Ubuntu', serif" %} 12 | {% set header_font_family = "'Ubuntu', " ~ font_family %} 13 | 14 | @import url("basic.css"); 15 | @import url(http://fonts.googleapis.com/css?family=Ubuntu); 16 | 17 | /* -- page layout ----------------------------------------------------------- */ 18 | 19 | body { 20 | font-family: {{ font_family }}; 21 | font-size: 1em; 22 | background-color: white; 23 | color: #000; 24 | margin: 0; 25 | padding: 0; 26 | } 27 | 28 | div.document { 29 | width: {{ page_width }}; 30 | margin: 30px auto 0 auto; 31 | } 32 | 33 | div.documentwrapper { 34 | float: left; 35 | width: 100%; 36 | } 37 | 38 | div.bodywrapper { 39 | margin: 0 0 0 {{ sidebar_width }}; 40 | } 41 | 42 | div.sphinxsidebar { 43 | width: {{ sidebar_width }}; 44 | } 45 | 46 | hr { 47 | border: 1px solid #B1B4B6; 48 | } 49 | 50 | div.body { 51 | background-color: #ffffff; 52 | color: #3E4349; 53 | padding: 0 30px 0 30px; 54 | } 55 | 56 | div.footer { 57 | width: {{ page_width }}; 58 | margin: 20px auto 30px auto; 59 | font-size: 14px; 60 | color: #888; 61 | text-align: right; 62 | } 63 | 64 | div.footer a { 65 | color: #888; 66 | } 67 | 68 | div.related { 69 | display: none; 70 | } 71 | 72 | 73 | /* -- sidebar ---------------------------------------------------------------- */ 74 | 75 | div.sphinxsidebar a { 76 | color: #444; 77 | text-decoration: none; 78 | border-bottom: 1px dotted #999; 79 | } 80 | 81 | div.sphinxsidebar a:hover { 82 | border-bottom: 1px solid #999; 83 | } 84 | 85 | div.sphinxsidebar { 86 | font-size: 90%; 87 | line-height: 1.5; 88 | } 89 | 90 | div.sphinxsidebarwrapper { 91 | padding: 18px 10px; 92 | } 93 | 94 | div.sphinxsidebarwrapper p.logo { 95 | padding: 0 0 20px 0; 96 | margin: 0; 97 | text-align: center; 98 | } 99 | 100 | div.sphinxsidebar h1, 101 | div.sphinxsidebar h2, 102 | div.sphinxsidebar h3, 103 | div.sphinxsidebar h4, 104 | div.sphinxsidebar h5, 105 | div.sphinxsidebar h6 { 106 | font-family: {{ font_family }}; 107 | font-weight: lighter; 108 | font-size: 2 109 | } 110 | 111 | div.sphinxsidebar h3, 112 | div.sphinxsidebar h4 { 113 | color: #444; 114 | font-size: 24px; 115 | margin: 0 0 5px 0; 116 | padding: 0; 117 | } 118 | 119 | div.sphinxsidebar h3 a { 120 | color: #444; 121 | font-family: {{ font_family }} 122 | } 123 | 124 | div.sphinxsidebar p.logo a, 125 | div.sphinxsidebar h3 a, 126 | div.sphinxsidebar p.logo a:hover, 127 | div.sphinxsidebar h3 a:hover { 128 | border: none; 129 | } 130 | 131 | div.sphinxsidebar p { 132 | color: #555; 133 | margin: 10px 0; 134 | } 135 | 136 | div.sphinxsidebar ul { 137 | margin: 10px 0; 138 | padding: 0; 139 | color: #000; 140 | } 141 | 142 | div.sphinxsidebar input { 143 | border: 1px solid #ccc; 144 | font-family: {{ font_family }}; 145 | font-size: 0.875em; 146 | } 147 | 148 | div.sphinxsidebar form.search input[name="q"] { 149 | width: 8.125em; 150 | } 151 | 152 | /* -- body styles ----------------------------------------------------------- */ 153 | 154 | div.highlight { 155 | font-size: 1.2em; 156 | background-color: white; 157 | } 158 | 159 | div.highlight-console { 160 | max-width: 95%; 161 | } 162 | 163 | div.highlight pre { 164 | background-color: #E5EDF1; 165 | max-width: 95%; 166 | border: 1px solid #C7ECB8; 167 | } 168 | 169 | div.body p { 170 | font-family: {{ font_family }}; 171 | background-color: transparent; 172 | } 173 | 174 | a:link { 175 | font-family: 'Georgia'; 176 | font-size: 105%; 177 | font-style: italic; 178 | color: {{ theme_link_color }}; 179 | background-color: transparent; 180 | text-decoration: underline; 181 | } 182 | 183 | a:visited { 184 | color: {{ theme_visited_link_color }} 185 | } 186 | 187 | a:hover { 188 | color: {{ theme_hover_link_color }}; 189 | } 190 | 191 | div.body h1, 192 | div.body h2, 193 | div.body h3, 194 | div.body h4, 195 | div.body h5, 196 | div.body h6 { 197 | font-family: {{ header_font_family }}; 198 | font-weight: normal; 199 | margin: 30px 0px 10px 0px; 200 | padding: 0; 201 | color: black; 202 | } 203 | 204 | div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } 205 | div.body h2 { font-size: 180%; } 206 | div.body h3 { font-size: 150%; } 207 | div.body h4 { font-size: 130%; } 208 | div.body h5 { font-size: 100%; } 209 | div.body h6 { font-size: 100%; } 210 | 211 | a.headerlink { 212 | color: #ddd; 213 | padding: 0 4px; 214 | text-decoration: none; 215 | } 216 | 217 | a.headerlink:hover { 218 | color: #444; 219 | background: #eaeaea; 220 | } 221 | 222 | div.body p, div.body dd, div.body li { 223 | line-height: 1.4em; 224 | } 225 | 226 | div.admonition { 227 | background: #fafafa; 228 | margin: 20px -30px; 229 | padding: 10px 30px; 230 | border-top: 1px solid #ccc; 231 | border-bottom: 1px solid #ccc; 232 | } 233 | 234 | div.admonition tt.xref, div.admonition a tt { 235 | border-bottom: 1px solid #fafafa; 236 | } 237 | 238 | dd div.admonition { 239 | margin-left: -60px; 240 | padding-left: 60px; 241 | } 242 | 243 | div.admonition p.admonition-title { 244 | font-family: {{ font_family }}; 245 | font-weight: normal; 246 | font-size: 24px; 247 | margin: 0 0 10px 0; 248 | padding: 0; 249 | line-height: 1; 250 | } 251 | 252 | div.admonition p.last { 253 | margin-bottom: 0; 254 | } 255 | 256 | dt:target, .highlight { 257 | background: #FAF3E8; 258 | } 259 | 260 | div.note { 261 | background-color: #EAEFF4; 262 | border: 1px solid #ccc; 263 | } 264 | 265 | div.seealso { 266 | background-color: #ffc; 267 | border: 1px solid #ff6; 268 | } 269 | 270 | div.topic { 271 | background-color: #eee; 272 | } 273 | 274 | p.admonition-title { 275 | display: inline; 276 | } 277 | 278 | p.admonition-title:after { 279 | content: ":"; 280 | } 281 | 282 | tt { 283 | font-family: {{ font_family }} 284 | font-size: 0.9em; 285 | } 286 | 287 | tt.descname, tt.descclassname { 288 | font-size: 0.95em; 289 | } 290 | 291 | tt.descname { 292 | padding-right: 0.08em; 293 | } 294 | 295 | img.screenshot { 296 | -moz-box-shadow: 2px 2px 4px #eee; 297 | -webkit-box-shadow: 2px 2px 4px #eee; 298 | box-shadow: 2px 2px 4px #eee; 299 | } 300 | 301 | table.docutils { 302 | border: 1px solid #888; 303 | -moz-box-shadow: 2px 2px 4px #eee; 304 | -webkit-box-shadow: 2px 2px 4px #eee; 305 | box-shadow: 2px 2px 4px #eee; 306 | } 307 | 308 | table.docutils td, table.docutils th { 309 | border: 1px solid #888; 310 | padding: 0.25em 0.7em; 311 | } 312 | 313 | table.field-list, table.footnote { 314 | border: none; 315 | -moz-box-shadow: none; 316 | -webkit-box-shadow: none; 317 | box-shadow: none; 318 | } 319 | 320 | table.footnote { 321 | margin: 15px 0; 322 | width: 100%; 323 | border: 1px solid #eee; 324 | background: #fdfdfd; 325 | font-size: 0.9em; 326 | } 327 | 328 | table.footnote + table.footnote { 329 | margin-top: -15px; 330 | border-top: none; 331 | } 332 | 333 | table.field-list th { 334 | padding: 0 0.8em 0 0; 335 | } 336 | 337 | table.field-list td { 338 | padding: 0; 339 | } 340 | 341 | table.footnote td.label { 342 | width: 0px; 343 | padding: 0.3em 0 0.3em 0.5em; 344 | } 345 | 346 | table.footnote td { 347 | padding: 0.3em 0.5em; 348 | } 349 | 350 | dl { 351 | margin: 0; 352 | padding: 0; 353 | } 354 | 355 | dl dd { 356 | margin-left: 30px; 357 | } 358 | 359 | blockquote { 360 | margin: 0 0 0 30px; 361 | padding: 0; 362 | } 363 | 364 | ul, ol { 365 | margin: 10px 0 10px 30px; 366 | padding: 0; 367 | } 368 | 369 | pre { 370 | background: #F0FFEB; 371 | padding: 7px 30px; 372 | margin: 15px -30px; 373 | line-height: 1.3em; 374 | font-family: {{ font_family }} 375 | font-size: 0.9em; 376 | } 377 | 378 | dl pre, blockquote pre, li pre { 379 | margin-left: -60px; 380 | padding-left: 60px; 381 | } 382 | 383 | dl dl pre { 384 | margin-left: -90px; 385 | padding-left: 90px; 386 | } 387 | 388 | tt { 389 | background-color: #E8EFF0; 390 | color: #222; 391 | /* padding: 1px 2px; */ 392 | } 393 | 394 | tt.xref, a tt { 395 | background-color: #E8EFF0; 396 | border-bottom: 1px solid white; 397 | } 398 | 399 | a.reference { 400 | text-decoration: none; 401 | border-bottom: 1px dotted #2BABC4; 402 | } 403 | 404 | a.reference:hover { 405 | border-bottom: 1px solid #2794AA; 406 | } 407 | 408 | a.footnote-reference { 409 | text-decoration: none; 410 | font-size: 0.7em; 411 | vertical-align: top; 412 | border-bottom: 1px dotted #004B6B; 413 | } 414 | 415 | a.footnote-reference:hover { 416 | border-bottom: 1px solid #6D4100; 417 | } 418 | 419 | a:hover tt { 420 | background: #EEE; 421 | } 422 | 423 | span.pre { 424 | font-size: 1.125em; 425 | } 426 | 427 | tt.descname { 428 | font-size: 1.3em; 429 | } 430 | 431 | tt.descclassname { 432 | font-size: 1.25em; 433 | font-style: italic; 434 | } -------------------------------------------------------------------------------- /docs/_themes/pyg/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = pyg.css 4 | 5 | [options] 6 | link_color = #dc3c01 7 | visited_link_color = #dc3c01 8 | hover_link_color = #dc3c01 -------------------------------------------------------------------------------- /docs/changes.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CHANGES -------------------------------------------------------------------------------- /docs/cmdline.rst: -------------------------------------------------------------------------------- 1 | Using Pyg from the command line 2 | =============================== 3 | 4 | Pyg should be used mostly from the command line, as it requires root's privileges to install and remove packages. 5 | 6 | On some systems (e.g. on Unix systems), you may need root privileges to execute some commands such as :command:`install`, :command:`uninstall`, or :command:`upgrade`. 7 | In this case you should use the :command:`sudo` command. 8 | 9 | Main commands: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | cmdline/inst_uninst.rst 15 | cmdline/req_bund.rst 16 | cmdline/upgr_down_shell.rst 17 | 18 | Minor commands: 19 | 20 | .. toctree:: 21 | :maxdepth: 2 22 | 23 | cmdline/minor_cmd.rst 24 | 25 | Other stuff: 26 | 27 | .. toctree:: 28 | :maxdepth: 2 29 | 30 | config_files.rst -------------------------------------------------------------------------------- /docs/cmdline/inst_uninst.rst: -------------------------------------------------------------------------------- 1 | Installing and removing packages 2 | ================================ 3 | 4 | Installing 5 | ---------- 6 | 7 | .. program:: install 8 | 9 | To install a package, simply run:: 10 | 11 | $ pyg install package 12 | 13 | *package* can be a number of things: 14 | 15 | * the name of the package you want to install (e.g. ``pyg`` or ``sphinx``) 16 | * a package URL (e.g. ``http://www.example.org/path/to/mypackage-0.1.tar.gz``) 17 | * a local file (like ``path/to/mypackage-0.42-py2.7.egg``) 18 | * a local directory containing a :file:`setup.py` file 19 | * a repository URL (like ``git+git@github.com/rubik/pyg``) 20 | * a gist URL (i.e. ``gist+928471``) 21 | 22 | Pyg supports these file-types: 23 | 24 | * :file:`.tar.gz` 25 | * :file:`.tgz` 26 | * :file:`.tar.bz2` 27 | * :file:`.zip` 28 | * :file:`.egg` 29 | * :file:`.exe` 30 | * :file:`.msi` 31 | * :file:`.pybundle` 32 | * :file:`.pyb` (an abbreviation of Pip's bundle files) 33 | 34 | .. option:: -e , --editable 35 | 36 | .. versionadded:: 0.3 37 | .. versionchanged:: 0.7 38 | Allow paths as arguments as well. 39 | 40 | Install a package in editable mode (``python setup.py develop``) from an online repository. Supported VCS are: 41 | 42 | * Git (prefix ``git+``) 43 | * Mercurial (prefix ``hg+``) 44 | * Bazaar (prefix ``bzr+``) 45 | * Subversion (prefix ``svn+``) 46 | 47 | The URL syntax is as follows:: 48 | 49 | #egg= 50 | 51 | All fields are required. The last part (``#egg=``) specifies the package name. 52 | 53 | You can run it with a path as well. Actually, this:: 54 | 55 | $ pyg install -e path/to/a/directory 56 | ... 57 | 58 | is equivalent to:: 59 | 60 | $ cd path/to/a/directory 61 | $ python setup.py develop 62 | 63 | .. option:: --no-script 64 | 65 | .. versionadded:: 0.3 66 | 67 | Do not install packages' scripts. 68 | 69 | .. option:: --no-data 70 | 71 | .. versionadded:: 0.3 72 | 73 | Do not install packages' data files. 74 | 75 | .. option:: -r , --req-file 76 | 77 | Install packages from the specified requirement file:: 78 | 79 | $ pyg install -r requirements.txt 80 | 81 | See also: :ref:`reqs` 82 | 83 | .. option:: -U, --upgrade 84 | 85 | .. versionadded:: 0.2 86 | 87 | If the package is already installed, install it again. 88 | For example, if you have installed ``pypol_ v0.4``:: 89 | 90 | $ pyg install pypol_==0.4 91 | Best match: pypol_==0.4 92 | Downloading pypol_ 93 | Checking md5 sum 94 | Running setup.py egg_info for pypol_ 95 | Running setup.py install for pypol_ 96 | pypol_ installed successfully 97 | 98 | Later you may want to re-install the package. Instead of running :command:`remove` and then :command:`install`, you can use the :option:`-U` option:: 99 | 100 | $ pyg install -U pypol_ 101 | Best match: pypol_==0.5 102 | Downloading pypol_ 103 | Checking md5 sum 104 | Installing pypol_ egg file 105 | pypol_ installed successfully 106 | 107 | This command **does not** upgrade dependencies (see :option:`install -A`). 108 | 109 | .. option:: -A, --upgrade-all 110 | 111 | .. versionadded:: 0.5 112 | 113 | Like, :option:`install --upgrade`, but upgrade dependencies too. 114 | 115 | .. option:: -n, --no-deps 116 | 117 | Do not install package's dependencies. 118 | 119 | .. option:: -i , --index-url 120 | 121 | Specify the base URL of Python Package Index (default to ``http://pypi.python.org/pypi``). 122 | 123 | .. option:: -d , --install-dir 124 | 125 | The base installation directory for all packages. 126 | 127 | .. option:: -u, --user 128 | 129 | Install the package in the user site-packages. 130 | 131 | 132 | .. _uninst: 133 | 134 | Uninstalling 135 | ------------ 136 | 137 | .. versionchanged:: 0.5 138 | 139 | Replaced :command:`uninstall` and :command:`rm` with :command:`remove`. 140 | 141 | Removing a package is dead simple:: 142 | 143 | $ pyg remove packname 144 | 145 | Pyg tries to detect the package's folder and delete it:: 146 | 147 | $ pyg remove sphinx 148 | Uninstalling sphinx 149 | /usr/bin/sphinx-build 150 | /usr/local/lib/python2.7/dist-packages/Sphinx-1.0.7-py2.7.egg 151 | /usr/bin/sphinx-quickstart 152 | /usr/bin/sphinx-autogen 153 | Proceed? (y/[n]) 154 | 155 | 156 | If *packname* is a module and not a package, Pyg will automatically detect it:: 157 | 158 | $ pyg remove roman 159 | Uninstalling roman 160 | /usr/local/lib/python2.7/dist-packages/roman.pyc 161 | /usr/local/lib/python2.7/dist-packages/roman.py 162 | Proceed? (y/[n]) 163 | 164 | If your answer is *yes* the files will be deleted. This operation is **not undoable**:: 165 | 166 | $ pyg remove itertools_recipes 167 | Uninstalling itertools_recipes 168 | /usr/local/lib/python2.7/dist-packages/itertools_recipes-0.1-py2.7.egg 169 | Proceed? (y/[n]) y 170 | Deleting: /usr/local/lib/python2.7/dist-packages/itertools_recipes-0.1-py2.7.egg... 171 | Removing egg path from easy_install.pth... 172 | itertools_recipes uninstalled succesfully 173 | 174 | .. program:: remove 175 | 176 | .. option:: -y, --yes 177 | 178 | Do not ask confirmation of uninstall deletions:: 179 | 180 | $ pyg remove -y iterutils 181 | Uninstalling iterutils 182 | /usr/local/lib/python2.7/dist-packages/iterutils.py 183 | /usr/local/lib/python2.7/dist-packages/iterutils-0.1.6.egg-info 184 | /usr/local/lib/python2.7/dist-packages/iterutils.pyc 185 | Deleting: /usr/local/lib/python2.7/dist-packages/iterutils.py... 186 | Deleting: /usr/local/lib/python2.7/dist-packages/iterutils-0.1.6.egg-info... 187 | Deleting: /usr/local/lib/python2.7/dist-packages/iterutils.pyc... 188 | Removing egg path from easy_install.pth... 189 | iterutils uninstalled succesfully 190 | 191 | .. option:: -r , --req-file 192 | 193 | Uninstall all the packages listed in the given requirement file. 194 | 195 | :: 196 | 197 | $ echo -e 'itertools_recipes\niterutils' > reqfile.txt 198 | $ cat reqfile.txt 199 | itertools_recipes 200 | iterutils 201 | 202 | :: 203 | 204 | $ pyg remove -r reqfile.txt 205 | Uninstalling itertools_recipes 206 | /usr/local/lib/python2.7/dist-packages/itertools_recipes.py 207 | /usr/local/lib/python2.7/dist-packages/itertools_recipes.pyc 208 | /usr/local/lib/python2.7/dist-packages/itertools_recipes-0.1.egg-info 209 | Proceed? (y/[n]) y 210 | Deleting: /usr/local/lib/python2.7/dist-packages/itertools_recipes.py... 211 | Deleting: /usr/local/lib/python2.7/dist-packages/itertools_recipes.pyc... 212 | Deleting: /usr/local/lib/python2.7/dist-packages/itertools_recipes-0.1.egg-info... 213 | Removing egg path from easy_install.pth... 214 | itertools_recipes uninstalled succesfully 215 | Uninstalling iterutils 216 | /usr/local/lib/python2.7/dist-packages/iterutils.py 217 | /usr/local/lib/python2.7/dist-packages/iterutils-0.1.6.egg-info 218 | /usr/local/lib/python2.7/dist-packages/iterutils.pyc 219 | Proceed? (y/[n]) y 220 | Deleting: /usr/local/lib/python2.7/dist-packages/iterutils.py... 221 | Deleting: /usr/local/lib/python2.7/dist-packages/iterutils-0.1.6.egg-info... 222 | Deleting: /usr/local/lib/python2.7/dist-packages/iterutils.pyc... 223 | Removing egg path from easy_install.pth... 224 | iterutils uninstalled succesfully 225 | 226 | You can supply both ``packname`` (one or more) and requirement files:: 227 | 228 | $ pyg remove -r reqfile.txt docutils 229 | Uninstalling itertools_recipes 230 | /usr/local/lib/python2.7/dist-packages/itertools_recipes.py 231 | /usr/local/lib/python2.7/dist-packages/itertools_recipes.pyc 232 | /usr/local/lib/python2.7/dist-packages/itertools_recipes-0.1.egg-info 233 | Proceed? (y/[n]) y 234 | Deleting: /usr/local/lib/python2.7/dist-packages/itertools_recipes.py 235 | Deleting: /usr/local/lib/python2.7/dist-packages/itertools_recipes.pyc 236 | Deleting: /usr/local/lib/python2.7/dist-packages/itertools_recipes-0.1.egg-info 237 | Removing egg path from easy_install.pth... 238 | itertools_recipes uninstalled succesfully 239 | Uninstalling iterutils 240 | /usr/local/lib/python2.7/dist-packages/iterutils.py 241 | /usr/local/lib/python2.7/dist-packages/iterutils-0.1.6.egg-info 242 | /usr/local/lib/python2.7/dist-packages/iterutils.pyc 243 | Proceed? (y/[n]) y 244 | Deleting: /usr/local/lib/python2.7/dist-packages/iterutils.py 245 | Deleting: /usr/local/lib/python2.7/dist-packages/iterutils-0.1.6.egg-info 246 | Deleting: /usr/local/lib/python2.7/dist-packages/iterutils.pyc 247 | Removing egg path from easy_install.pth... 248 | iterutils uninstalled succesfully 249 | Uninstalling docutils 250 | /usr/local/lib/python2.7/dist-packages/docutils 251 | /usr/local/lib/python2.7/dist-packages/docutils-0.7.egg-info 252 | Proceed? (y/[n]) y 253 | Deleting: /usr/local/lib/python2.7/dist-packages/docutils 254 | Deleting: /usr/local/lib/python2.7/dist-packages/docutils-0.7.egg-info 255 | Removing egg path from easy_install.pth... 256 | docutils uninstalled succesfully 257 | 258 | .. note:: 259 | 260 | You can remove Pyg either with ``pyg remove pyg`` or ``pyg remove yourself``. 261 | 262 | .. versionadded:: 0.5 -------------------------------------------------------------------------------- /docs/cmdline/minor_cmd.rst: -------------------------------------------------------------------------------- 1 | Some minor stuff 2 | ================ 3 | 4 | The ``list`` command 5 | -------------------- 6 | 7 | You can use this command to list all package's avaiable versions:: 8 | 9 | $ pyg list pypol_ 10 | 0.5 installed 11 | 0.4 12 | 0.3 13 | 0.2 14 | 15 | $ pyg list itertools_recipes 16 | 0.1 17 | 18 | If that package is installed, Pyg will add ``installed`` after the current version. 19 | 20 | Checking installed packages 21 | --------------------------- 22 | 23 | If you want to check if a package is installed, you can use the ``check`` command:: 24 | 25 | $ pyg check packname 26 | 27 | Some examples:: 28 | 29 | $ pyg check pyg 30 | True 31 | $ pyg check pyg==42 32 | False 33 | $ pyg check pyg==0.1.2 34 | True 35 | $ pyg check pyg==0.1.3 36 | False 37 | 38 | 39 | Searching PyPI 40 | -------------- 41 | 42 | Pyg can perform searches on PyPI with the ``search`` command:: 43 | 44 | $ pyg search pypol_ 45 | pypol_ 0.5 - Python polynomial library 46 | pypolkit 0.1 - Python bindings for polkit-grant 47 | 48 | $ pyg search distribute 49 | distribute 0.6.15 - Easily download, build, install, upgrade, and uninstall Python packages 50 | virtualenv-distribute 1.3.4.4 - Virtual Python Environment builder 51 | 52 | 53 | Linking directories 54 | ------------------- 55 | 56 | If you want to add a directory to :envvar:`PYTHONPATH` permanently the ``link`` command is what you need:: 57 | 58 | $ pyg link dirname 59 | 60 | When you link a directory Pyg add in a :file:`.pth` file the dir's path. 61 | 62 | 63 | Unlinking 64 | --------- 65 | 66 | .. program:: unlink 67 | 68 | If you want to remove a directory from :envvar:`PYTHONPATH` you can use the ``unlink`` command. 69 | Pyg can remove a directory from :envvar:`PYTHONPATH` only if that directory has been added previously. 70 | 71 | .. option:: -a, --all 72 | 73 | Remove all links in the :file:`.pth` file. -------------------------------------------------------------------------------- /docs/cmdline/req_bund.rst: -------------------------------------------------------------------------------- 1 | Requirement files and bundles 2 | ============================= 3 | 4 | .. _reqs: 5 | 6 | Freezing requirements 7 | --------------------- 8 | 9 | .. versionchanged:: 0.7 10 | From Pyg 0.7 onwards, this command has been renamed ``pyg site``. 11 | 12 | When you launch:: 13 | 14 | $ pyg site 15 | 16 | Pyg tries to detect all installed packages and prints requirements on Standard Output:: 17 | 18 | # Python version: '2.7.1+ (r271:86832, Apr 11 2011, 18:05:24) \n[GCC 4.5.2]' 19 | # Python version info: '2.7.1.final.0' 20 | # Python Prefix: '/usr' 21 | # Platform: 'linux-i686' 22 | # Pyg version: '0.6' 23 | 24 | Brlapi==0.5.5 25 | BzrTools==2.3.1 26 | Cython==0.14.1 27 | ... 28 | wadllib==1.1.8 29 | wsgi-intercept==0.4 30 | wsgiref==0.1.2 31 | xkit==0.0.0 32 | zope.interface==3.6.1 33 | 34 | Note that the first lines -- information about the site -- are commented, so that if they're written into a requirement file, they will be ignored. 35 | 36 | .. program:: site 37 | 38 | .. option:: -f , --file 39 | 40 | Write requirements into the specified file. 41 | Equivalent to:: 42 | 43 | $ pyg site > reqfile.txt 44 | 45 | .. option:: -c, --count 46 | 47 | Return the number of installed packages:: 48 | 49 | $ pyg site -c 50 | 55 51 | 52 | .. option:: -n, --no-info 53 | 54 | Do not add site information:: 55 | 56 | $ pyg site -n 57 | Brlapi==0.5.5 58 | BzrTools==2.3.1 59 | Cython==0.14.1 60 | ... 61 | wadllib==1.1.8 62 | wsgi-intercept==0.4 63 | wsgiref==0.1.2 64 | xkit==0.0.0 65 | zope.interface==3.6.1 66 | 67 | .. _bundles: 68 | 69 | Bundles 70 | ------- 71 | 72 | .. program:: bundle 73 | 74 | The bundle format is specific to Pip (see `Pip documentation `_). 75 | To create a bundle do:: 76 | 77 | $ pyg bundle app.pyb package_name 78 | 79 | This will download all packages (including dependencies) and put them in a bundle file. 80 | Install packages from a bundle is dead simple, and you don't need internet access:: 81 | 82 | $ pyg install app.pyb 83 | 84 | For example, here is ``Pyg`` bundle:: 85 | 86 | $ pyg bundle pyg.pyb pyg 87 | pyg: 88 | Retrieving data for pyg [100% - 472.3 Kb / 472.3 Kb] 89 | Writing data into pyg-0.6.tar.gz 90 | pyg downloaded successfully 91 | Looking for pyg dependencies 92 | Found: setuptools 93 | Found: pkgtools>=0.4 94 | Found: argh>=0.14 95 | argh>=0.14: 96 | Retrieving data for argh [100% - 11.4 Kb / 11.4 Kb] 97 | Writing data into argh-0.14.0.tar.gz 98 | argh downloaded successfully 99 | Looking for argh>=0.14 dependencies 100 | pkgtools>=0.4: 101 | Retrieving data for pkgtools [100% - 28.7 Kb / 28.7 Kb] 102 | Writing data into pkgtools-0.6.tar.gz 103 | pkgtools downloaded successfully 104 | Looking for pkgtools>=0.4 dependencies 105 | setuptools: 106 | Retrieving data for setuptools [100% - 250.8 Kb / 250.8 Kb] 107 | Writing data into setuptools-0.6c11.tar.gz 108 | setuptools downloaded successfully 109 | Looking for setuptools dependencies 110 | Finished processing dependencies 111 | Adding packages to the bundle 112 | Adding the manifest file 113 | 114 | 115 | You can download the generated example bundle `from here `_ (direct link to download). 116 | 117 | .. option:: -r , --req-file 118 | 119 | .. versionadded:: 0.5 120 | 121 | Specify requirement files containing packages to add. This option can be repeated many times:: 122 | 123 | $ pyg bundle bundlename.pybundle -r myreqs.txt -r other_reqs ... 124 | 125 | .. option:: -e , --exclude 126 | 127 | .. versionadded:: 0.5 128 | 129 | Specify packages to exclude from the bundle (can be repeated many times):: 130 | 131 | $ pyg bundle pyg.pyb pyg -e argh -e "pkgtools<=0.3" 132 | 133 | 134 | .. option:: -d, --use-develop 135 | 136 | .. versionadded:: 0.7 137 | 138 | If specified, for every package look for a local (*develop*) package. If it does not find it, it will download it from PyPI:: 139 | 140 | $ pyg bundle pyg pyg -d 141 | pyg: 142 | Looking for a local package... 143 | Looking for pyg dependencies 144 | Found: setuptools 145 | Found: pkgtools>=0.6.1 146 | Found: argh 147 | argh: 148 | Looking for a local package... 149 | Cannot find the location of argh 150 | Retrieving data for argh [100% - 11.4 Kb / 11.4 Kb] 151 | Writing data into argh-0.14.0.tar.gz 152 | argh downloaded successfully 153 | Looking for argh dependencies 154 | pkgtools>=0.6.1: 155 | Looking for a local package... 156 | Looking for pkgtools>=0.6.1 dependencies 157 | setuptools: 158 | Looking for a local package... 159 | Cannot find the location of setuptools 160 | Retrieving data for setuptools [100% - 250.8 Kb / 250.8 Kb] 161 | Writing data into setuptools-0.6c11.tar.gz 162 | setuptools downloaded successfully 163 | Looking for setuptools dependencies 164 | Finished processing dependencies 165 | Adding packages to the bundle 166 | Adding the manifest file 167 | 168 | 169 | .. _pack_doc: 170 | 171 | Packs 172 | ----- 173 | 174 | .. program:: pack 175 | 176 | .. versionadded:: 0.7 177 | 178 | Packs are zip files containing an egg (which includes all necessary packages) and some Python executable files (:file:`run_\{name\}.py`). 179 | The :command:`pack` command has the following syntax:: 180 | 181 | pyg pack {packname} {package} [{options}, ...] 182 | 183 | Its name can either have the ``.zip`` extension or not, and can be a path. 184 | 185 | You can create a pack with the following command:: 186 | 187 | $ pyg pack pyg.zip pyg 188 | Generating the bundle... 189 | pyg: 190 | Retrieving data for pyg [100% - 472.3 Kb / 472.3 Kb] 191 | Writing data into pyg-0.6.tar.gz 192 | pyg downloaded successfully 193 | Looking for pyg dependencies 194 | Found: setuptools 195 | Found: pkgtools>=0.4 196 | Found: argh>=0.14 197 | argh>=0.14: 198 | Retrieving data for argh [100% - 11.4 Kb / 11.4 Kb] 199 | Writing data into argh-0.14.0.tar.gz 200 | argh downloaded successfully 201 | Looking for argh>=0.14 dependencies 202 | pkgtools>=0.4: 203 | Retrieving data for pkgtools [100% - 27.2 Kb / 27.2 Kb] 204 | Writing data into pkgtools-0.6.1.tar.gz 205 | pkgtools downloaded successfully 206 | Looking for pkgtools>=0.4 dependencies 207 | setuptools: 208 | Retrieving data for setuptools [100% - 250.8 Kb / 250.8 Kb] 209 | Writing data into setuptools-0.6c11.tar.gz 210 | setuptools downloaded successfully 211 | Looking for setuptools dependencies 212 | Finished processing dependencies 213 | Adding packages to the bundle 214 | Generating EGG-INFO... 215 | 216 | 217 | For example, Pyg Pack has the following structure:: 218 | 219 | pyg-0.6 220 | ├── pyg.egg 221 | ├── run_easy_install-2.3.py 222 | ├── run_easy_install.py 223 | └── run_pyg.py 224 | 225 | As you can see, there are already some executable files (Pyg looks for them in the packages' :file:`entry_points.txt` file) and you can run them without installing Pyg: everything necessary is in :file:`pyg.egg`. 226 | Amazing! 227 | 228 | If you want to try it, download it `from here `_ (direct link to download), unpack it and run the :file:`run_pyg.py` file. You will be able to use Pyg without installing it! 229 | 230 | .. option:: -e , --exclude 231 | 232 | Specify packages to exclude from the pack (can be repeated many times):: 233 | 234 | $ pyg pack pyg.zip pyg -e argh -e "pkgtools<=0.3" 235 | 236 | .. warning:: 237 | If you exclude some packages from the pack it is very likely that its executables will not work, without some dependencies. 238 | 239 | .. option:: -d, --use-develop 240 | 241 | If specified, for every package look for a local (*develop*) distribution. If it does not find it, it will download it from PyPI. 242 | 243 | On certain systems (probably Unix-like ones) the :command:`pack` command with this option enabled may require root privileges, because Pyg will run the :command:`sdist` command (``python setup.py sdist``) for every local distribution. 244 | 245 | (See also :option:`bundle -d`.) -------------------------------------------------------------------------------- /docs/cmdline/upgr_down_shell.rst: -------------------------------------------------------------------------------- 1 | Upgrading, downloading and Pyg Shell 2 | ==================================== 3 | 4 | .. _upd: 5 | 6 | Upgrading installed packages 7 | ---------------------------- 8 | 9 | .. versionadded:: 0.3 10 | 11 | .. program:: update 12 | 13 | When you use the ``update`` command, Pyg searches through all installed packages and checks for updates. If there are some, Pyg installs them. 14 | 15 | Before loading the entire list of installed packages, Pyg checks the :file:`~/.pyg/installed_packages.txt` file. If it exists Pyg will update only packages in that file:: 16 | 17 | $ pyg update 18 | Cache file not found: $HOME/.pyg/installed_packages.txt 19 | Loading list of installed packages... 20 | 15 packages loaded 21 | Searching for updates 22 | A new release is avaiable for simplejson: 2.1.5 (old 2.1.3) 23 | Do you want to upgrade? (y/[n]) y 24 | Upgrading simplejson to 2.1.5 25 | Installing simplejson-2.1.5.tar.gz... 26 | Installing simplejson-2.1.5.tar.gz 27 | Running setup.py egg_info for simplejson 28 | Running setup.py install for simplejson 29 | simplejson installed successfully 30 | 31 | ... 32 | 33 | Updating finished successfully 34 | 35 | $ pyg update 36 | Loading list of installed packages... 37 | Reading cache... 38 | 15 packages loaded 39 | Searching for updates 40 | A new release is avaiable for wadllib: 1.2.0 (old 1.1.8) 41 | Do you want to upgrade? (y/[n]) n 42 | wadllib has not been upgraded 43 | A new release is avaiable for launchpadlib: 1.9.8 (old 1.9.7) 44 | Do you want to upgrade? (y/[n]) n 45 | launchpadlib has not been upgraded 46 | Updating finished successfully 47 | 48 | Downloading packages 49 | -------------------- 50 | 51 | .. versionadded:: 0.2 52 | 53 | If you only need to download a package you can use the ``download`` command:: 54 | 55 | $ pyg download packname 56 | 57 | If the requirement is not satisfied Pyg won't download anything:: 58 | 59 | $ pyg download pyg==1024 60 | E: Did not find files to download 61 | 62 | .. program:: download 63 | 64 | .. option:: -u, --unpack 65 | 66 | After downloading a package, Pyg unpacks it:: 67 | 68 | $ pyg download -u pypol_ 69 | Found egg file for another Python version: 2.6. Continue searching... 70 | Retrieving data for pypol_ 71 | Writing data into pypol_-0.5-py2.7.egg 72 | pypol_ downloaded successfully 73 | Unpacking pypol_-0.5-py2.7.egg to ./pypol_-0.5-py2.7 74 | $ l 75 | pypol_-0.5-py2.7/ pypol_-0.5-py2.7.egg 76 | 77 | .. option:: -d , --download-dir 78 | 79 | Where to download the package, default to :file:`.` (current working directory):: 80 | 81 | $ pyg download -d /downloads/python_downloads/ pyg 82 | 83 | If the path does not exist, Pyg will create it. 84 | 85 | .. option:: -p , --prefer 86 | 87 | The preferred file type for the download. Pyg looks for that file type and, if it does not exists, will try another extension:: 88 | 89 | $ pyg download -p .tar.gz pyg 90 | Retrieving data for pyg 91 | Writing data into pyg-0.1.tar.gz 92 | pyg downloaded successfully 93 | 94 | $ pyg download -p .egg pyg 95 | Retrieving data for pyg 96 | Writing data into pyg-0.1-py2.7.egg 97 | pyg downloaded successfully 98 | 99 | $ pyg download -p .myawesomeext pyg 100 | Retrieving data for pyg 101 | Writing data into pyg-0.1-py2.7.egg 102 | pyg downloaded successfully 103 | 104 | .. _shell: 105 | 106 | Pyg Shell 107 | --------- 108 | 109 | .. versionadded:: 0.4 110 | 111 | If you need to execute many Pyg commands and you need root privileges (for example on Unix systems), you can fire up Pyg Shell and you are done:: 112 | 113 | $ pyg shell 114 | 115 | Now you can use all Pyg's commands plus 3 shell commands: :command:`cd`, :command:`pwd`, and :command:`ls`:: 116 | 117 | pyg:/home/user$ check pyg 118 | True 119 | pyg:/home/user$ check pyg==0.3.2 120 | True 121 | pyg:/home/user$ ls 122 | pkgtools pyg 123 | pyg:/home/user$ pwd 124 | /home/user 125 | pyg:/home/user$ cd pyg 126 | pyg:/home/user/pyg$ pwd 127 | /home/user/pyg 128 | pyg:/home/user/pyg$ install sphinx 129 | sphinx is already installed 130 | pyg:/home/user/pyg$ install -U sphinx 131 | sphinx is already installed, upgrading... 132 | Looking for sphinx releases on PyPI 133 | Best match: Sphinx==1.0.7 134 | Downloading Sphinx 135 | Checking md5 sum 136 | Running setup.py egg_info for Sphinx 137 | Running setup.py install for Sphinx 138 | Installing dependencies... 139 | Jinja2>=2.2 is already installed 140 | docutils>=0.5 is already installed 141 | Pygments>=0.8 is already installed 142 | Finished installing dependencies 143 | Sphinx installed successfully 144 | pyg:/home/user/pyg$ cd 145 | pyg:/home/user$ exit -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Pyg documentation build configuration file, created by 4 | # sphinx-quickstart on Sat Mar 5 09:31:18 2011. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | sys.path.insert(0, os.path.abspath('..')) 20 | import pyg 21 | 22 | # -- General configuration ----------------------------------------------------- 23 | 24 | # If your documentation needs a minimal Sphinx version, state it here. 25 | #needs_sphinx = '1.0' 26 | 27 | # Add any Sphinx extension module names here, as strings. They can be extensions 28 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 29 | extensions = [] 30 | try: 31 | import sphinxtogithub 32 | except ImportError: 33 | pass 34 | else: 35 | extensions.append('sphinxtogithub') 36 | 37 | # Add any paths that contain templates here, relative to this directory. 38 | templates_path = ['_templates'] 39 | 40 | # The suffix of source filenames. 41 | source_suffix = '.rst' 42 | 43 | # The encoding of source files. 44 | #source_encoding = 'utf-8-sig' 45 | 46 | # The master toctree document. 47 | master_doc = 'index' 48 | 49 | # General information about the project. 50 | project = u'Pyg' 51 | copyright = u'2011, Michele Lacchia' 52 | 53 | # The version info for the project you're documenting, acts as replacement for 54 | # |version| and |release|, also used in various other places throughout the 55 | # built documents. 56 | # 57 | # The short X.Y version. 58 | version = pyg.__version__ 59 | # The full version, including alpha/beta/rc tags. 60 | release = version 61 | 62 | # The language for content autogenerated by Sphinx. Refer to documentation 63 | # for a list of supported languages. 64 | #language = None 65 | 66 | # There are two options for replacing |today|: either, you set today to some 67 | # non-false value, then it is used: 68 | #today = '' 69 | # Else, today_fmt is used as the format for a strftime call. 70 | #today_fmt = '%B %d, %Y' 71 | 72 | # List of patterns, relative to source directory, that match files and 73 | # directories to ignore when looking for source files. 74 | exclude_patterns = ['_build', 'api'] 75 | 76 | # The reST default role (used for this markup: `text`) to use for all documents. 77 | #default_role = None 78 | 79 | # If true, '()' will be appended to :func: etc. cross-reference text. 80 | #add_function_parentheses = True 81 | 82 | # If true, the current module name will be prepended to all description 83 | # unit titles (such as .. function::). 84 | #add_module_names = True 85 | 86 | # If true, sectionauthor and moduleauthor directives will be shown in the 87 | # output. They are ignored by default. 88 | #show_authors = False 89 | 90 | # The name of the Pygments (syntax highlighting) style to use. 91 | pygments_style = 'bw' 92 | highlight_language = 'console' 93 | 94 | # A list of ignored prefixes for module index sorting. 95 | #modindex_common_prefix = [] 96 | 97 | 98 | # -- Options for HTML output --------------------------------------------------- 99 | 100 | # The theme to use for HTML and HTML Help pages. See the documentation for 101 | # a list of builtin themes. 102 | html_theme = 'nature2' 103 | 104 | # Theme options are theme-specific and customize the look and feel of a theme 105 | # further. For a list of options available for each theme, see the 106 | # documentation. 107 | #html_theme_options = {} 108 | 109 | # Add any paths that contain custom themes here, relative to this directory. 110 | html_theme_path = ['./_themes'] 111 | 112 | # The name for this set of Sphinx documents. If None, it defaults to 113 | # " v documentation". 114 | #html_title = None 115 | 116 | # A shorter title for the navigation bar. Default is the same as html_title. 117 | #html_short_title = None 118 | 119 | # The name of an image file (relative to this directory) to place at the top 120 | # of the sidebar. 121 | #html_logo = None 122 | 123 | # The name of an image file (within the static path) to use as favicon of the 124 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 125 | # pixels large. 126 | #html_favicon = None 127 | 128 | # Add any paths that contain custom static files (such as style sheets) here, 129 | # relative to this directory. They are copied after the builtin static files, 130 | # so a file named "default.css" will overwrite the builtin "default.css". 131 | html_static_path = ['_static'] 132 | 133 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 134 | # using the given strftime format. 135 | #html_last_updated_fmt = '%b %d, %Y' 136 | 137 | # If true, SmartyPants will be used to convert quotes and dashes to 138 | # typographically correct entities. 139 | #html_use_smartypants = True 140 | 141 | # Custom sidebar templates, maps document names to template names. 142 | #html_sidebars = {} 143 | 144 | # Additional templates that should be rendered to pages, maps page names to 145 | # template names. 146 | #html_additional_pages = {} 147 | 148 | # If false, no module index is generated. 149 | #html_domain_indices = True 150 | 151 | # If false, no index is generated. 152 | #html_use_index = True 153 | 154 | # If true, the index is split into individual pages for each letter. 155 | #html_split_index = False 156 | 157 | # If true, links to the reST sources are added to the pages. 158 | #html_show_sourcelink = True 159 | 160 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 161 | #html_show_sphinx = True 162 | 163 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 164 | #html_show_copyright = True 165 | 166 | # If true, an OpenSearch description file will be output, and all pages will 167 | # contain a tag referring to it. The value of this option must be the 168 | # base URL from which the finished HTML is served. 169 | #html_use_opensearch = '' 170 | 171 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 172 | #html_file_suffix = None 173 | 174 | # Output file base name for HTML help builder. 175 | htmlhelp_basename = 'Pygdoc' 176 | 177 | 178 | # -- Options for LaTeX output -------------------------------------------------- 179 | 180 | # The paper size ('letter' or 'a4'). 181 | #latex_paper_size = 'letter' 182 | 183 | # The font size ('10pt', '11pt' or '12pt'). 184 | #latex_font_size = '10pt' 185 | 186 | # Grouping the document tree into LaTeX files. List of tuples 187 | # (source start file, target name, title, author, documentclass [howto/manual]). 188 | latex_documents = [ 189 | ('index', 'Pyg.tex', u'Pyg Documentation', 190 | u'Michele Lacchia', 'manual'), 191 | ] 192 | 193 | # The name of an image file (relative to this directory) to place at the top of 194 | # the title page. 195 | #latex_logo = None 196 | 197 | # For "manual" documents, if this is true, then toplevel headings are parts, 198 | # not chapters. 199 | #latex_use_parts = False 200 | 201 | # If true, show page references after internal links. 202 | #latex_show_pagerefs = False 203 | 204 | # If true, show URL addresses after external links. 205 | #latex_show_urls = False 206 | 207 | # Additional stuff for the LaTeX preamble. 208 | #latex_preamble = '' 209 | 210 | # Documents to append as an appendix to all manuals. 211 | #latex_appendices = [] 212 | 213 | # If false, no module index is generated. 214 | #latex_domain_indices = True 215 | 216 | 217 | # -- Options for manual page output -------------------------------------------- 218 | 219 | # One entry per manual page. List of tuples 220 | # (source start file, name, description, authors, manual section). 221 | man_pages = [ 222 | ('index', 'Pyg', u'Pyg Documentation', 223 | [u'Michele Lacchia'], 1) 224 | ] 225 | -------------------------------------------------------------------------------- /docs/config_files.rst: -------------------------------------------------------------------------------- 1 | Pyg's config files 2 | ================== 3 | 4 | .. versionadded:: 0.4 5 | .. versionchanged:: 0.8 6 | From version 0.8 onwards, Pyg supports a :envvar:`PYG_CONFIG_FILE` environment variable. 7 | 8 | Config files allow you to override command-line options, and to save them somewhere. 9 | During the initialization process, Pyg looks for configurations file, it this order: 10 | 11 | * :envvar:`PYG_CONFIG_FILE`: an environment variable pointing to the config file. This variable can contain multiple paths (separated by a color, ``:``). (Introduced in Pyg 0.8) 12 | * :file:`./pyg.conf`: a configuration file in the current working directory: 13 | * :file:`~/pyg.conf`: where ``~`` stands for your :envvar:`HOME` directory; 14 | * :file:`~/.pyg/pyg.conf`. 15 | 16 | A config file has this structure:: 17 | 18 | [section_name] 19 | option = value 20 | 21 | [section_name2] 22 | option1 = value1 23 | option2 = value2 24 | 25 | ... 26 | 27 | 28 | In addition to this, Pyg supports a nonstandard syntax, allowing multiple section names in a single header:: 29 | 30 | [section1 & section2 & section6] 31 | option = value 32 | 33 | It will set that option in all specified sections. 34 | 35 | Note: If you set an option to ``False``, ``false``, or ``0``, Pyg will consider it as false. 36 | 37 | The ``global`` section 38 | ---------------------- 39 | 40 | .. versionadded:: 0.7 41 | 42 | This section is a special one: there is no ``global`` command, and it refers to Pyg's global options. 43 | At the moment there is only one global option: :option:`--no-colors`. You can set it as follows:: 44 | 45 | [global] 46 | no_colors = True 47 | 48 | 49 | Example usage 50 | ------------- 51 | 52 | Example 1 53 | +++++++++ 54 | 55 | :file:`~/pyg.conf`:: 56 | 57 | [remove] 58 | yes = True 59 | 60 | [install] 61 | upgrade_all = True 62 | 63 | Pyg:: 64 | 65 | $ pyg install iterutils 66 | Loading options from ~/pyg.conf 67 | iterutils is already installed, upgrading... 68 | Looking for iterutils releases on PyPI 69 | Best match: iterutils==0.1.6 70 | Downloading iterutils [100% - 2.9 Kb] 71 | Checking md5 sum 72 | Running setup.py egg_info for iterutils 73 | Running setup.py install for iterutils 74 | iterutils installed successfully 75 | $ pyg remove iterutils 76 | Loading options from ~/pyg.conf 77 | Uninstalling iterutils 78 | /usr/local/lib/python2.7/dist-packages/iterutils.py 79 | /usr/local/lib/python2.7/dist-packages/iterutils.pyc 80 | /usr/local/lib/python2.7/dist-packages/iterutils-0.1.6-py2.7.egg-info 81 | Deleting: /usr/local/lib/python2.7/dist-packages/iterutils.py 82 | Deleting: /usr/local/lib/python2.7/dist-packages/iterutils.pyc 83 | Deleting: /usr/local/lib/python2.7/dist-packages/iterutils-0.1.6-py2.7.egg-info 84 | Removing egg path from easy_install.pth... 85 | iterutils uninstalled succesfully 86 | 87 | Example 2 88 | +++++++++ 89 | 90 | :file:`~/pyg.conf`:: 91 | 92 | [freeze] 93 | count = True 94 | 95 | Pyg:: 96 | 97 | $ pyg freeze 98 | Loading options from ~/pyg.conf 99 | 84 100 | 101 | Example 3 102 | +++++++++ 103 | 104 | You can also override saved options from the command line. 105 | :file:`pyg.conf`:: 106 | 107 | [install] 108 | index_url = http://pypi.python.org/pypi 109 | 110 | Pyg:: 111 | 112 | $ pyg install itertools_recipes -U --index-url = http://pypi.python.org/simple 113 | itertools_recipes is already installed, upgrading... 114 | Looking for links on http://pypi.python.org/simple 115 | Found: itertools_recipes-0.1.tar.gz 116 | Downloading itertools_recipes [100% - 2.3 Kb] 117 | Running setup.py egg_info for itertools_recipes 118 | Running setup.py install for itertools_recipes 119 | itertools_recipes installed successfully 120 | 121 | instead of:: 122 | 123 | $ pyg install -U itertools_recipes 124 | itertools_recipes is already installed, upgrading... 125 | Looking for itertools_recipes releases on PyPI 126 | Best match: itertools_recipes==0.1 127 | Downloading itertools_recipes [100% - 2.3 Kb] 128 | Checking md5 sum 129 | Running setup.py egg_info for itertools_recipes 130 | Running setup.py install for itertools_recipes 131 | itertools_recipes installed successfully 132 | 133 | Option tree 134 | ----------- 135 | 136 | Here is a list of all sections and their default options: 137 | 138 | **global**: 139 | 140 | - *no_colors* = False 141 | 142 | **install**: 143 | 144 | - *upgrade* = False 145 | - *upgrade_all* = False 146 | - *no_deps* = False 147 | - *index_url* = ``http://pypi.python.org/pypi`` 148 | - *install_dir* = :data:`pyg.locations.INSTALL_DIR` 149 | - *user* = False 150 | - *no_scripts* = False 151 | - *ignore* = False 152 | 153 | **remove**: 154 | 155 | - *yes* = False 156 | 157 | **bundle**: 158 | 159 | - *exclude* = None 160 | 161 | **update**: 162 | 163 | - *yes* = False 164 | 165 | **download**: 166 | 167 | - *unpack* = False 168 | - *download_dir* = :file:`.` 169 | - *prefer* = None 170 | 171 | **freeze**: 172 | 173 | - *count* = False 174 | - *file* = None 175 | 176 | **unlink**: 177 | 178 | - *all* = False -------------------------------------------------------------------------------- /docs/features.rst: -------------------------------------------------------------------------------- 1 | Pyg's features 2 | ============== 3 | 4 | Pyg can: 5 | 6 | * install packages from :file:`.tar.gz`, :file:`.tar.bz2`, :file:`.zip` archives, as well as from :file:`.egg` files and :file:`.pybundle` files. 7 | * Uninstall packages. 8 | * Define fixed sets of requirement. 9 | * Perform searches on PyPI. 10 | * Install from binaries (e.g. from :file:`.exe` or :file:`.msi`) on Windows. 11 | * Install packages in editable mode from VCS (Git, Mercurial, Bazaar, Svn). 12 | 13 | Currently Pyg don't: 14 | 15 | * understand Setuptools extras (like ``package[extra]``). This is planned for Pyg 1.0. 16 | 17 | 18 | Pyg compared to easy_install 19 | ---------------------------- 20 | 21 | Pyg is meant to be a replacement for easy_install, and tries to improve it. In particular: 22 | 23 | * It can install packages from a name, URL, local directory or file. 24 | * It supports installation from requirements files. 25 | * It can install packages from Pip's bundels. 26 | * Easy yet very powerful uninstallation of packages. 27 | * It can perform searches on PyPI. 28 | * It offers the possibility to download a package without installing it. 29 | * Source code concise and cohesive and easily extensible. 30 | * Pyg can used either as a command-line tool or a Python library. 31 | * The output on the console should be useful. 32 | 33 | But at the moment Pyg does not do everything that easy_install does: 34 | 35 | * It does not support Setuptools extras (like ``package[extra]``). This is planned for Pyg 1.0. 36 | 37 | 38 | Pyg compared to Pip 39 | ------------------- 40 | 41 | Pyg is very similar to Pip but tries to improve something. Specifically: 42 | 43 | * Pyg uses the same installation method as Pip but a different package discovery system that should be faster. 44 | * Pyg supports Python Eggs installation, while Pip doesn't. 45 | * A better uninstallation of packages (Pip cannot install packages installed with ``python setup.py install``). 46 | 47 | In addition to that, Pyg is completely useable under vitualenvs. 48 | 49 | 50 | Uninstall 51 | --------- 52 | 53 | Pyg can uninstall most installed packages with:: 54 | 55 | $ pyg uninstall packname 56 | 57 | It tries to detect the directory where the packages have been installed and delete them. 58 | Pyg can uninstall all packages, except those that have been installed in editable mode. 59 | 60 | See also: :ref:`uninst`. 61 | 62 | 63 | Package upgrading 64 | ----------------- 65 | 66 | This is a feature unique to Pyg: by running ``pyg update`` you can check all your installed packages and upgrade those for which there is a newer release. 67 | Pyg collects all packages that can upgrade and then check for updates. 68 | 69 | See also: :ref:`upd`. 70 | 71 | Pyg and virtualenv 72 | ------------------ 73 | 74 | .. versionadded:: 0.5 75 | 76 | From Pyg 0.5 onwards, virtualenv is completely supported. You can easily manage packages from inside it. 77 | A little example:: 78 | 79 | $ virtualenv env -p /usr/bin/python2.6 --no-site-packages 80 | Running virtualenv with interpreter /usr/bin/python2.6 81 | New python executable in env/bin/python2.6 82 | Also creating executable in env/bin/python 83 | Installing setuptools............................done. 84 | $ cd env 85 | $ source bin/activate 86 | (env)$ curl -O https://github.com/rubik/pyg/raw/master/get_pyg.py 87 | (env)$ python get_pyg.py 88 | (env)$ pyg install sphinx 89 | Looking for sphinx releases on PyPI 90 | Best match: Sphinx==1.0.7 91 | Downloading Sphinx 92 | Checking md5 sum 93 | Running setup.py egg_info for Sphinx 94 | Running setup.py install for Sphinx 95 | Installing dependencies... 96 | Installing Jinja2>=2.2 (from Sphinx==1.0.7) 97 | Looking for Jinja2 releases on PyPI 98 | Best match: Jinja2==2.5.5 99 | Downloading Jinja2 100 | Checking md5 sum 101 | Running setup.py egg_info for Jinja2 102 | Running setup.py install for Jinja2 103 | Installing dependencies... 104 | Installing Babel>=0.8 (from Jinja2==2.2) 105 | Looking for Babel releases on PyPI 106 | Best match: Babel==0.9.6 107 | Downloading Babel 108 | Checking md5 sum 109 | Running setup.py egg_info for Babel 110 | Running setup.py install for Babel 111 | Babel installed successfully 112 | Finished installing dependencies 113 | Jinja2 installed successfully 114 | Installing docutils>=0.5 (from Sphinx==1.0.7) 115 | Looking for docutils releases on PyPI 116 | Best match: docutils==0.7 117 | Downloading docutils 118 | Checking md5 sum 119 | Running setup.py egg_info for docutils 120 | Running setup.py install for docutils 121 | docutils installed successfully 122 | Installing Pygments>=0.8 (from Sphinx==1.0.7) 123 | Looking for Pygments releases on PyPI 124 | Best match: Pygments==1.4 125 | Downloading Pygments 126 | Checking md5 sum 127 | Running setup.py egg_info for Pygments 128 | Running setup.py install for Pygments 129 | Pygments installed successfully 130 | Finished installing dependencies 131 | Sphinx installed successfully 132 | (env)$ python 133 | Python 2.6.6 (r266:84292, Mar 25 2011, 19:24:58) 134 | [GCC 4.5.2] on linux2 135 | Type "help", "copyright", "credits" or "license" for more information. 136 | >>> import sphinx 137 | >>> sphinx.__version__ 138 | '1.0.7' 139 | >>> 140 | (env)$ pyg remove sphinx 141 | Uninstalling sphinx 142 | env/lib/python2.6/site-packages/Sphinx-1.0.7-py2.6.egg-info 143 | env/bin/sphinx-quickstart 144 | env/lib/python2.6/site-packages/sphinx 145 | env/bin/sphinx-build 146 | env/bin/sphinx-autogen 147 | Proceed? (y/[n]) y 148 | Removing egg path from easy_install.pth... 149 | sphinx uninstalled succesfully 150 | 151 | Pyg Shell 152 | --------- 153 | 154 | You can launch Pyg Shell with:: 155 | 156 | $ pyg shell 157 | 158 | and it will open a shell where you can use all Pyg's command. This is particularly useful on when you need root privileges to installs packages (e.g. Unix): if you need to execute many commands you can fire up the shell and then use Pyg without worrying about root privileges. 159 | 160 | See also: :ref:`shell`. 161 | 162 | 163 | Bundles 164 | ------- 165 | 166 | Pyg supports Pip's bundles. The bundle format is specific to Pip (see `Pip documentation `_). 167 | Once you have one you can install it like:: 168 | 169 | $ pyg install yourbundle.pyb 170 | 171 | The internet access is not necessary. 172 | In addition to that, you can easily create bundles with Pyg. For example, if you want to create a bundle of Pyg, you would do:: 173 | 174 | $ pyg bundle pyg-bundle.pyb pyg 175 | 176 | See also: :ref:`bundles`. 177 | 178 | .. _packs: 179 | 180 | Packs 181 | ----- 182 | 183 | .. versionadded:: 0.7 184 | 185 | Packs are very similar to bundles, except that they can contain Python executables too. Packs were invented by Fabien Devaux for the Zicbee project (check it at PyPI: `Zicbee `_). 186 | 187 | A pack contains a folder in which there is an egg (with all necessary packages) and some Python executable files (:file:`run_{name}.py`). You can unpack the pack and then run the executables without touching the egg! Like a bundle, a pack does not require an internet connection to work: all required package are inside the zipped egg. 188 | The advantage of packs over bundles is that you can run included Python executables without installing the library, because everything necessary is included in the egg! 189 | 190 | See also: :ref:`pack_doc`. -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. pyg documentation master file, created by 2 | sphinx-quickstart on Sat Mar 5 09:31:18 2011. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Thanks to: `Free Domain Name `_ for the pyg-installer.co.nr hook. 7 | 8 | Welcome to Pyg's documentation! 9 | =============================== 10 | 11 | :Authors: Michele Lacchia , Fabien Devaux 12 | :Version: |version| 13 | :Source code: http://github.com/rubik/pyg 14 | :Download: http://pypi.python.org/pypi/pyg 15 | :Homepage: http://pyg-installer.co.nr 16 | :Keywords: python, easy_install, packages, pypi, command line, cli 17 | :Date: |today| 18 | 19 | .. note:: 20 | 21 | These are development docs. Documentation for Pyg last stable release is here: http://pyg.readthedocs.org/en/latest 22 | 23 | .. toctree:: 24 | :maxdepth: 1 25 | 26 | quickstart 27 | 28 | Overview 29 | -------- 30 | 31 | .. toctree:: 32 | :maxdepth: 1 33 | 34 | features 35 | 36 | Using Pyg 37 | --------- 38 | 39 | .. toctree:: 40 | :maxdepth: 1 41 | 42 | cmdline 43 | 44 | 45 | Development 46 | ----------- 47 | 48 | .. toctree:: 49 | 50 | changes -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | if NOT "%PAPER%" == "" ( 11 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 12 | ) 13 | 14 | if "%1" == "" goto help 15 | 16 | if "%1" == "help" ( 17 | :help 18 | echo.Please use `make ^` where ^ is one of 19 | echo. html to make standalone HTML files 20 | echo. dirhtml to make HTML files named index.html in directories 21 | echo. singlehtml to make a single large HTML file 22 | echo. pickle to make pickle files 23 | echo. json to make JSON files 24 | echo. htmlhelp to make HTML files and a HTML help project 25 | echo. qthelp to make HTML files and a qthelp project 26 | echo. devhelp to make HTML files and a Devhelp project 27 | echo. epub to make an epub 28 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 29 | echo. text to make text files 30 | echo. man to make manual pages 31 | echo. changes to make an overview over all changed/added/deprecated items 32 | echo. linkcheck to check all external links for integrity 33 | echo. doctest to run all doctests embedded in the documentation if enabled 34 | goto end 35 | ) 36 | 37 | if "%1" == "clean" ( 38 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 39 | del /q /s %BUILDDIR%\* 40 | goto end 41 | ) 42 | 43 | if "%1" == "html" ( 44 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 45 | if errorlevel 1 exit /b 1 46 | echo. 47 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 48 | goto end 49 | ) 50 | 51 | if "%1" == "dirhtml" ( 52 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 53 | if errorlevel 1 exit /b 1 54 | echo. 55 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 56 | goto end 57 | ) 58 | 59 | if "%1" == "singlehtml" ( 60 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 61 | if errorlevel 1 exit /b 1 62 | echo. 63 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 64 | goto end 65 | ) 66 | 67 | if "%1" == "pickle" ( 68 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 69 | if errorlevel 1 exit /b 1 70 | echo. 71 | echo.Build finished; now you can process the pickle files. 72 | goto end 73 | ) 74 | 75 | if "%1" == "json" ( 76 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished; now you can process the JSON files. 80 | goto end 81 | ) 82 | 83 | if "%1" == "htmlhelp" ( 84 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished; now you can run HTML Help Workshop with the ^ 88 | .hhp project file in %BUILDDIR%/htmlhelp. 89 | goto end 90 | ) 91 | 92 | if "%1" == "qthelp" ( 93 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 94 | if errorlevel 1 exit /b 1 95 | echo. 96 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 97 | .qhcp project file in %BUILDDIR%/qthelp, like this: 98 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\pyg.qhcp 99 | echo.To view the help file: 100 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\pyg.ghc 101 | goto end 102 | ) 103 | 104 | if "%1" == "devhelp" ( 105 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 106 | if errorlevel 1 exit /b 1 107 | echo. 108 | echo.Build finished. 109 | goto end 110 | ) 111 | 112 | if "%1" == "epub" ( 113 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 117 | goto end 118 | ) 119 | 120 | if "%1" == "latex" ( 121 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 122 | if errorlevel 1 exit /b 1 123 | echo. 124 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 125 | goto end 126 | ) 127 | 128 | if "%1" == "text" ( 129 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 130 | if errorlevel 1 exit /b 1 131 | echo. 132 | echo.Build finished. The text files are in %BUILDDIR%/text. 133 | goto end 134 | ) 135 | 136 | if "%1" == "man" ( 137 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 141 | goto end 142 | ) 143 | 144 | if "%1" == "changes" ( 145 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.The overview file is in %BUILDDIR%/changes. 149 | goto end 150 | ) 151 | 152 | if "%1" == "linkcheck" ( 153 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Link check complete; look for any errors in the above output ^ 157 | or in %BUILDDIR%/linkcheck/output.txt. 158 | goto end 159 | ) 160 | 161 | if "%1" == "doctest" ( 162 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 163 | if errorlevel 1 exit /b 1 164 | echo. 165 | echo.Testing of doctests in the sources finished, look at the ^ 166 | results in %BUILDDIR%/doctest/output.txt. 167 | goto end 168 | ) 169 | 170 | :end 171 | -------------------------------------------------------------------------------- /docs/quickstart.rst: -------------------------------------------------------------------------------- 1 | Quickstart 2 | ========== 3 | 4 | Installing Pyg 5 | -------------- 6 | 7 | The :file:`get-pyg.py` file 8 | +++++++++++++++++++++++++++++ 9 | 10 | The preferred way to install Pyg (and the easiest one) is to download the :file:`get-pyg.py` file from `Github `_ and to launch it:: 11 | 12 | $ curl -O https://raw.github.com/rubik/pyg/master/get-pyg.py 13 | $ python get-pyg.py 14 | 15 | This will install the latest stable release, but if you want to install the development version, just do:: 16 | 17 | $ python get-pyg.py --dev 18 | 19 | 20 | Using ``Pip`` or ``easy_install`` 21 | +++++++++++++++++++++++++++++++++ 22 | 23 | If you have ``easy_install`` or ``Pip`` installed, to install Pyg it only takes one simple command:: 24 | 25 | $ pip install pyg 26 | 27 | or if you must:: 28 | 29 | $ easy_install pyg 30 | 31 | And then you should no longer need them! 32 | 33 | .. _get: 34 | 35 | Getting the source 36 | ------------------ 37 | 38 | You can also install Pyg from source. The lastest release is avaiable on GitHub: 39 | 40 | * `tarball `_ 41 | * `zipball `_ 42 | 43 | Once you have unpacked the archive you can install it easily:: 44 | 45 | $ python setup.py install 46 | 47 | 48 | Building the documentation 49 | -------------------------- 50 | 51 | In order to build Pyg's documentation locally you have to install `Sphinx `_:: 52 | 53 | $ pyg install sphinx 54 | 55 | Download the source (see :ref:`get`), go to :file:`docs/` and run :command:`make`:: 56 | 57 | $ cd docs/ 58 | $ make html 59 | 60 | Now Pyg's documentation is in :file:`_build/html`. -------------------------------------------------------------------------------- /get-pyg.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | import os 4 | import sys 5 | import urllib 6 | import urllib2 7 | import tarfile 8 | import zipfile 9 | import tempfile 10 | import subprocess 11 | 12 | try: 13 | import simplejson as json 14 | except ImportError: 15 | import json 16 | 17 | 18 | URL = 'http://pypi.python.org/pypi/{0}/json' 19 | 20 | 21 | def log(msg): 22 | return sys.stdout.write(msg + '\n') 23 | 24 | def unpack(path): 25 | if tarfile.is_tarfile(path): 26 | archive = tarfile.open(path) 27 | elif zipfile.is_zipfile(path): 28 | archive = zipfile.open(path) 29 | else: 30 | raise TypeError('Unknown file-type: {0}'.format(path)) 31 | tempdir = tempfile.mkdtemp() 32 | archive.extractall(tempdir) 33 | return os.path.join(tempdir, os.listdir(tempdir)[0]) 34 | 35 | def get_url(url): 36 | data = urllib2.urlopen(url).read() 37 | json_data = json.loads(data) 38 | installable = (release for release in json_data['urls'] if release['packagetype'] == 'sdist') 39 | return min(installable, key=lambda item: item['size'])['url'] 40 | 41 | def call(args, **kwargs): 42 | try: 43 | subprocess.check_call(args, **kwargs) 44 | except subprocess.CalledProcessError as e: 45 | log('Installation failed. Installation command returned non-zero ' \ 46 | 'exit status: ' + str(e.returncode) + '\n') 47 | 48 | 49 | def install(pkg, dev=False, single_exe=False): 50 | log('Installing {0}'.format(pkg)) 51 | if dev and pkg == 'pyg': 52 | url = 'https://github.com/rubik/pyg/tarball/develop' 53 | else: 54 | url = get_url(URL.format(pkg)) 55 | log('Retrieving archive from {0}'.format(url)) 56 | path = urllib.urlretrieve(url)[0] 57 | log('Unpacking archive...') 58 | path = unpack(path) 59 | setup_py = os.path.join(path, 'setup.py') 60 | log('Running setup.py install...') 61 | call([sys.executable, setup_py, 'install'] + (['--single-exe'] if single_exe else []), 62 | cwd=path) 63 | 64 | def main(): 65 | try: 66 | import setuptools 67 | except ImportError: 68 | install('setuptools') 69 | install('pyg', '--dev' in sys.argv, '--single-exe' in sys.argv) 70 | if '--tests' in sys.argv: 71 | for pkg in ('virtualenv', 'lettuce'): 72 | try: 73 | __import__(pkg) 74 | except ImportError: 75 | call(['pyg', 'install', pkg]) 76 | 77 | 78 | if __name__ == '__main__': 79 | main() -------------------------------------------------------------------------------- /pyg/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '1.0' 2 | 3 | 4 | import copy_reg, types 5 | 6 | def _pickle_method(method): 7 | func_name = method.im_func.__name__ 8 | obj = method.im_self 9 | cls = method.im_class 10 | return _unpickle_method, (func_name, obj, cls) 11 | 12 | def _unpickle_method(func_name, obj, cls): 13 | for cls in cls.mro(): 14 | try: 15 | func = cls.__dict__[func_name] 16 | except KeyError: 17 | pass 18 | else: 19 | break 20 | return func.__get__(obj, cls) 21 | 22 | copy_reg.pickle(types.MethodType, _pickle_method, _unpickle_method) 23 | 24 | 25 | def _suggest_cmd(argv): 26 | import difflib 27 | from pyg.log import logger 28 | from pyg.parser.parser import COMMANDS 29 | 30 | if not argv: 31 | argv.append('--help') 32 | return 33 | if argv[0] in ('-h', '--help'): 34 | argv[0] = 'help' 35 | cmd = argv[0] 36 | _proposals = [c for c in COMMANDS if c.startswith(cmd)] 37 | if len(_proposals) == 1: 38 | argv[0] = _proposals[0] 39 | elif len(_proposals): 40 | logger.exit('Ambiguous command, {0!r} could be one of:\n\t{1}'.format(cmd, 41 | '\n\t'.join(_proposals)) 42 | ) 43 | else: 44 | close = '\n\t'.join(difflib.get_close_matches(cmd, COMMANDS, cutoff=0.3)) 45 | logger.exit('{0!r} is not a Pyg command.\n\nDid you mean one of these?\n\t{1}'.format(cmd, close)) 46 | 47 | def _clean_argv(argv): 48 | from pyg.log import logger 49 | from pyg.core import args_manager 50 | 51 | precmd = argv[:2] 52 | if '--no-colors' in precmd or args_manager['global']['no_colors']: 53 | logger.disable_colors() 54 | argv.remove('--no-colors') 55 | if '-d' in precmd or '--debug' in precmd: 56 | logger.level = logger.DEBUG 57 | try: 58 | argv.remove('-d') 59 | except ValueError: 60 | argv.remove('--debug') 61 | elif '--verbose' in precmd: 62 | logger.level = logger.VERBOSE 63 | argv.remove('--verbose') 64 | 65 | 66 | def main(argv=None): 67 | import sys 68 | import urllib2 69 | 70 | try: 71 | ## If Python fails to import pyg we just add this directory to 72 | ## sys.path so we don't have to worry whether Pyg is installed or not. 73 | __import__('pyg') 74 | except ImportError: 75 | sys.path.insert(0, '..') 76 | from pyg.parser.parser import init_parser, load_options 77 | from pyg.core import PygError, InstallationError, AlreadyInstalled, args_manager 78 | from pyg.log import logger 79 | 80 | try: 81 | # Output Pyg version when `-v, --version` is specified 82 | if len(sys.argv) == 2 and sys.argv[-1] in ('-v', '--version'): 83 | logger.info(__version__) 84 | sys.exit(0) 85 | parser = init_parser(__version__) 86 | argv = argv or sys.argv[1:] 87 | # we have to remove -d, --debug and --verbose to make 88 | # _suggest_cmd work 89 | _clean_argv(argv) 90 | _suggest_cmd(argv) 91 | args = parser.parse_args(argv) 92 | load_options() 93 | parser.dispatch(argv=argv) 94 | except (PygError, InstallationError, ValueError) as e: 95 | sys.exit(1) 96 | except AlreadyInstalled: 97 | sys.exit(0) 98 | except urllib2.HTTPError as e: 99 | logger.exit('HTTPError: {0}'.format(e.msg)) 100 | except urllib2.URLError as e: 101 | logger.exit('urllib error: {0}'.format(e.reason if hasattr(e, 'reason') else e.msg)) 102 | except KeyboardInterrupt: 103 | logger.exit('Process interrupted...') 104 | except Exception as e: 105 | if logger.level == logger.DEBUG: 106 | raise 107 | logger.exit('Unknown error occurred: {0}'.format(e)) 108 | 109 | sys.exit(0) 110 | 111 | if __name__ == '__main__': 112 | main() 113 | -------------------------------------------------------------------------------- /pyg/freeze.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import distutils 3 | 4 | from pyg import __version__ 5 | from pyg.req import Requirement 6 | from pyg.web import get_versions 7 | from pyg.utils import is_installed, installed_distributions 8 | from pyg.locations import under_virtualenv, ALL_SITE_PACKAGES, BIN 9 | 10 | 11 | __all__ = ['freeze', 'list_releases', 'site_info'] 12 | 13 | 14 | def freeze(): 15 | '''Freeze the current environment (i.e. all installed packages).''' 16 | 17 | packages = [] 18 | for dist in installed_distributions(): 19 | packages.append('{0.project_name}=={0.version}'.format(dist)) 20 | return sorted(packages) 21 | 22 | def list_releases(name): 23 | '''List all releases for the given requirement.''' 24 | 25 | name = name[:-1] + '_' if name.endswith('-') else name 26 | res = [] 27 | for v in get_versions(Requirement(name)): 28 | v = str(v) 29 | if is_installed('{0}=={1}'.format(name, v)): 30 | res.append((v, True)) 31 | else: 32 | res.append((v, False)) 33 | return res 34 | 35 | def site_info(): 36 | '''Return some site information''' 37 | 38 | template = '''# Python version: {py_version!r} 39 | # Python version info: {py_version_info!r} 40 | # Python Prefix: {prefix!r} 41 | # Platform: {platform!r} 42 | # Pyg version: {pyg_version!r} 43 | # Inside a virtualenv: {in_env} 44 | # Site-Packages: {all_site!r} 45 | # Bin: {bin!r} 46 | 47 | ''' 48 | 49 | return template.format( 50 | py_version=sys.version.replace('\n', ''), 51 | py_version_info='.'.join(str(v) for v in sys.version_info), 52 | platform=distutils.util.get_platform(), 53 | prefix=sys.prefix, 54 | pyg_version=__version__, 55 | in_env=under_virtualenv(), 56 | all_site=', '.join(ALL_SITE_PACKAGES), 57 | bin=BIN 58 | ) -------------------------------------------------------------------------------- /pyg/locations.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import site 4 | import platform 5 | 6 | 7 | __all__ = ['under_virtualenv', 'INSTALL_DIR', 'USER_SITE', 'ALL_SITE_PACKAGES', 8 | 'EASY_INSTALL', 'PYG_LINKS', 'BIN', 'HOME', 'PYG_HOME', 'CFG_FILES', 9 | 'PYTHON_VERSION'] 10 | 11 | 12 | def under_virtualenv(): 13 | return hasattr(sys, 'real_prefix') 14 | 15 | PY_VERSION_INFO = sys.version_info[:2] 16 | PYTHON_VERSION = ''.join(map(str, PY_VERSION_INFO)) 17 | 18 | INSTALL_DIR = None 19 | if hasattr(site, 'getsitepackages'): 20 | INSTALL_DIR = site.getsitepackages()[0] 21 | USER_SITE = site.getusersitepackages() 22 | ALL_SITE_PACKAGES = site.getsitepackages() + [USER_SITE] 23 | 24 | # XXX: WORKAROUND for older python versions and some broken virtualenvs 25 | ## we have to guess site packages location... 26 | if INSTALL_DIR is None or not os.path.exists(INSTALL_DIR): 27 | USER_SITE = site.USER_SITE 28 | INSTALL_DIR = None 29 | system = platform.system() 30 | if system == 'Linux': 31 | tmp = ['{0}/lib/python{1}.{2}/'.format(sys.prefix, *PY_VERSION_INFO)] 32 | if not under_virtualenv(): 33 | tmp.append('{0}/local/lib/python{1}.{2}/'.format(sys.prefix, *PY_VERSION_INFO)) 34 | for dir in tmp: 35 | d, s = map(os.path.join, (dir, dir), ('dist-packages', 'site-packages')) 36 | if os.path.exists(d): 37 | INSTALL_DIR = d 38 | break 39 | elif os.path.exists(s): 40 | INSTALL_DIR = s 41 | break 42 | elif system == 'Windows': 43 | tmp = os.path.join(sys.prefix, 'site-packages') 44 | if os.path.exists(tmp): 45 | INSTALL_DIR = tmp 46 | if INSTALL_DIR is None: 47 | from distutils.sysconfig import get_python_lib 48 | INSTALL_DIR = get_python_lib(True) 49 | 50 | if under_virtualenv(): 51 | ## Under virtualenv USER_SITE is the same as INSTALL_DIR 52 | USER_SITE = INSTALL_DIR 53 | ALL_SITE_PACKAGES = [INSTALL_DIR] 54 | local_site = os.path.join(sys.prefix, 'local', 'lib', 55 | 'python{0}.{1}'.format(*PY_VERSION_INFO), 56 | os.path.basename(INSTALL_DIR) 57 | ) 58 | if 'local' not in INSTALL_DIR.split(os.sep) and os.path.exists(local_site): 59 | ALL_SITE_PACKAGES.append(local_site) 60 | else: 61 | ALL_SITE_PACKAGES = [INSTALL_DIR, USER_SITE] 62 | 63 | #try: 64 | # INSTALL_DIR = sorted([p for p in sys.path if p.endswith('dist-packages')], 65 | # key=lambda i: 'local' in i, reverse=True)[0] 66 | #except IndexError: 67 | # pass 68 | #if not INSTALL_DIR: ## Are we on Windows? 69 | # try: 70 | # INSTALL_DIR = sorted([p for p in sys.path if p.endswith('site-packages')], 71 | # key=lambda i: 'local' in i, reverse=True)[0] 72 | # except IndexError: 73 | # pass 74 | #if not INSTALL_DIR: ## We have to use /usr/lib/pythonx.y/dist-packages or something similar 75 | # from distutils.sysconfig import get_python_lib 76 | # INSTALL_DIR = get_python_lib() 77 | 78 | 79 | EASY_INSTALL = os.path.join(INSTALL_DIR, 'easy-install.pth') 80 | if not os.path.exists(EASY_INSTALL): 81 | d = os.path.dirname(EASY_INSTALL) 82 | try: 83 | if not os.path.exists(d): 84 | os.makedirs(d) 85 | open(EASY_INSTALL, 'w').close() 86 | ## We do not have root permissions... 87 | except IOError: 88 | ## So we do nothing! 89 | pass 90 | 91 | PYG_LINKS = os.path.join(USER_SITE, 'pyg-links.pth') 92 | 93 | BIN2 = None 94 | if platform.system() == 'Windows': 95 | BIN = os.path.join(sys.prefix, 'Scripts') 96 | if not os.path.exists(BIN): 97 | BIN = os.path.join(sys.prefix, 'bin') 98 | else: 99 | BIN = os.path.join(sys.prefix, 'bin') 100 | ## Forcing to use /usr/local/bin on standard Mac OS X 101 | if sys.platform[:6] == 'darwin' and sys.prefix[:16] == '/System/Library/': 102 | BIN = '/usr/local/bin' 103 | local_bin = os.path.join(sys.prefix, 'local', 'bin') 104 | if 'local' not in BIN.split(os.sep) and os.path.exists(local_bin): 105 | BIN2 = local_bin 106 | 107 | 108 | ## If we are running on a Unix system and we are in a SUDO session the `SUDO_UID` 109 | ## environment variable will be set. We can use that to get the user's home 110 | ## We also set to None all the variables that depend on HOME. 111 | 112 | ## Look for environment variables: HOME, PYG_CONFIG_FILE, 113 | HOME = os.getenv('HOME') 114 | CFG_FILES = os.getenv('PYG_CONFIG_FILE') 115 | if CFG_FILES is not None: 116 | CFG_FILES = CFG_FILES.split(':') 117 | else: 118 | CFG_FILES = [] 119 | CFG_FILES.append(os.path.join(os.getcwd(), 'pyg.conf')) 120 | 121 | ## Look for SUDO_UID environment variable (Unix) 122 | _sudo_uid = os.getenv('SUDO_UID') 123 | if _sudo_uid: 124 | import pwd 125 | _sudo_uid = int(_sudo_uid) 126 | HOME = pwd.getpwuid(_sudo_uid).pw_dir 127 | if HOME is not None: 128 | CFG_FILES.append(os.path.join(HOME, 'pyg.conf')) 129 | 130 | ## Here is Pyg's HOME directory 131 | ## If it does not exists we create it 132 | PYG_HOME = os.getenv('PYG_HOME') 133 | if PYG_HOME is None and HOME is not None: 134 | PYG_HOME = os.path.join(HOME, '.pyg') 135 | if PYG_HOME and not os.path.exists(PYG_HOME): 136 | os.makedirs(PYG_HOME) 137 | ## PACKAGES_CACHE has been removed because with `pkg_resources.working_set` we 138 | ## don't need a cache 139 | if PYG_HOME is not None: 140 | CFG_FILES.append(os.path.join(PYG_HOME, 'pyg.conf')) 141 | -------------------------------------------------------------------------------- /pyg/log.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import logging 4 | 5 | 6 | __all__ = ['logger'] 7 | 8 | try: 9 | from colorama import init, Fore, Style 10 | init(autoreset=False) 11 | colors = { 12 | 'good' : Fore.GREEN, 13 | 'bad' : Fore.RED, 14 | 'vgood' : Fore.GREEN + Style.BRIGHT, 15 | 'vbad' : Fore.RED + Style.BRIGHT, 16 | 17 | 'std' : '', # Do not color "standard" text 18 | 'warn' : Fore.YELLOW + Style.BRIGHT, 19 | 'reset' : Style.RESET_ALL, 20 | } 21 | except ImportError: 22 | colors = { 23 | 'good' : '', 24 | 'bad' : '', 25 | 'vgood' : '', 26 | 'vbad' : '', 27 | 28 | 'std' : '', 29 | 'warn' : '', 30 | 'reset' : '', 31 | } 32 | 33 | 34 | def get_console_width(): 35 | """ 36 | Return width of available window area. Autodetection works for 37 | Windows and POSIX platforms. Returns 80 for others 38 | 39 | Code from http://bitbucket.org/techtonik/python-wget 40 | """ 41 | 42 | if os.name == 'nt': 43 | STD_INPUT_HANDLE = -10 44 | STD_OUTPUT_HANDLE = -11 45 | STD_ERROR_HANDLE = -12 46 | 47 | # get console handle 48 | from ctypes import windll, Structure, byref 49 | try: 50 | from ctypes.wintypes import SHORT, WORD, DWORD 51 | except ImportError: 52 | # workaround for missing types in Python 2.5 53 | from ctypes import ( 54 | c_short as SHORT, c_ushort as WORD, c_ulong as DWORD) 55 | console_handle = windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE) 56 | 57 | # CONSOLE_SCREEN_BUFFER_INFO Structure 58 | class COORD(Structure): 59 | _fields_ = [("X", SHORT), ("Y", SHORT)] 60 | 61 | class SMALL_RECT(Structure): 62 | _fields_ = [("Left", SHORT), ("Top", SHORT), 63 | ("Right", SHORT), ("Bottom", SHORT)] 64 | 65 | class CONSOLE_SCREEN_BUFFER_INFO(Structure): 66 | _fields_ = [("dwSize", COORD), 67 | ("dwCursorPosition", COORD), 68 | ("wAttributes", WORD), 69 | ("srWindow", SMALL_RECT), 70 | ("dwMaximumWindowSize", DWORD)] 71 | 72 | sbi = CONSOLE_SCREEN_BUFFER_INFO() 73 | ret = windll.kernel32.GetConsoleScreenBufferInfo(console_handle, byref(sbi)) 74 | if ret == 0: 75 | return 0 76 | return sbi.srWindow.Right+1 77 | 78 | elif os.name == 'posix': 79 | from fcntl import ioctl 80 | from termios import TIOCGWINSZ 81 | from array import array 82 | 83 | winsize = array("H", [0] * 4) 84 | try: 85 | ioctl(sys.stdout.fileno(), TIOCGWINSZ, winsize) 86 | except IOError: 87 | pass 88 | return (winsize[1], winsize[0])[0] 89 | 90 | return 80 91 | 92 | 93 | class Logger(object): 94 | 95 | VERBOSE = logging.DEBUG - 1 96 | DEBUG = logging.DEBUG 97 | INFO = logging.INFO 98 | WARN = logging.WARNING 99 | ERROR = logging.ERROR 100 | FATAL = logging.FATAL 101 | 102 | ## This attribute is set to True when the user does not want colors 103 | ## by __init__.py 104 | _NO_COLORS = False 105 | 106 | def __init__(self,level=None): 107 | self.indent = 0 108 | self.level = level or Logger.INFO 109 | self._stack = [''] 110 | self.enabled = True 111 | 112 | def disable_colors(self): 113 | self._NO_COLORS = True 114 | for k in colors.keys(): 115 | colors[k] = '' 116 | 117 | def newline(self): 118 | '''Print a newline character (\n) on Standard Output.''' 119 | 120 | sys.stdout.write('\n') 121 | 122 | def raise_last(self, exc): 123 | raise exc(self.last_msg) 124 | 125 | @property 126 | def last_msg(self): 127 | return self._stack[-1] 128 | 129 | def ask(self, message=None, bool=None, choices=None, dont_ask=False): 130 | if bool is not None: 131 | if bool in (True, False) or (isinstance(bool, (list, tuple)) and len(bool) == 1): 132 | if bool == False: 133 | txt = "Cancel" 134 | elif bool == True: 135 | txt = "OK" 136 | else: 137 | txt = bool[0] 138 | self.log(self.info, 'std', "%s, %s..."%(message, txt), addn=False) 139 | if not dont_ask: 140 | raw_input() 141 | return 142 | else: 143 | if dont_ask: 144 | self.log(self.info, 'std', '%s ? Yes'%message) 145 | return True 146 | 147 | while True: 148 | self.log(self.info, 'std', "yes: "+bool[0]) 149 | self.log(self.info, 'std', "no: "+bool[1]) 150 | try: 151 | self.log(self.info, 'std', '%s ? (y/[n]) '%message, addn=False) 152 | ans = raw_input() 153 | except Exception: 154 | continue 155 | 156 | # default choice : no 157 | if not ans.strip(): 158 | return False 159 | 160 | if ans not in 'yYnN': 161 | continue 162 | 163 | return ans in 'yY' 164 | 165 | if choices: 166 | if isinstance(choices, dict): 167 | _data = choices 168 | choices = choices.keys() 169 | else: 170 | _data = None 171 | 172 | self.log(self.info, 'std', message) 173 | for n, choice in enumerate(choices): 174 | self.log(self.info, 'std', "%2d - %s"%(n+1, choice)) 175 | 176 | while True: 177 | try: 178 | ans = input('Your choice ? ') 179 | except Exception: 180 | self.log(self.info, 'std', "Please enter selected option's number.") 181 | continue 182 | if ans < 0 or ans > len(choices): 183 | continue 184 | break 185 | 186 | idx = choices[ans-1] 187 | return (_data[idx] if _data else idx) 188 | 189 | def verbose(self, msg, *a, **kw): 190 | self.log(self.VERBOSE, 'std', msg, *a, **kw) 191 | 192 | def debug(self, msg, *a, **kw): 193 | self.log(self.DEBUG, 'std', msg, *a, **kw) 194 | 195 | def info(self, msg, *a, **kw): 196 | self.log(self.INFO, 'std', msg, *a, **kw) 197 | 198 | def success(self, msg, *a, **kw): 199 | self.log(self.INFO, 'good', msg, *a, **kw) 200 | 201 | def warn(self, msg, *a, **kw): 202 | self.log(self.WARN, 'warn', msg, *a, **kw) 203 | 204 | def error(self, msg, *a, **kw): 205 | self.log(self.ERROR, 'bad', msg, *a, **kw) 206 | exc = kw.get('exc', None) 207 | if exc is not None: 208 | raise exc(self.last_msg) 209 | 210 | def fatal(self, msg, *a, **kw): 211 | self.log(self.FATAL, 'vbad', msg, *a, **kw) 212 | exc = kw.get('exc', None) 213 | if exc is not None: 214 | raise exc(self.last_msg) 215 | 216 | def exit(self, msg=None, status=1): 217 | if msg != None: 218 | self.log(self.FATAL, 'vbad', msg) 219 | sys.exit(status) 220 | 221 | def log(self, level, col, msg, *a, **kw): 222 | ''' 223 | This is the base function that logs all messages. This function prints a newline character too, 224 | unless you specify ``addn=False``. When the message starts with a return character (\r) it automatically 225 | cleans the line. 226 | ''' 227 | 228 | if level >= self.level and self.enabled: 229 | std = sys.stdout 230 | if level >= self.ERROR: 231 | std = sys.stderr 232 | 233 | ## We can pass to logger.log any object: it must have at least 234 | ## a __repr__ or a __str__ method. 235 | msg = str(msg) 236 | if msg.startswith('\r') or self.last_msg.startswith('\r'): 237 | ## We have to clear the line in case this message is longer than 238 | ## the previous 239 | 240 | std.write('\r' + ' ' * get_console_width()) 241 | msg = '\r' + ' ' * self.indent + msg.lstrip('\r').format(*a) 242 | else: 243 | try: 244 | msg = ' ' * self.indent + msg.format(*a) 245 | except KeyError: 246 | msg = ' ' * self.indent + msg 247 | 248 | col, col_reset = colors[col], colors['reset'] 249 | if self._NO_COLORS: 250 | col, col_reset = '', '' 251 | std.write(col + msg + col_reset) 252 | 253 | ## Automatically adds a newline character 254 | if kw.get('addn', True): 255 | self.newline() 256 | 257 | ## flush() makes the log immediately readable 258 | std.flush() 259 | self._stack.append(msg) 260 | 261 | 262 | logger = Logger() 263 | if __name__ == '__main__': 264 | print logger.ask("Beware, you enter a secret place", bool=True) 265 | print logger.ask("Sorry, can't install this package", bool=False) 266 | print logger.ask("Sorry, can't install this package", bool=['Press any key to continue']) 267 | print logger.ask('Proceed', bool=('remove files', 'cancel')) 268 | print logger.ask('Do you want to upgrade', bool=('upgrade version', 'keep working version')) 269 | print logger.ask('Installation method', choices=('Egg based', 'Flat directory')) 270 | print logger.ask('some dict', choices={'choice a': 'a', 'choice b': 'b', 'choice c': 'c'}) 271 | -------------------------------------------------------------------------------- /pyg/pack.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Create Packs, like Zicbee Pack (at http://pypi.python.org/zicbee). 3 | Inspired by zicbee-workshop's manage.py 4 | ''' 5 | 6 | import re 7 | import os 8 | import sys 9 | import shutil 10 | import platform 11 | 12 | from pyg.log import logger 13 | from pyg.web import highest_version 14 | from pyg.inst import Bundler 15 | from pyg.core import PygError 16 | from pyg.utils import TempDir, ZipFile, call_setup, print_output 17 | 18 | 19 | PY_RUN_CODE = '''#!/usr/bin/env python 20 | # Python executable file auto-generated by Pyg 21 | 22 | # import pack file to extend sys.path 23 | import {req_name}_pack 24 | import sys 25 | 26 | # run! 27 | if __name__ == '__main__': 28 | import {mod_name} 29 | sys.exit({mod_name}.{callable}()) 30 | ''' 31 | 32 | PACK_PY = '''#!/usr/bin/env python 33 | # Python file auto-generated by Pyg 34 | # Import this file if you want to use packed modules in 35 | # your code without unpacking the egg 36 | 37 | import os 38 | import sys 39 | 40 | exists = os.path.exists 41 | j = os.path.join 42 | # we don't care about order 43 | paths = set(sys.path) 44 | paths.add(os.getcwd()) 45 | 46 | my_dir = [p for p in paths if exists(j(p, {egg!r}))] 47 | if not my_dir: 48 | print "Unable to find {egg!r} file !" 49 | raise SystemExit() 50 | 51 | # keep the first egg found 52 | egg_path = os.path.join(my_dir[0], {egg!r}) 53 | 54 | # extend sys.path 55 | sys.path.insert(0, egg_path) 56 | ''' 57 | 58 | 59 | ## Now it is still a placeholder 60 | def _gen_executable(req_name, code): 61 | import_tok, func = map(str.strip, code.split(':')) 62 | return PY_RUN_CODE.format( 63 | req_name=req_name, 64 | mod_name=import_tok, 65 | callable=func, 66 | ) 67 | 68 | 69 | class Packer(object): 70 | 71 | EMPTY, INI, CAT, SCAT, MERGE, PACKAGES = range(6) 72 | REQ = 42 73 | 74 | EGG_FILES = { 75 | 'dependency_links.txt': MERGE, 76 | 'entry_points.txt': INI, 77 | 'not-zip-safe': EMPTY, 78 | 'PKG-INFO': REQ, 79 | 'requires.txt': EMPTY, 80 | 'SOURCES.txt': SCAT, 81 | 'top_level.txt': SCAT, 82 | 'packages.txt': PACKAGES, 83 | 'spec/depend': """metadata_version = '1.1' 84 | name = {req!r} 85 | version = {req_version!r} 86 | build = 1 87 | 88 | arch = {arch} 89 | platform = {platform!r} 90 | osdist = None 91 | python = {python_version!r} 92 | packages = [ 93 | {packages} 94 | ] 95 | """ 96 | } 97 | 98 | def __init__(self, req, bundle_name, dest=None): 99 | self.req = req 100 | self.bundle_name = bundle_name 101 | self.pack_name = bundle_name 102 | if not self.pack_name.endswith('.zip'): 103 | self.pack_name = self.pack_name + '.zip' 104 | self.dest = dest 105 | self.bundled = {} 106 | self.entry_points = {} 107 | 108 | def _bundle_callback(self, req, sdist): 109 | sdist._name, sdist._version = req.name, req.version 110 | self.bundled[sdist.name.replace('-', '_')] = sdist 111 | 112 | def packages(self, spaces=True, commas=True): 113 | s = ' ' if spaces else '' 114 | c = ',' if commas else '' 115 | return (c + '\n' + s).join('{0.name} {0.version}'.format(dist) for dist in self.bundled.values()) 116 | 117 | def _fill_metadata(self): 118 | content = self.EGG_FILES['spec/depend'] 119 | req_version = self.req.version 120 | try: 121 | if req_version is None: 122 | req_version = self.bundled[self.req.name].version 123 | except KeyError: 124 | pass 125 | if req_version is None: 126 | req_version = highest_version(self.req) 127 | 128 | self.EGG_FILES['spec/depend'] = content.format( 129 | req=self.req.name, 130 | req_version=req_version, 131 | arch=platform.machine(), 132 | platform=sys.platform, 133 | python_version='.'.join(map(str, sys.version_info[:2])), 134 | packages=self.packages() 135 | ) 136 | 137 | def _safe_readlines(self, dist, filename): 138 | try: 139 | return dist.file(filename) 140 | except KeyError: 141 | return {} 142 | 143 | def _mk_egg_info(self, egg_info_dir): 144 | ## This function returns the EGG-INFO path 145 | logger.info('Generating EGG-INFO...') 146 | with TempDir(dont_remove=True) as tempdir: 147 | egg_info = os.path.join(tempdir, 'EGG-INFO') 148 | # copy egg-info from egg_info_dir to spec/ 149 | shutil.copytree(egg_info_dir, os.path.join(egg_info, 'spec')) 150 | 151 | self._fill_metadata() 152 | for mfile, data in self.EGG_FILES.iteritems(): 153 | deps = dict((name, self._safe_readlines(dist, mfile)) for name, dist in self.bundled.iteritems()) 154 | if data == self.EMPTY: 155 | content = '' 156 | elif data == self.PACKAGES: 157 | content = self.packages(False, False) 158 | elif data == self.INI: 159 | ini_file = {} 160 | for dep, content in deps.iteritems(): 161 | for section, options in content.iteritems(): 162 | if section in ini_file: 163 | ini_file[section].update(options) 164 | else: 165 | ini_file[section] = options 166 | result = [] 167 | for section in sorted(ini_file.iterkeys()): 168 | result.append('[{0}]'.format(section)) 169 | for option, value in ini_file[section].iteritems(): 170 | result.append('{0} = {1}'.format(option, value)) 171 | result.append('\n') 172 | content = '\n'.join(result) 173 | elif data in (self.CAT, self.SCAT): 174 | lines = [] 175 | for dep, content in deps.iteritems(): 176 | lines.extend(content) 177 | if data == self.SCAT: 178 | lines.sort() 179 | content = '\n'.join(lines) 180 | elif data == self.MERGE: 181 | d = set() 182 | for dep, content in deps.iteritems(): 183 | d.update(content) 184 | content = '\n'.join(sorted(d)) 185 | elif data == self.REQ: 186 | content = '\n'.join('{0}: {1}'.format(opt, val) \ 187 | for opt, val in self._safe_readlines(self.bundled[self.req.name], mfile).iteritems()) 188 | else: 189 | content = data 190 | 191 | if '/' in mfile: 192 | # Make it platform-indipendent 193 | parts = mfile.split('/') 194 | mfile = os.path.join(*parts) 195 | path = os.path.join(egg_info, *parts[:-1]) 196 | if not os.path.exists(path): 197 | os.makedirs(path) 198 | with open(os.path.join(egg_info, mfile), 'w') as f: 199 | # FIXME: this is a bit ugly 200 | if mfile == 'entry_points.txt': 201 | try: 202 | raw = content.split('[console_scripts]')[1].split('[', 1)[0].split('\n') 203 | except IndexError: 204 | raw = [] 205 | for line in raw: 206 | line = line.strip() 207 | if not line: 208 | continue 209 | cmd, code = (x.strip() for x in line.split('=')) 210 | self.entry_points[cmd] = code 211 | f.write(content) 212 | 213 | # this is for utils.ZipFile.add_to_archive: it needs the temporary 214 | # directory length 215 | tempdir_len = len(tempdir) 216 | return egg_info, tempdir_len 217 | 218 | def _gen_eggs(self, source_dir, egg_dir, egg_info_dir): 219 | ## Old method 220 | r = re.compile(r'-py\d\.\d') 221 | def sep_egg_info(arch_path): 222 | arch = os.path.basename(arch_path) 223 | eggname = arch[:r.search(arch).start()] 224 | with ZipFile(arch_path) as z: 225 | z.extractall(os.path.join(egg_dir, eggname)) 226 | 227 | ## New method 228 | def no_egg_info(arch_path): 229 | with ZipFile(arch_path) as z: 230 | z.extractall(egg_dir) 231 | 232 | with TempDir() as tempdir: 233 | dist_dir = os.listdir(source_dir) 234 | dist_no = float(len(dist_dir)) 235 | # start progress 236 | logger.info('\rGenerating eggs...', addn=False) 237 | for i, dist in enumerate(dist_dir): 238 | code, output = call_setup(os.path.join(source_dir, dist), ['bdist_egg', '-d', tempdir]) 239 | if code != 0: 240 | logger.fatal('Error: cannot generate egg for {0}', dist) 241 | print_output(output, 'setup.py bdist_egg') 242 | raise PygError('cannot generate egg for {0}'.format(dist)) 243 | arch = os.path.join(tempdir, os.listdir(tempdir)[0]) 244 | no_egg_info(arch) 245 | os.remove(arch) 246 | logger.info('\rGenerating eggs... [{0} - {1:.1%}]', dist, i / dist_no, addn=False) 247 | # copy metadata file to egg_info_dir location 248 | shutil.move(os.path.join(egg_dir, 'EGG-INFO'), os.path.join(egg_info_dir, dist)) 249 | # complete the progress 250 | logger.info('\rGenerating eggs... 100%') 251 | 252 | def gen_pack(self, exclude=[], use_develop=False): 253 | # This is where to download all packages 254 | # and where to build the pack 255 | with TempDir() as tempdir: 256 | # This is where to store the egg 257 | with TempDir() as bundle_dir: 258 | logger.info('Generating the bundle...') 259 | b = Bundler([self.req], self.bundle_name, exclude=exclude, \ 260 | callback=self._bundle_callback, use_develop=use_develop) 261 | 262 | # we download packages without creating the bundle to build 263 | # eggs 264 | b._download_all(tempdir) 265 | b._clean(tempdir) 266 | 267 | bundle = os.path.join(bundle_dir, self.req.name) + '.egg' 268 | with ZipFile(bundle, mode='w') as egg: 269 | # Create a new directory to store unpacked eggs 270 | with TempDir() as egg_dir: 271 | # where to store egg-info 272 | with TempDir() as egg_info_dir: 273 | # generate eggs (through distributions' setups) 274 | self._gen_eggs(tempdir, egg_dir, egg_info_dir) 275 | egg.add_to_archive(egg_dir, len(egg_dir)) 276 | # generate egg-info (merging) 277 | egg_info, tl = self._mk_egg_info(egg_info_dir) 278 | egg.add_to_archive(egg_info, tl) 279 | 280 | pack = os.path.join(tempdir, self.pack_name) 281 | eggname = self.req.name + '.egg' 282 | folder_name = '{0.name}-{0.version}'.format(self.bundled[self.req.name]) 283 | with ZipFile(pack, mode='w') as z: 284 | z.write(bundle, '/'.join([folder_name, eggname])) 285 | # write executable files 286 | for command_name, code in self.entry_points.iteritems(): 287 | z.add_executable('/'.join((folder_name, 'run_{0}.py'.format(command_name))), 288 | _gen_executable(self.req.name, code) 289 | ) 290 | # write pack file 291 | z.writestr('/'.join([folder_name, '{0}_pack.py'.format(self.req.name)]), 292 | PACK_PY.format(egg=eggname) 293 | ) 294 | dest = os.path.join(self.dest, self.pack_name) 295 | if os.path.exists(dest): 296 | os.remove(dest) 297 | shutil.move(pack, self.dest) 298 | -------------------------------------------------------------------------------- /pyg/parser/__init__.py: -------------------------------------------------------------------------------- 1 | ## Simply nothing... -------------------------------------------------------------------------------- /pyg/parser/_opts.py: -------------------------------------------------------------------------------- 1 | import re 2 | import os 3 | import functools 4 | import pkg_resources 5 | 6 | from pkgtools.pypi import PyPIXmlRpc 7 | 8 | from pyg.vcs import vcs 9 | from pyg.log import logger 10 | from pyg.req import Requirement 11 | from pyg.pack import Packer 12 | from pyg.freeze import freeze, list_releases, site_info 13 | from pyg.core import PygError, Version, args_manager 14 | from pyg.inst import Installer, Uninstaller, Updater, Bundler 15 | from pyg.utils import TempDir, is_installed, unpack 16 | from pyg.web import ReqManager 17 | 18 | 19 | def check_permissions(dir): 20 | if not os.path.exists(dir): 21 | logger.fatal('Error: installation directory {0} does not exist', dir, exc=PygError) 22 | try: 23 | path = os.path.join(dir, 'pyg-permissions-test.pth') 24 | with open(path, 'w'): 25 | pass 26 | os.remove(path) 27 | ## FIXME: Do we need OSError too? 28 | except (IOError, OSError): 29 | return False 30 | else: 31 | return True 32 | 33 | def check_and_exit(): 34 | dir = os.path.abspath(args_manager['install']['install_dir']) 35 | if not check_permissions(dir): 36 | logger.exit('''Pyg cannot create new files in the installation directory. 37 | Installation directory was: 38 | 39 | {0} 40 | 41 | Perhaps your account does not have write access to this directory? If the 42 | installation directory is a system-owned directory, you may need to sign in 43 | as the administrator or "root" account. If you do not have administrative 44 | access to this machine, you may wish to choose a different installation 45 | directory, preferably one that is listed in your PYTHONPATH environment 46 | variable. 47 | 48 | If you need further information about Pyg command-line options visit: 49 | 50 | http://pyg.readthedocs.org/en/latest/cmdline.html 51 | or 52 | http://pyg-installer.co.nr 53 | '''.format(dir)) 54 | 55 | def _install_package_from_name(package, ignore=False): 56 | if os.path.exists(package) and not ignore: 57 | path = os.path.abspath(package) 58 | logger.info('Installing {0}', path) 59 | if os.path.isfile(path): 60 | return Installer.from_file(path) 61 | elif os.path.isdir(path): 62 | if not os.path.exists(os.path.join(path, 'setup.py')): 63 | raise PygError('{0} must contain the setup.py file', path) 64 | return Installer.from_dir(path) 65 | else: 66 | raise PygError('Cannot install the package: {0} is neither a file nor a directory', path) 67 | if package.startswith(('http://', 'https://')): 68 | return Installer.from_url(package) 69 | for s in ('git+', 'hg+', 'bzr+', 'svn+'): 70 | if package.startswith(s): 71 | with TempDir() as tempdir: 72 | return vcs(package, tempdir).install() 73 | return Installer(package).install() 74 | 75 | def install_func(packname, req_file, editable, ignore): 76 | check_and_exit() 77 | if editable: 78 | if len(packname) > 1: 79 | logger.error('Error: Unable to install multiple packages in editable mode') 80 | return 81 | package = packname[0] 82 | if os.path.exists(os.path.abspath(package)): 83 | package = 'dir+{0}#egg={1}'.format(os.path.abspath(package), 84 | os.path.basename(package)) 85 | return vcs(package).develop() 86 | if req_file: 87 | logger.info('Installing from requirements file') 88 | for rq in req_file: 89 | Installer.from_req_file(os.path.abspath(rq)) 90 | return 91 | if packname: 92 | for package in packname: 93 | _install_package_from_name(package, ignore) 94 | 95 | def remove_func(packname, req_file, yes, info, local): 96 | uninstaller = functools.partial(Uninstaller, yes=yes, local=local) 97 | if info: 98 | for p in packname: 99 | logger.info('{0}:', p) 100 | files = uninstaller(p).find_files() 101 | logger.indent = 8 102 | for path in files: 103 | logger.info(path) 104 | logger.indent = 0 105 | return 106 | check_and_exit() 107 | ## Little Easter egg... 108 | if len(packname) == 1 and packname[0] == 'yourself': 109 | return uninstaller('pyg').uninstall() 110 | if req_file: 111 | with open(os.path.abspath(req_file)) as f: 112 | for line in f: 113 | try: 114 | uninstaller(line.strip()).uninstall() 115 | except PygError: 116 | continue 117 | for p in packname: 118 | try: 119 | uninstaller(p).uninstall() 120 | except PygError: 121 | continue 122 | 123 | def check_func(name, info=False): 124 | INFO = '{0.project_name} - {0.version}\nInstalled in {0.location}' 125 | if not info: 126 | logger.info(is_installed(name)) 127 | return 128 | if info: 129 | try: 130 | dist = pkg_resources.get_distribution(name) 131 | logger.info(INFO.format(dist)) 132 | except pkg_resources.DistributionNotFound: 133 | logger.info(False) 134 | 135 | def site_func(count, no_info, file): 136 | f = freeze() 137 | if count: 138 | logger.info(str(len(f))) 139 | return 140 | f = '\n'.join(f) 141 | if not no_info: 142 | f = site_info() + f 143 | if file: 144 | with open(os.path.abspath(file), 'w') as req_file: 145 | req_file.write(f) 146 | return logger.info(f) 147 | 148 | def list_func(name): 149 | res = [] 150 | for v, inst in list_releases(name): 151 | if inst: 152 | res.append(v + '\tinstalled') 153 | else: 154 | res.append(v) 155 | return logger.info('\n'.join(res)) 156 | 157 | def search_func(query, exact, show_all_version, max_num): 158 | def _pypi_order(item): 159 | # this is the old implementation, that looks buggy (try on "sphinx") 160 | return item['_pypi_ordering'] 161 | 162 | def _pkgresources_order(item): 163 | return (item[0],) + item[2].v 164 | 165 | res = sorted(PyPIXmlRpc(index_url=args_manager['install']['index_url']).search({'name': query, 'summary': query}, 'or')) 166 | processed = {} 167 | for release in res: 168 | name, version, summary = release['name'], Version(release['version']), release['summary'] 169 | 170 | ## We have already parsed a different version 171 | if name in processed: 172 | if show_all_version: 173 | processed[name].append((version, summary)) 174 | elif version > processed[name][0][0]: 175 | processed[name] = [(version, summary)] 176 | continue 177 | ## This is the first time 178 | processed[name] = [(version, summary)] 179 | 180 | pattern = re.compile('$|'.join(query) + '$') 181 | results = [] 182 | for name, values in processed.iteritems(): 183 | try: 184 | _installed = Version(pkg_resources.get_distribution(name).version) 185 | except pkg_resources.DistributionNotFound: 186 | _installed = None 187 | except Exception: 188 | logger.warn('WARN: Cannot get package data for {0!r}', name) 189 | _installed = None 190 | if exact: 191 | if pattern.match(name) is None: 192 | continue 193 | 194 | for version in values: 195 | if not _installed: 196 | dec = '' 197 | elif _installed == version[0]: 198 | dec = '@' 199 | else: 200 | dec = '*' 201 | results.append((name, dec, version[0], version[1])) 202 | 203 | results = sorted(results, key=_pkgresources_order) 204 | results = results[:max_num or len(results)] 205 | output = '\n'.join('{0} {1}{2} - {3}'.format(name, dec, version, summary) for name, dec, version, summary in results) 206 | return logger.info(output) 207 | 208 | def download_func(args): 209 | pref = None 210 | if args.prefer: 211 | pref = ['.' + args.prefer.strip('.')] 212 | name = args.packname 213 | dest = args_manager['download']['download_dir'] 214 | unpk = args_manager['download']['unpack'] 215 | downloader = ReqManager(Requirement(name), pref) 216 | if args_manager['download']['dry']: 217 | res = downloader.download(None) 218 | else: 219 | res = downloader.download(dest) 220 | 221 | if args_manager['download']['md5']: 222 | for dl in res: 223 | logger.info('%(url)s md5: %(hash)s'%dl) 224 | if downloader.downloaded_name is None: 225 | logger.fatal('Error: Did not find any files for {0}', name, exc=PygError) 226 | if unpk: 227 | path = os.path.abspath(downloader.downloaded_name) 228 | logger.info('Unpacking {0} to {1}', os.path.basename(path), os.getcwd()) 229 | unpack(path) 230 | 231 | def update_func(): 232 | check_and_exit() 233 | up = Updater() 234 | up.update() 235 | 236 | def bundle_func(packages, bundlename, exclude, req_file, develop): 237 | def get_reqs(path): 238 | path = os.path.abspath(path) 239 | reqs = set() 240 | with open(path) as f: 241 | for line in f: 242 | line = line.strip() 243 | if not line or line.startswith('#'): 244 | continue 245 | reqs.add(line) 246 | return reqs 247 | if not packages and not req_file: 248 | logger.fatal('Error: You must specify at least one package', exc=PygError) 249 | reqs = [] 250 | if req_file: 251 | reqs = [Requirement(r) for f in req_file for r in get_reqs(f)] 252 | exclude = [Requirement(r) for r in (exclude or [])] 253 | bundlename = os.path.abspath(bundlename) 254 | dest, bundlename = os.path.dirname(bundlename), os.path.basename(bundlename) 255 | b = Bundler(map(Requirement, packages) + reqs, bundlename, exclude, use_develop=develop) 256 | b.bundle(dest=dest) 257 | 258 | def pack_func(package, packname, exclude, use_develop): 259 | packname = os.path.abspath(packname) 260 | dest, packname = os.path.dirname(packname), os.path.basename(packname) 261 | exclude = [Requirement(r) for r in (exclude or [])] 262 | Packer(Requirement(package), packname, dest).gen_pack(exclude, use_develop) 263 | 264 | def completion_func(commands, file): 265 | code = '''_pyg() 266 | { 267 | local cur prev opts 268 | COMPREPLY=() 269 | cur="${COMP_WORDS[COMP_CWORD]}" 270 | prev="${COMP_WORDS[COMP_CWORD-1]}" 271 | opts=%r 272 | COMPREPLY=($(compgen -W "${opts}" -- "${cur}")) 273 | } 274 | 275 | complete -o default -F _pyg pyg''' 276 | 277 | code %= ' '.join(commands) 278 | 279 | if not file: 280 | return logger.info(code) 281 | 282 | dir = os.path.dirname(file) 283 | # permissions test 284 | try: 285 | path = os.path.join(dir, 'pyg-test') 286 | with open(path, 'w') as f: 287 | f.write('pyg test') 288 | os.remove(path) 289 | except IOError: 290 | logger.fatal('Pyg cannot write files into {0}.\nTry to run it with root privileges.', dir, exc=PygError) 291 | 292 | with open(file, 'a') as f: 293 | f.write('\n{0}\n'.format(code)) 294 | os.system('source %s' % (file)) 295 | 296 | def shell_func(): 297 | check_and_exit() 298 | 299 | from pyg.parser.shell import PygShell 300 | PygShell().cmdloop() 301 | -------------------------------------------------------------------------------- /pyg/parser/formatter.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Pyg's own HelpFormatter for argparse. 3 | The standard help would be like this: 4 | 5 | usage: pyg [-h] [-d] [--verbose] [-v] 6 | 7 | {search,shell,help,list,update,remove,freeze,link,bundle,install, 8 | download,unlink,check} 9 | ... 10 | 11 | positional arguments: 12 | {search,shell,help,list,update,remove,freeze,link,bundle,install,download,unlink,check} 13 | install 14 | remove 15 | freeze 16 | link 17 | unlink 18 | list 19 | search 20 | check 21 | download 22 | update 23 | shell 24 | bundle 25 | help 26 | 27 | It repeats 3 times the commands without giving some useful information. 28 | With this modification Pyg's help is more comprehensive and useful: 29 | 30 | usage: pyg [-h] [-v] [-d] [--verbose] [command] args 31 | or: pyg command -h 32 | or: pyg command --help 33 | 34 | Available commands: 35 | install [-eUAngu] [-r ] [-i ] [-d ] [--no-scripts] [--no-data] packname 36 | Install a package 37 | 38 | bundle [-r ] [-e ] bundlename packages 39 | Create bundles (like Pip's ones) 40 | 41 | download [-u] [-d ] [-p ] packname 42 | Download a package 43 | 44 | remove [-yi] [-r ] packname 45 | Remove a package 46 | 47 | freeze [-c] [-f ] 48 | Freeze current environment (i.e. installed packages) 49 | 50 | check [-i] packname 51 | Check if a package is installed 52 | 53 | search [-ea] query 54 | Search PyPI 55 | 56 | unlink [-a] path 57 | Remove a previously added directory (with link) from PYTHONPATH 58 | 59 | list packname 60 | List all versions for a package 61 | 62 | update [-y] 63 | Check for updates for installed packages 64 | 65 | link path 66 | Add a directory to PYTHONPATH 67 | 68 | shell 69 | Fire up Pyg Shell 70 | 71 | help 72 | Show this help and exit 73 | ''' 74 | 75 | 76 | import re 77 | import argparse 78 | 79 | 80 | TEMPLATE = '''{0} 81 | 82 | Available commands: 83 | {1} 84 | ''' 85 | 86 | 87 | def _formatter(parser): 88 | 89 | class PygHelpFormatter(argparse.HelpFormatter): 90 | 91 | _argh_parser = None 92 | 93 | def _get_commands(self): 94 | commands = {} 95 | actions = [a for a in self._argh_parser._actions if isinstance(a, argparse._SubParsersAction)] 96 | for action in actions: 97 | ## List all subcommands (i.e. install, remove, etc.) and their 98 | ## options. None is the placeholder for an action's help (if there is) 99 | for subcommand, parser in action.choices.iteritems(): 100 | commands[subcommand] = [None, parser._actions] 101 | ## Look for actions' help 102 | for pseudo_action in action._choices_actions: 103 | if pseudo_action.dest in commands: 104 | commands[pseudo_action.dest][0] = pseudo_action.help 105 | return commands 106 | 107 | def _format_usage(self): 108 | return 'usage: pyg [-h] [-v] [-d] [--verbose] [--no-colors] [command] args\n' \ 109 | 'or: pyg command -h\n' \ 110 | 'or: pyg command --help' 111 | 112 | def _format_args(self): 113 | args = [] 114 | _spaces_re = re.compile(r'[ ]+') 115 | for command, help_actions in self._get_commands().iteritems(): 116 | help, actions = help_actions 117 | 118 | ## Split the action depending on their type: 119 | ## 120 | ## - no_value: -e, -g, -A, -U, etc. 121 | ## - with_value: -i , -r , etc. 122 | ## - one_opt: --no-scripts, --no-data, etc. 123 | ## - positional: packname, bundlename, etc. 124 | no_value, with_value, one_opt, positional = [], [], [], [] 125 | for action in actions: 126 | if isinstance(action, argparse._HelpAction): 127 | continue 128 | options = action.option_strings 129 | if not options: 130 | positional.append(action.dest) 131 | elif action.metavar: 132 | with_value.append((options[0], action.metavar)) 133 | else: 134 | if len(options) == 1: 135 | one_opt.append(options[0]) 136 | continue 137 | no_value.append(options[0]) 138 | 139 | line = '{0} [-{1}] {2} {3} {4}\n\t\t{5}'.format( 140 | command, 141 | ''.join(option.strip('-') for option in no_value), 142 | ' '.join('[{0} {1}]'.format(option, metavar) for option, metavar in with_value), 143 | ' '.join('[{0}]'.format(opt) for opt in one_opt), 144 | ' '.join(positional), 145 | help.strip() or '' 146 | ) 147 | 148 | ## Remove [-] in case there weren't options with no value 149 | line = line.replace('[-]', '') 150 | ## Replace multiple spaces 151 | line = _spaces_re.sub(' ', line) 152 | args.append(line.lstrip()) 153 | 154 | return '\t' + '\n\t'.join(sorted(args, key=lambda line: -len(line.split('\n\t\t')[0]))) + '\n' 155 | 156 | def format_help(self): 157 | return TEMPLATE.format( 158 | self._format_usage(), 159 | self._format_args() 160 | ) 161 | 162 | PygHelpFormatter._argh_parser = parser 163 | return PygHelpFormatter 164 | -------------------------------------------------------------------------------- /pyg/parser/parser.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This module contains the command-line parser. All imports are inside the functions 3 | because we don't want to execute code before the parser is created and when Pyg is 4 | used as a library. 5 | ''' 6 | 7 | ITERABLE_T = (list, tuple) 8 | COMMANDS = set(['install', 'remove', 'bundle', 'pack', 'download', 'update', 9 | 'search', 'list', 'site', 'check', 'shell', 'completion', 'help']) 10 | 11 | 12 | def load_options(): 13 | import os.path 14 | 15 | from pyg.core import args_manager 16 | from pyg.locations import CFG_FILES 17 | from pyg.log import logger 18 | 19 | if CFG_FILES: 20 | for cfg in CFG_FILES: 21 | if os.path.exists(cfg): 22 | logger.debug('Loading options from {0}', cfg) 23 | 24 | ## This is for potential warnings 25 | logger.indent = 8 26 | args_manager.load(cfg) 27 | logger.indent = 0 28 | break 29 | 30 | def init_parser(version=None): 31 | import os 32 | import _opts as opts 33 | from pyg.locations import INSTALL_DIR, USER_SITE 34 | from pyg.parser.formatter import _formatter 35 | from pyg.core import args_manager 36 | from argh import ArghParser, arg, command 37 | 38 | 39 | parser = ArghParser(prog='pyg') 40 | parser.add_argument('-d', '--debug', action='store_true', help='Set logger to DEBUG level') 41 | parser.add_argument('--verbose', action='store_true', help='Set logger to VERBOSE level') 42 | if version is not None: 43 | parser.add_argument('-v', '--version', action='version', version=version) 44 | parser.add_argument('--no-colors', action='store_true', help='Disable colors') 45 | #parser.add_argument('-i', '--index-url', default='http://pypi.python.org', metavar="", help='Base URL of Python Package Index (default to %(default)s)') 46 | 47 | 48 | @arg('packname', nargs='*') 49 | @arg('-e', '--editable', action='store_true', help='Install a package from an online repository in editable mode') 50 | @arg('-r', '--req-file', metavar='', action='append', help='Install packages from the specified requirement file') 51 | @arg('-U', '--upgrade', action='store_true', help='If the package is already installed re-install it again') 52 | @arg('-A', '--upgrade-all', action='store_true', help='Like -U, --upgrade, but install again dependencies too') 53 | @arg('-n', '--no-deps', action='store_true', help='Do not install dependencies') 54 | @arg('-g', '--ignore', action='store_true', help='Ignore local files or directories') 55 | @arg('-i', '--index-url', default='http://pypi.python.org', metavar='', help='Base URL of Python Package Index (default to %(default)s)') 56 | @arg('-d', '--install-dir', default=INSTALL_DIR, metavar='', help='Base installation directory') 57 | @arg('-u', '--user', action='store_true', help='Install to user site') 58 | @arg('--no-scripts', action='store_true', help='Do not install scripts') 59 | @arg('--no-data', action='store_true', help='Do not install data files') 60 | @arg('--force-egg-install', action='store_true', help='Allow installing eggs with a different Python version') 61 | def install(args): 62 | ''' 63 | Install a package 64 | ''' 65 | 66 | if args.no_deps: 67 | args_manager['install']['no_deps'] = True 68 | if args.upgrade: 69 | args_manager['install']['upgrade'] = True 70 | if args.no_scripts: 71 | args_manager['install']['no_scripts'] = True 72 | if args.no_data: 73 | args_manager['install']['no_data'] = True 74 | if args.ignore: 75 | args_manager['install']['ignore'] = True 76 | if args.force_egg_install: 77 | args_manager['install']['force_egg_install'] = True 78 | 79 | if isinstance(args.index_url, ITERABLE_T): 80 | args.index_url = args.index_url[0] 81 | args_manager['install']['packages_url'] = args.index_url + '/simple' 82 | args_manager['install']['index_url'] = args.index_url + '/pypi' 83 | 84 | if args.upgrade_all: 85 | args_manager['install']['upgrade_all'] = True 86 | args_manager['install']['upgrade'] = True 87 | if args.user: 88 | args_manager['install']['user'] = True 89 | args_manager['install']['install_dir'] = USER_SITE 90 | if args.install_dir != INSTALL_DIR: 91 | dir = os.path.abspath(args.install_dir) 92 | args_manager['install']['install_dir'] = dir 93 | if any(os.path.basename(dir) == p for p in args.packname): 94 | ## Automatically set ignore=True when INSTALL_DIR has the same 95 | ## name of one of the packages to install 96 | args_manager['install']['ignore'] = True 97 | opts.install_func(args.packname, args.req_file, args.editable, 98 | args_manager['install']['ignore']) 99 | 100 | @arg('packname', nargs='+') 101 | @arg('-r', '--req-file', metavar='', help='Uninstall all the packages listed in the given requirement file') 102 | @arg('-y', '--yes', action='store_true', help='Do not ask confirmation of uninstall deletions') 103 | @arg('-l', '--local', action='store_true', help='Add to files to delete local files too.') 104 | @arg('-i', '--info', action='store_true', help='Only list files to delete') 105 | def remove(args): 106 | ''' 107 | Remove a package 108 | ''' 109 | 110 | if args.yes: 111 | args_manager['remove']['yes'] = True 112 | if args.info: 113 | args_manager['remove']['info'] = True 114 | if args.local: 115 | args_manager['remove']['local'] = True 116 | opts.remove_func(args.packname, args.req_file, 117 | args_manager['remove']['yes'], args_manager['remove']['info'], 118 | args_manager['remove']['local']) 119 | 120 | @arg('packname', nargs=1) 121 | @arg('-i', '--index-url', nargs=1, default='http://pypi.python.org', metavar='', help='Base URL of Python Package Index (default to %(default)s)') 122 | def list(args): 123 | ''' 124 | List all versions for a package 125 | ''' 126 | 127 | if isinstance(args.index_url, ITERABLE_T): 128 | args.index_url = args.index_url[0] 129 | 130 | args_manager['install']['packages_url'] = args.index_url + '/simple' 131 | args_manager['install']['index_url'] = args.index_url + '/pypi' 132 | 133 | opts.list_func(args.packname[0]) 134 | 135 | @arg('-c', '--count', action='store_true', help='Only returns requirements count') 136 | @arg('-n', '--no-info', action='store_true', help='Do not add site information') 137 | @arg('-f', '--file', metavar='', help='Writes requirements into the specified file') 138 | def site(args): 139 | ''' 140 | Show installed packages and some site information 141 | ''' 142 | 143 | if args.count: 144 | args_manager['site']['count'] = True 145 | if args.no_info: 146 | args_manager['site']['no_info'] = True 147 | if args.file: 148 | args_manager['site']['file'] = args.file 149 | count, no_info, file = args_manager['site']['count'], \ 150 | args_manager['site']['no_info'], args_manager['site']['file'] 151 | opts.site_func(count, no_info, file) 152 | 153 | @arg('query', nargs='+') 154 | @arg('-i', '--index-url', default='http://pypi.python.org', metavar='', help='Base URL of Python Package Index (default to %(default)s)') 155 | @arg('-e', '--exact', action='store_true', help='List only exact hits') 156 | @arg('-n', '--max-num', type=int, default=None, help='List at most results') 157 | @arg('-a', '--all', action='store_true', help='Show all versions for specified package') 158 | def search(args): 159 | ''' 160 | Search PyPI 161 | ''' 162 | 163 | if isinstance(args.index_url, ITERABLE_T): 164 | args.index_url = args.index_url[0] 165 | 166 | args_manager['install']['packages_url'] = args.index_url + '/simple' 167 | args_manager['install']['index_url'] = args.index_url + '/pypi' 168 | 169 | opts.search_func(args.query, args.exact, args.all, args.max_num) 170 | 171 | @arg('packname') 172 | @arg('-i', '--info', action='store_true', help='Show infos for specified package') 173 | def check(args): 174 | ''' 175 | Check if a package is installed 176 | ''' 177 | 178 | opts.check_func(args.packname, args.info) 179 | 180 | @arg('packname') 181 | @arg('-i', '--index-url', default='http://pypi.python.org', metavar='', help='Base URL of Python Package Index (default to %(default)s)') 182 | @arg('-u', '--unpack', action='store_true', help='Once downloaded, unpack the package') 183 | @arg('-d', '--download-dir', default=os.path.curdir, metavar='', help='The destination directory') 184 | @arg('-p', '--prefer', metavar='', help='The preferred file type for the download') 185 | @arg('-m', '--md5',action='store_true', help='Show md5 sum & link after download') 186 | @arg('-n', '--dry',action='store_true', help='Dry run, just display informations') 187 | def download(args): 188 | ''' 189 | Download a package 190 | ''' 191 | 192 | if isinstance(args.index_url, ITERABLE_T): 193 | args.index_url = args.index_url[0] 194 | 195 | args_manager['install']['packages_url'] = args.index_url + '/simple' 196 | args_manager['install']['index_url'] = args.index_url + '/pypi' 197 | 198 | if args.download_dir != args_manager['download']['download_dir']: 199 | args_manager['download']['download_dir'] = args.download_dir 200 | if args.prefer != args_manager['download']['prefer']: 201 | args_manager['download']['prefer'] = args.prefer 202 | args_manager['download']['unpack'] = bool(args.unpack) 203 | args_manager['download']['md5'] = bool(args.md5) 204 | if args.dry: 205 | args_manager['download']['download_dir'] = None 206 | opts.download_func(args) 207 | 208 | @arg('-i', '--index-url', default='http://pypi.python.org', metavar='', help='Base URL of Python Package Index (default to %(default)s)') 209 | @arg('-y', '--yes', action='store_true', help='Do not ask confirmation for the upgrade') 210 | def update(args): 211 | ''' 212 | Check for updates for installed packages 213 | ''' 214 | 215 | if isinstance(args.index_url, ITERABLE_T): 216 | args.index_url = args.index_url[0] 217 | 218 | args_manager['install']['packages_url'] = args.index_url + '/simple' 219 | args_manager['install']['index_url'] = args.index_url + '/pypi' 220 | 221 | if args.yes: 222 | args_manager['update']['yes'] = True 223 | opts.update_func() 224 | 225 | @command 226 | def shell(): 227 | ''' 228 | Fire up Pyg Shell 229 | ''' 230 | 231 | opts.shell_func() 232 | 233 | @arg('bundlename', help='Name of the bundle to create') 234 | @arg('packages', nargs='*', help='Name of the package(s) to bundle') 235 | @arg('-i', '--index-url', default='http://pypi.python.org', metavar='', help='Base URL of Python Package Index (default to %(default)s)') 236 | @arg('-r', '--req-file', action='append', metavar='', help='Requirement files which contains packages to bundle') 237 | @arg('-e', '--exclude', action='append', default=[], metavar='', help='Exclude packages matching `requirement`') 238 | @arg('-d', '--use-develop', action='store_true', help='Look for local packages before downloading them') 239 | def bundle(args): 240 | ''' 241 | Create bundles (like Pip's ones) 242 | ''' 243 | 244 | if isinstance(args.index_url, ITERABLE_T): 245 | args.index_url = args.index_url[0] 246 | args_manager['install']['packages_url'] = args.index_url + '/simple' 247 | args_manager['install']['index_url'] = args.index_url + '/pypi' 248 | 249 | if args.exclude: 250 | args_manager['bundle']['exclude'] = args.exclude 251 | if args.use_develop: 252 | args_manager['bundle']['use_develop'] = True 253 | exclude, use_develop = args_manager['bundle']['exclude'], args_manager['bundle']['use_develop'] 254 | opts.bundle_func(args.packages, args.bundlename, exclude, args.req_file, use_develop) 255 | 256 | @arg('packname', help='Name of the pack to create') 257 | @arg('package', help='Name of the package to pack') 258 | @arg('-i', '--index-url', default='http://pypi.python.org', metavar='', help='Base URL of Python Package Index (default to %(default)s)') 259 | @arg('-d', '--use-develop', action='store_true', help='Look for local packages before downloading them') 260 | @arg('-e', '--exclude', action='append', default=[], metavar='', help='Exclude packages matching `requirement`') 261 | def pack(args): 262 | ''' 263 | Create packs 264 | ''' 265 | 266 | if isinstance(args.index_url, ITERABLE_T): 267 | args.index_url = args.index_url[0] 268 | args_manager['install']['packages_url'] = args.index_url + '/simple' 269 | args_manager['install']['index_url'] = args.index_url + '/pypi' 270 | 271 | # XXX: Duplication is evil. (See above.) 272 | if args.exclude: 273 | args_manager['pack']['exclude'] = args.exclude 274 | if args.use_develop: 275 | args_manager['pack']['use_develop'] = True 276 | exclude, use_develop = args_manager['pack']['exclude'], args_manager['pack']['use_develop'] 277 | return opts.pack_func(args.package, args.packname, exclude, use_develop) 278 | 279 | @arg('-f', '--file', metavar='', help='Write code for completion into the specified file. Default to %(default)r') 280 | def completion(args): 281 | ''' 282 | Generate bash code for Pyg completion 283 | ''' 284 | return opts.completion_func(COMMANDS, args.file) 285 | 286 | @command 287 | def help(): 288 | ''' 289 | Show this help and exit 290 | ''' 291 | 292 | return 293 | 294 | parser.add_commands([locals()[cmd] for cmd in COMMANDS]) 295 | parser.formatter_class = _formatter(parser) 296 | return parser 297 | -------------------------------------------------------------------------------- /pyg/parser/shell.py: -------------------------------------------------------------------------------- 1 | import os 2 | import cmd 3 | import difflib 4 | 5 | from pyg.parser.parser import COMMANDS, init_parser 6 | from pyg.core import PygError, AlreadyInstalled 7 | 8 | 9 | __all__ = ['PygShell'] 10 | 11 | 12 | SUPPORTED_COMMANDS = COMMANDS - set(['shell', 'help']) 13 | ADDITIONAL_COMMANDS = ['cd', 'pwd', 'ls'] 14 | HELP = '''Supported commands: 15 | =================== 16 | 17 | {0} 18 | 19 | 20 | Additional commands: 21 | ==================== 22 | 23 | {1} 24 | ''' 25 | 26 | 27 | def command_hook(attr): 28 | def wrapper(*args, **kwargs): 29 | if attr == 'EOF': 30 | print '\n' 31 | return True 32 | print '*** Unknown command: {0}'.format(attr) 33 | close = difflib.get_close_matches(attr, SUPPORTED_COMMANDS, n=1, cutoff=0.5) 34 | if close: 35 | print 'Did you mean this?\n\t{0}'.format(close[0]) 36 | return wrapper 37 | 38 | def command(parser, cmd_name): 39 | def inner(args): 40 | try: 41 | return parser.dispatch([cmd_name] + args.split()) 42 | except (SystemExit, AlreadyInstalled, PygError): 43 | pass 44 | except Exception as e: 45 | print e 46 | return inner 47 | 48 | 49 | class PygShell(cmd.Cmd, object): 50 | def __init__(self, *args, **kwargs): 51 | self.startdir = os.getcwd() 52 | self.prompt = 'pyg:{0}$ '.format(self.startdir) 53 | self.parser = init_parser(__import__('pyg').__version__) 54 | super(PygShell, self).__init__(*args, **kwargs) 55 | 56 | def get_names(self): 57 | return ['do_' + name for name in ADDITIONAL_COMMANDS + SUPPORTED_COMMANDS + ['ls', 'cd', 'exit', 'help']] 58 | 59 | def __getattr__(self, attr): 60 | if not attr.startswith('do_') or attr in ADDITIONAL_COMMANDS: 61 | return super(PygShell, self).__getattribute__(attr) 62 | attr = attr[3:] 63 | if not attr in SUPPORTED_COMMANDS: 64 | return command_hook(attr) 65 | return command(self.parser, attr) 66 | 67 | def do_help(self, line): 68 | print HELP.format(' '.join(SUPPORTED_COMMANDS), ' '.join(ADDITIONAL_COMMANDS)) 69 | 70 | def do_exit(self, line): 71 | return self.do_EOF() 72 | 73 | def do_cd(self, line): 74 | paths = line.split() 75 | if not paths: 76 | return self.do_cd(self.startdir) 77 | try: 78 | path = os.path.abspath(paths[0]) 79 | os.chdir(path) 80 | except OSError as e: 81 | print 'cd: {0}'.format(e.strerror) 82 | else: 83 | self.prompt = 'pyg:{0}$ '.format(path) 84 | 85 | def do_pwd(self, line): 86 | print os.getcwd() 87 | 88 | #def do_rm(self, line): 89 | # if not line.split(): 90 | # print '*** Error: rm must have an argument' 91 | # return 92 | # path = line.split()[0] 93 | # try: 94 | # if os.path.isdir(path): 95 | # shutil.rmtree(path) 96 | # else: 97 | # os.remove(path) 98 | # except OSError as e: 99 | # print 'rm: {0}: {1}'.format(e.strerror, path) 100 | 101 | def do_ls(self, line): 102 | args = line.split() 103 | path = args[0] if args else os.getcwd() 104 | ls = os.listdir(path) 105 | if not '-a' in args and not '--all' in args: 106 | ls = [p for p in ls if not p.startswith('.')] 107 | print ' '.join(sorted(ls)) -------------------------------------------------------------------------------- /pyg/req.py: -------------------------------------------------------------------------------- 1 | import urllib2 2 | import operator 3 | import urlparse 4 | import pkg_resources 5 | 6 | from hashlib import md5 7 | 8 | from pyg.utils import ext, right_egg, is_windows 9 | from pyg.web import ReqManager, get_links, download 10 | from pyg.core import Version, Egg, Archive, Binary, ReqSet, InstallationError, args_manager 11 | from pyg.log import logger 12 | 13 | 14 | __all__ = ['Requirement', 'WINDOWS_EXT'] 15 | 16 | 17 | WINDOWS_EXT = ('.exe', '.msi') if is_windows() else () 18 | 19 | 20 | class Requirement(object): 21 | 22 | OPMAP = {'==': operator.eq, 23 | '>=': operator.ge, 24 | '>': operator.gt, 25 | '<=': operator.le, 26 | '<': operator.lt, 27 | '!=': lambda a,b: a != b, 28 | None: lambda a,b: True, ##FIXME: does None really work? 29 | } 30 | 31 | version = op = None 32 | 33 | def __init__(self, req): 34 | self.req = req 35 | self._specs = pkg_resources.Requirement.parse(req).specs 36 | ## When no operator is specified 37 | if not self._specs: 38 | self._specs = [(None, None)] 39 | self.is_dev = None 40 | else: 41 | self.is_dev = any('dev' in v for op, v in self._specs) 42 | # version and op will be set after a call to ReqManager is made 43 | self.name, _, _ = self.split(req) 44 | self.reqset = ReqSet(self) 45 | self.package_index = args_manager['install']['index_url'] 46 | 47 | def __repr__(self): 48 | return 'Requirement({0})'.format(self.req) 49 | 50 | def __str__(self): 51 | if self.version: 52 | return '{0}=={1}'.format(self.name, self.version) 53 | return str(self.req) 54 | 55 | def __eq__(self, other): 56 | return self.name == other.name and self.match(other.version) 57 | 58 | @staticmethod 59 | def split(chunk): 60 | for c in ('==', '>=', '>', '<=', '<', '!='): 61 | if c in chunk: 62 | name, op, version = map(str.strip, chunk.partition(c)) 63 | version = Version(version) 64 | break 65 | else: 66 | name = chunk.split()[0] 67 | op = version = None 68 | return name, op, version 69 | 70 | def match(self, v): 71 | if v is None: 72 | return True 73 | return all(self.OPMAP[op](v, Version(version)) for op, version in self._specs) 74 | 75 | #def best_match(self, reqs): 76 | # matched = {} 77 | # for r in reqs: 78 | # parts = r.split('-') 79 | # version = Requirement.find_version('-'.join(parts[1:])) 80 | # if self.version is None or self.match(version): 81 | # matched[version] = r 82 | # if len(matched) == 0: 83 | # return None 84 | # elif len(matched) == 1: 85 | # return matched[matched.keys()[0]] 86 | # ## The highest version possible 87 | # return matched[max(matched)] ## OR matched[sorted(matched.keys(), reverse=True)[0]]? 88 | 89 | def _install_from_links(self, package_index): 90 | logger.info('Looking for links on {0}', package_index) 91 | try: 92 | links = get_links(self, package_index) 93 | if not links: 94 | raise InstallationError('Error: did not find any files') 95 | except Exception as e: 96 | raise InstallationError(str(e)) 97 | logger.indent = 8 98 | for url in links: 99 | filename = urlparse.urlparse(url).path.split('/')[-1] 100 | logger.info('Found: {0}', filename) 101 | try: 102 | self._download_and_install(url, filename, self.name, ext(filename)) 103 | except Exception as e: 104 | logger.error('Error: {0}', e) 105 | continue 106 | break 107 | logger.indent = 0 108 | if not self.success: 109 | raise InstallationError('Fatal: cannot install {0}'.format(self.name)) 110 | 111 | def _check_bad_eggs(self, bad_eggs): 112 | ## Bad eggs are eggs which require a different Python version. 113 | ## If we don't find anything more, we check bad eggs. 114 | txt = 'Found only eggs for another Python version, which one do you want to install' 115 | choice = logger.ask(txt, choices=dict((v[1], v) for v in bad_eggs)) 116 | self._download_and_install(*choice) 117 | 118 | def _download_and_install(self, url, filename, packname, e, hash=None): 119 | fobj = download(url, 'Downloading {0}'.format(self.name)) 120 | if hash is not None: 121 | logger.info('Checking md5 sum') 122 | if md5(fobj.getvalue()).hexdigest() != hash: 123 | logger.fatal('Error: {0} appears to be corrupted', self.name) 124 | return 125 | if e in ('.tar.gz', '.tar.bz2', '.zip'): 126 | installer = Archive(fobj, e, packname, self.reqset) 127 | elif e == '.egg': 128 | installer = Egg(fobj, filename, self.reqset, packname) 129 | elif is_windows() and e in WINDOWS_EXT: 130 | installer = Binary(fobj, e, packname) 131 | else: 132 | logger.error('Error: unknown filetype: {0}', e, exc=InstallationError) 133 | 134 | ## There is no need to catch exceptions now, this will be done by `pyg.inst.Installer.install` 135 | installer.install() 136 | self.success = True 137 | 138 | def install(self): 139 | self.success = False 140 | logger.info('Looking for {0} releases on PyPI', self.name) 141 | p = ReqManager(self) 142 | try: 143 | files = p.files() 144 | except (urllib2.URLError, urllib2.HTTPError) as e: 145 | raise InstallationError(repr(e.reason) if hasattr(e, 'reason') else e.msg) 146 | bad_eggs = [] 147 | for pext in ('.tar.gz', '.tar.bz2', '.zip', '.egg') + WINDOWS_EXT: 148 | for v, name, hash, url in files[pext]: 149 | if pext == '.egg' and not right_egg(name): 150 | if args_manager['install']['force_egg_install']: 151 | bad_eggs.append((url, name, p.name, pext, hash)) 152 | continue 153 | logger.info('Best match: {0}=={1}', self.name, v) 154 | try: 155 | self._download_and_install(url, name, p.name, pext, hash) 156 | except InstallationError: 157 | logger.info('Trying another file (if there is one)...') 158 | continue 159 | if not self.version: 160 | self.version = v 161 | break 162 | if self.success: 163 | return 164 | if bad_eggs: 165 | self._check_bad_eggs(bad_eggs) 166 | if not self.success: 167 | raise InstallationError('Error: Cannot find files available for dowloading and installing') 168 | -------------------------------------------------------------------------------- /pyg/scripts.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Script installation resources. Modified from setuptools/easy_install.py. 3 | ''' 4 | 5 | 6 | import os 7 | import re 8 | import sys 9 | import pkg_resources 10 | 11 | SCRIPT_TEXT = '''# PYG-ENTRY-SCRIPT: {spec!r},{group!r},{name!r} 12 | __requires__ = {spec!r} 13 | import sys 14 | from pkg_resources import load_entry_point 15 | 16 | if __name__ == '__main__': 17 | sys.exit( 18 | load_entry_point({spec!r}, {group!r}, {name!r})() 19 | )''' 20 | 21 | 22 | def isascii(s): 23 | try: 24 | s.encode('ascii') 25 | return True 26 | except UnicodeError: 27 | return False 28 | 29 | def nt_quote_arg(arg): 30 | result = [] 31 | needquote = False 32 | nb = 0 33 | 34 | needquote = (" " in arg) or ("\t" in arg) 35 | if needquote: 36 | result.append('"') 37 | 38 | for c in arg: 39 | if c == '\\': 40 | nb += 1 41 | elif c == '"': 42 | # double preceding backslashes, then add a \" 43 | result.append('\\' * (nb * 2) + '\\"') 44 | nb = 0 45 | else: 46 | if nb: 47 | result.append('\\' * nb) 48 | nb = 0 49 | result.append(c) 50 | 51 | if nb: 52 | result.append('\\' * nb) 53 | if needquote: 54 | result.append('\\' * nb) # double the trailing backslashes 55 | result.append('"') 56 | return ''.join(result) 57 | 58 | def get_script_header(script_text, executable=sys.executable): 59 | first_line_re = re.compile('^#!.*python[0-9.]*([ \t].*)?$') 60 | first = (script_text + '\n').splitlines()[0] 61 | match = first_line_re.match(first) 62 | options = '' 63 | if match: 64 | options = match.group(1) or '' 65 | if options: 66 | options = ' ' + options 67 | executable = nt_quote_arg(executable) 68 | hdr = "#!{0}{1}\n".format(executable, options) 69 | if not isascii(hdr): 70 | # Non-ascii path to sys.executable, use -x to prevent warnings 71 | if options: 72 | if options.strip().startswith('-'): 73 | options = ' -x' + options.strip()[1:] 74 | # else: punt, we can't do it, let the warning happen anyway 75 | else: 76 | options = ' -x' 77 | hdr = "#!{0}{1}\n".format(executable, options) 78 | return hdr 79 | 80 | def script_args(dist): 81 | spec = dist.as_req 82 | header = get_script_header("", sys.executable) 83 | for group in 'console_scripts', 'gui_scripts': 84 | for name, ep in dist.entry_points_map(group).items(): 85 | script_text = SCRIPT_TEXT.format(**locals()) 86 | if sys.platform == 'win32': 87 | # On Windows/wininst, add a .py extension and an .exe launcher 88 | if group == 'gui_scripts': 89 | ext, launcher = '-script.pyw', 'gui.exe' 90 | new_header = re.sub('(?i)python.exe', 'pythonw.exe', header) 91 | else: 92 | ext, launcher = '-script.py', 'cli.exe' 93 | new_header = re.sub('(?i)pythonw.exe', 'python.exe', header) 94 | 95 | if os.path.exists(new_header[2:-1]): 96 | hdr = new_header 97 | else: 98 | hdr = header 99 | yield (name + ext, hdr + script_text, 't') 100 | yield ( 101 | name + '.exe', pkg_resources.resource_string('setuptools', launcher), 102 | 'b' # write in binary mode 103 | ) 104 | else: 105 | # On other platforms, we assume the right thing to do is to 106 | # just write the stub with no extension. 107 | yield (name, header + script_text, '') -------------------------------------------------------------------------------- /pyg/utils.py: -------------------------------------------------------------------------------- 1 | import re 2 | import os 3 | import sys 4 | import atexit 5 | import shutil 6 | import tarfile 7 | import zipfile 8 | import platform 9 | import tempfile 10 | import subprocess 11 | import pkg_resources 12 | 13 | 14 | from pyg.locations import PYG_LINKS, PYTHON_VERSION, under_virtualenv 15 | from pyg.log import logger 16 | 17 | try: 18 | import zlib 19 | COMPRESSION_LEVEL=zipfile.ZIP_DEFLATED 20 | except ImportError: 21 | COMPRESSION_LEVEL=zipfile.ZIP_STORED 22 | 23 | SETUP_PY_TEMPLATE = '''import distutils 24 | from setuptools import setup 25 | from setuptools.command.install import install as setuptools_install 26 | distutils.command.install.install = setuptools_install 27 | 28 | __file__={0!r};execfile(__file__)''' 29 | 30 | 31 | try: 32 | ## subprocess.check_output has been introduced in Python 2.7 33 | ## Since Pyg run on Python 2.6 too, we have to reproduce it. 34 | check_output = subprocess.check_output 35 | except AttributeError: 36 | def check_output(*popenargs, **kwargs): 37 | '''Borrowed from Python2.7 subprocess.py''' 38 | 39 | if 'stdout' in kwargs: 40 | raise ValueError('stdout argument not allowed, it will be overridden.') 41 | process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs) 42 | output, unused_err = process.communicate() 43 | retcode = process.poll() 44 | if retcode: 45 | cmd = kwargs.get("args") 46 | if cmd is None: 47 | cmd = popenargs[0] 48 | raise CalledProcessError(retcode, cmd, output=output) 49 | return output 50 | 51 | class CalledProcessError(Exception): 52 | """This exception is raised when a process run by check_call() or 53 | check_output() returns a non-zero exit status. 54 | The exit status will be stored in the returncode attribute; 55 | check_output() will also store the output in the output attribute. 56 | """ 57 | 58 | def __init__(self, returncode, cmd, output=None): 59 | self.returncode = returncode 60 | self.cmd = cmd 61 | self.output = output 62 | def __str__(self): 63 | return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode) 64 | 65 | def is_installed(req): 66 | ''' 67 | Check whether the given requirement is installed or not. 68 | A requirement can be either a name or a name plus a version: both `pyg` and 69 | `pyg==0.7` are valid requirements. 70 | ''' 71 | 72 | try: 73 | pkg_resources.get_distribution(req) 74 | except (pkg_resources.DistributionNotFound, pkg_resources.VersionConflict, 75 | ValueError): 76 | return False 77 | else: 78 | return True 79 | 80 | def installed_distributions(): 81 | for dist in pkg_resources.working_set: 82 | # Filter out Python==version, in case Pyg is executed in a virtual env 83 | if dist.project_name.lower() == 'python': 84 | continue 85 | yield dist 86 | 87 | def is_windows(): 88 | '''Return True when Pyg is running on a Windows system, False otherwise.''' 89 | 90 | return platform.system() == 'Windows' 91 | 92 | def name_from_name(pkg_name): 93 | ''' 94 | Get the name of a package from its egg name: 95 | 96 | >>> name_from_name('zicbee-mplayer-0.7-py2.7.egg') 97 | ('zicbee_mplayer', '-0.7-py2.7.egg') 98 | ''' 99 | name, version = re.compile(r'([.\w\d_-]+)-([\d\w.]+.*)').match(pkg_name).groups() 100 | return "%s-%s"%(name.replace('-', '_') , version.replace('-', '_')) 101 | 102 | 103 | ## We could use str.split, but regex give us a better control over 104 | ## the strings. 105 | 106 | def name_from_egg(eggname): 107 | ''' 108 | Get the name of a package from its egg name: 109 | 110 | >>> name_from_egg('pyg-0.7-py2.7.egg') 111 | 'pyg' 112 | ''' 113 | 114 | ## We could use str.split, but regex give us a better control over 115 | ## the strings. 116 | egg = re.compile(r'([\w\d_]+)-.+') 117 | return egg.search(eggname).group(1) 118 | 119 | def right_egg(eggname): 120 | '''Return True if the egg can be installed basing on the running Python version.''' 121 | 122 | vcode = 'py{0}'.format('.'.join(map(str, sys.version_info[:2]))) 123 | return vcode in eggname 124 | 125 | def version_egg(eggname): 126 | '''Extract Python version from an egg name.''' 127 | 128 | eggv = re.compile(r'py(\d\.\d)') 129 | return eggv.search(eggname).group(1) 130 | 131 | def call_subprocess(args, cwd=None): 132 | ''' 133 | Call subprocess with the given argument and return the tuple 134 | `(returncode, output)`. You can also specify the current working directory. 135 | ''' 136 | 137 | try: 138 | output = check_output(args, stderr=subprocess.STDOUT, cwd=cwd) 139 | except (subprocess.CalledProcessError, CalledProcessError) as e: 140 | return e.returncode, e.output 141 | return 0, output 142 | 143 | def call_setup(path, a): 144 | ''' 145 | Call the `setup.py` file under the specified path with the given arguments. 146 | 147 | Note that `path` must be the directory in which the setup file is located, 148 | not the direct path to the file. For example, `/home/user/packages/pyg-0.7/' 149 | is right (assuming there is a `setup.py` file in it), while 150 | '/home/user/packages/pyg-0.7/setup.py` is not. 151 | ''' 152 | 153 | code = SETUP_PY_TEMPLATE.format(os.path.join(path, 'setup.py')) 154 | args = [sys.executable, '-c', code] + a 155 | # we add the --install-header option under virtualenv only if 156 | # we are installing 157 | if under_virtualenv() and 'install' in args: 158 | logger.debug('debug: virtualenv detected') 159 | headers = os.path.join(sys.prefix, 'include', 'site', 'python' + PYTHON_VERSION) 160 | args += ['--install-headers', headers] 161 | return call_subprocess(args, cwd=path) 162 | 163 | def run_setup(path, name, global_args=[], args=[], exc=TypeError): 164 | ''' 165 | Run `setup.py install` for the given setup file. `name` is the package name; 166 | `global_args` are the arguments to pass before the `install` command; `args` 167 | are the options for the `install` command and `exc` is the exception to throw 168 | in case of a failed installation. 169 | 170 | The warning for `path` is the same as `call_setup`. 171 | ''' 172 | 173 | logger.info('Running setup.py install for {0}', name) 174 | if name == 'dreampie': # little exception to make it works 175 | ar = global_args + ['install'] + args 176 | else: 177 | ar = global_args + ['install', '--single-version-externally-managed', 178 | '--record', os.path.join(tempfile.mkdtemp(), '.pyg-install-record')] + args 179 | code, output = call_setup(path, ar) 180 | if code != 0: 181 | logger.fatal('Error: setup.py did not install {0}', name) 182 | print_output(output, 'setup.py install') 183 | raise exc('setup.py did not install {0}'.format(name)) 184 | 185 | def print_output(output, cmd): 186 | '''Print to sys.stderr the complete output of a failed command''' 187 | 188 | logger.info('Complete output from command `{0}`:', cmd) 189 | logger.indent += 8 190 | for line in output.splitlines(): 191 | logger.error(line) 192 | logger.indent -= 8 193 | 194 | def name_ext(path): 195 | '''Like os.path.splitext(), but split off .tar too.''' 196 | 197 | p, e = os.path.splitext(path) 198 | if p.endswith('.tar'): 199 | e = '.tar' + e 200 | p = p[:-4] 201 | 202 | ## Little hack to support .tgz files too without adding them to 203 | ## pyg.web.PREFERENCES, etc. 204 | if e == '.tgz': 205 | return p, '.tar.gz' 206 | return p, e 207 | 208 | def name(path): 209 | ''' 210 | Return the name of a file (i.e. strip off the file extension): 211 | 212 | >>> name('pyg-0.7-py2.7.egg') 213 | 'pyg-0.7-py2.7' 214 | ''' 215 | 216 | return name_ext(path)[0] 217 | 218 | def ext(path): 219 | ''' 220 | Return the extension of a filename: 221 | 222 | >>> ext('pyg-0.7-py2.7.egg') 223 | '.egg' 224 | >>> ext('pyg-0.7.tar.gz') 225 | '.tar.gz' 226 | ''' 227 | 228 | return name_ext(path)[1] 229 | 230 | def unpack(path, dest=None): 231 | ''' 232 | Unpack the specified archive into the same directory or a specified destination. 233 | ''' 234 | 235 | path = os.path.abspath(path) 236 | d, n = os.path.split(path) 237 | e = ext(n) 238 | if e in ('.egg', '.zip'): 239 | arch = ZipFile(path) 240 | elif e in ('.tar', '.tar.gz', '.tar.bz2'): 241 | mode = 'r' if e == '.tar' else 'r:' + e.split('.')[2] 242 | arch = tarfile.open(path, mode=mode) 243 | else: 244 | logger.error('Unknown extension: {0}', e, exc=TypeError) 245 | arch.extractall(dest or d) 246 | 247 | 248 | class TempDir(object): 249 | 250 | not_removed = set() 251 | 252 | def __init__(self, prefix='pyg-', suffix='-record', dont_remove=False): 253 | self.prefix = prefix 254 | self.suffix = suffix 255 | self.dont_remove = dont_remove 256 | 257 | def __enter__(self): 258 | self.tempdir = tempfile.mkdtemp(self.suffix, self.prefix) 259 | self.not_removed.add(self.tempdir) 260 | return self.tempdir 261 | 262 | def __exit__(self, *args): 263 | if not self.dont_remove: 264 | try: 265 | shutil.rmtree(self.tempdir) 266 | ## Experimental (remember to remove before releasing) 267 | except Exception as e: 268 | logger.verbose('Error: cannot remove temporary directory {0}: {1}', self.tempdir, e) 269 | 270 | @staticmethod 271 | @atexit.register 272 | def __clean_tempdir(): 273 | to_remove = TempDir.not_removed 274 | if to_remove: 275 | logger.verbose('Cleaning temporary folders', addn=False) 276 | for fold in to_remove: 277 | if os.path.isdir(fold): 278 | logger.verbose('.', addn=False) 279 | try: 280 | shutil.rmtree(fold) 281 | except (OSError, IOError): 282 | logger.verbose('\bx', addn=False) 283 | sys.stdout.flush() 284 | if logger.level <= logger.VERBOSE: 285 | logger.newline() 286 | 287 | 288 | class ChDir(object): 289 | def __init__(self, dir): 290 | self.cwd = os.getcwd() 291 | self.dir = dir 292 | 293 | def __enter__(self): 294 | os.chdir(self.dir) 295 | return self.dir 296 | 297 | def __exit__(self, *args): 298 | os.chdir(self.cwd) 299 | 300 | 301 | ## No more used. Now pyg.web.ReqManager.files() uses directly 302 | # collections.defaultdict 303 | # 304 | #class FileMapper(collections.defaultdict): 305 | # ''' 306 | # Container for pyg.web.ReqManager, which needs it to hold files preferences. 307 | # ''' 308 | # 309 | # def __init__(self, pref): 310 | # self.pref = pref 311 | # super(FileMapper, self).__init__(list) 312 | # 313 | # def __missing__(self, key): 314 | # if key in self.pref: 315 | # if key not in self: 316 | # self[key] = self.default_factory() 317 | # return self[key] 318 | # return self.default_factory() 319 | 320 | 321 | ## ZipFile subclass for Python < 2.7 322 | ## In Python 2.6 zipfile.ZipFile and tarfile.TarFile do not have __enter__ and 323 | ## __exit__ methods 324 | ## EDIT: Removed TarFile since it caused problems 325 | 326 | class ZipFile(zipfile.ZipFile): 327 | def __init__(self, file, *args, **kwargs): 328 | if not 'compression' in kwargs: 329 | kwargs['compression'] = COMPRESSION_LEVEL 330 | zipfile.ZipFile.__init__(self, file, *args, **kwargs) 331 | 332 | def __enter__(self): 333 | return self 334 | 335 | def __exit__(self, type, value, traceback): 336 | self.close() 337 | 338 | def add_executable(self, filename, content): 339 | zi = zipfile.ZipInfo(filename) 340 | zi.external_attr = 0777 << 16L 341 | self.writestr(zi, content) 342 | 343 | def add_to_archive(self, dir, tempdir_len): 344 | for file in os.listdir(dir): 345 | path = os.path.join(dir, file) 346 | if os.path.isfile(path): 347 | self.write(path, path[tempdir_len:]) 348 | elif os.path.isdir(path): 349 | self.add_to_archive(path, tempdir_len) 350 | 351 | #class TarFile(tarfile.TarFile): 352 | # def __enter__(self): 353 | # return self 354 | # 355 | # def __exit__(self, type, value, traceback): 356 | # self.close() 357 | 358 | 359 | ## This is a fake file object needed by ConfigParser.ConfigParser 360 | ## It implements only a readline() method plus an __iter__ method 361 | 362 | class File(object): 363 | def __init__(self, lines): 364 | self._i = (l for l in lines) 365 | 366 | def __iter__(self): 367 | return self._i 368 | 369 | def readline(self): 370 | try: 371 | return next(self._i) 372 | except StopIteration: 373 | return '' 374 | -------------------------------------------------------------------------------- /pyg/vcs/__init__.py: -------------------------------------------------------------------------------- 1 | from vcs import * 2 | 3 | from pyg.log import logger 4 | 5 | 6 | def vcs(url, dest=None): 7 | schemes = ('git+', 'hg+', 'bzr+', 'svn+', 'dir+') 8 | for scheme in schemes: 9 | if url.startswith(scheme): 10 | break 11 | else: 12 | logger.fatal('Error: URL should start with one of these schemes:\n{0}', schemes, exc=ValueError) 13 | if not '#egg=' in url: 14 | logger.fatal('Error: URL should contain `#egg=PACKAGE`', exc=ValueError) 15 | 16 | MAP = { 17 | 'git': Git, 18 | 'hg': Hg, 19 | 'bzr': Bzr, 20 | 'svn': Svn, 21 | 'dir': Local, 22 | } 23 | 24 | return MAP[scheme[:-1]](url, dest) -------------------------------------------------------------------------------- /pyg/vcs/base.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import shutil 4 | import subprocess 5 | 6 | from pyg.utils import call_subprocess, call_setup, print_output 7 | from pyg.inst import Installer 8 | from pyg.core import InstallationError, PygError 9 | from pyg.log import logger 10 | 11 | 12 | class VCS(object): 13 | 14 | ARGS = None 15 | 16 | def __init__(self, dest): 17 | self.dest = os.path.abspath(dest) 18 | 19 | ## Check command line programs existence (git, hg, bzr, etc.) to avoid 20 | ## strange errors. 21 | try: 22 | subprocess.check_call([self.cmd], stdout=subprocess.PIPE, stderr=subprocess.PIPE) 23 | except (OSError, subprocess.CalledProcessError) as e: 24 | # git returns 1 when it displays help 25 | if not (self.cmd == 'git' and e.returncode == 1): 26 | logger.fatal('Error: {0!r} command not found. Please make sure you ' \ 27 | 'have installed required vcs', self.cmd, exc=PygError) 28 | 29 | def __repr__(self): 30 | return '<{0}[{1}] object at {2}>'.format(self.__class__.__name__, 31 | self.package_name, 32 | id(self)) 33 | 34 | @property 35 | def cmd(self): 36 | return self.CMD 37 | 38 | @property 39 | def method(self): 40 | return self.METHOD 41 | 42 | @property 43 | def dir(self): 44 | try: 45 | return os.path.join(self.dest, os.listdir(self.dest)[0]) 46 | except OSError: 47 | return None 48 | 49 | def parse_url(self, url): 50 | if not '#egg=' in url: 51 | raise ValueError('You must specify #egg=PACKAGE') 52 | return url.split('#egg=') 53 | 54 | def develop(self): 55 | self.retrieve_data() 56 | if not os.path.exists(os.path.join(self.dir, 'setup.py')): 57 | logger.fatal('Error: The repository must have a top-level setup.py file', exc=InstallationError) 58 | logger.info('Running setup.py develop for {0}', self.package_name) 59 | code, output = call_setup(self.dir, ['develop']) 60 | if code != 0: 61 | logger.fatal('Error: setup.py develop did not install {0}', self.package_name) 62 | print_output(output, 'setup.py develop') 63 | raise InstallationError('setup.py did not install {0}'.format(self.package_name)) 64 | logger.info('{0} installed succesfully', self.package_name) 65 | 66 | def install(self): 67 | self.retrieve_data() 68 | Installer.from_dir(self.dir, self.package_name) 69 | 70 | def retrieve_data(self): 71 | code, output = self.call_cmd([self.url]) 72 | if code != 0: 73 | logger.fatal('Error: Cannot retrieve data') 74 | print_output(output, '{0} {1}'.format(self.cmd, self.method)) 75 | logger.raise_last(InstallationError) 76 | 77 | def call_cmd(self, args): 78 | ## Ensure that the destination directory exists 79 | self.check_dest() 80 | if self.ARGS is not None: 81 | args = self.ARGS + args 82 | logger.info('Copying data from {0} to {1}', self.url, self.dest) 83 | return call_subprocess([self.cmd, self.method] + args, cwd=self.dest) 84 | 85 | def check_dest(self): 86 | ## If the target directory is empty we don't have to worry 87 | try: 88 | if not os.listdir(self.dest): 89 | return 90 | ## If self.dest does not exist we leave it as it is, because it will be 91 | ## created by the `else` clause (below) 92 | except OSError: 93 | pass 94 | if os.path.exists(self.dest): 95 | txt = 'The destination already exists: {0}\nWhat do you want to do ?'.format(self.dest) 96 | u = logger.ask(txt, choices={'destroy': 'd', 'backup': 'b', 'exit': 'x'}) 97 | if u == 'd': 98 | logger.info('Removing {0}...', self.dest) 99 | shutil.rmtree(self.dest) 100 | os.makedirs(self.dest) 101 | elif u == 'b': 102 | dst = os.path.join(os.path.dirname(self.dest), 103 | self.dest + '-pyg-backup') 104 | logger.info('Moving {0} to {1}', self.dest, dst) 105 | shutil.move(self.dest, dst) 106 | os.makedirs(self.dest) 107 | elif u == 'x': 108 | logger.info('Exiting...') 109 | sys.exit(0) 110 | else: 111 | os.makedirs(self.dest) -------------------------------------------------------------------------------- /pyg/vcs/gist.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import shutil 4 | 5 | try: 6 | import simplejson as json 7 | except ImportError: 8 | import json 9 | 10 | from pyg.inst import Installer 11 | from pyg.utils import TempDir 12 | from pyg.web import request 13 | from pyg.log import logger 14 | 15 | 16 | class Gist(object): 17 | 18 | _data = None 19 | _files = None 20 | 21 | def __init__(self, gist_id): 22 | self.gist_id = str(gist_id) 23 | 24 | def __repr__(self): 25 | return ''.format(self.gist_id, id(self)) 26 | 27 | @property 28 | def data(self): 29 | if self._data is not None: 30 | return self._data 31 | url = 'https://gist.github.com/api/v1/json/{0}'.format(self.gist_id) 32 | self._data = json.loads(request(url)) 33 | return self._data 34 | 35 | @property 36 | def files(self): 37 | if self._files is not None: 38 | return self._files 39 | self._files = self.data['gists'][0]['files'] 40 | return self._files 41 | 42 | def get_file_content(self, filename): 43 | url = 'https://gist.github.com/raw/{0}/{1}'.format(self.gist_id, filename) 44 | return request(url) 45 | 46 | def download(self, dest, accumulator): 47 | logger.info('Retrieving file names') 48 | for filename in self.files: 49 | logger.indent = 0 50 | logger.info('Downloading {0}', filename) 51 | logger.indent = 8 52 | path = os.path.abspath(os.path.join(dest, filename)) 53 | if os.path.exists(path): 54 | txt = 'The destination already exists: {0}\nWhat do you want to do'.format(path) 55 | u = logger.ask(txt, choices={'destroy': 'd', 'backup': 'b', 'exit': 'x'}) 56 | 57 | if u == 'd': 58 | logger.info('Removing {0}', path) 59 | os.remove(path) 60 | elif u == 'b': 61 | d = path + '.pyg-backup' 62 | logger.info('Moving {0} to {1}', path, d) 63 | shutil.copyfile(path, d) 64 | elif u == 'x': 65 | logger.info('Exiting...') 66 | sys.exit(0) 67 | with open(path, 'w') as f: 68 | logger.info('Writing data into {0}', filename) 69 | f.write(self.get_file_content(filename)) 70 | accumulator.add(filename) 71 | 72 | 73 | class GistInstaller(object): 74 | ''' 75 | Install a Github Gist. ``url`` must be in the form:: 76 | 77 | gist+{gist_id} 78 | ''' 79 | 80 | def __init__(self, url): 81 | if url.startswith('gist+'): 82 | url = url[5:] 83 | self.gist = Gist(url) 84 | 85 | def install(self): 86 | with TempDir() as tempdir: 87 | acc = set() 88 | self.gist.download(tempdir, acc) 89 | if 'setup.py' not in self.gist.files: 90 | logger.fatal('Error: gist must contain at least the `setup.py` file') 91 | Installer.from_dir(tempdir, 'gist {0}'.format(self.gist.gist_id)) -------------------------------------------------------------------------------- /pyg/vcs/vcs.py: -------------------------------------------------------------------------------- 1 | from pyg.vcs.base import VCS 2 | from pyg.utils import call_setup, print_output 3 | from pyg.core import InstallationError 4 | from pyg.log import logger 5 | 6 | 7 | __all__ = ['Git', 'Hg', 'Bzr', 'Svn', 'Local'] 8 | 9 | 10 | class Git(VCS): 11 | 12 | CMD = 'git' 13 | METHOD = 'clone' 14 | 15 | def __init__(self, url, dest=None): 16 | if url.startswith('git+'): 17 | url = url[4:] 18 | self.url, self.package_name = self.parse_url(url) 19 | super(Git, self).__init__(dest or self.package_name) 20 | 21 | 22 | class Hg(VCS): 23 | 24 | CMD = 'hg' 25 | METHOD = 'clone' 26 | 27 | def __init__(self, url, dest=None): 28 | if url.startswith('hg+'): 29 | url = url[3:] 30 | self.url, self.package_name = self.parse_url(url) 31 | super(Hg, self).__init__(dest or self.package_name) 32 | 33 | 34 | class Bzr(VCS): 35 | 36 | CMD = 'bzr' 37 | METHOD = 'branch' 38 | 39 | def __init__(self, url, dest=None): 40 | if url.startswith('bzr+'): 41 | url = url[4:] 42 | self.url, self.package_name = self.parse_url(url) 43 | super(Bzr, self).__init__(dest or self.package_name) 44 | 45 | 46 | class Svn(VCS): 47 | 48 | CMD = 'svn' 49 | METHOD = 'checkout' 50 | 51 | def __init__(self, url, dest=None): 52 | if url.startswith('svn+'): 53 | url = url[4:] 54 | self.url, self.package_name = self.parse_url(url) 55 | super(Svn, self).__init__(dest or self.package_name) 56 | 57 | 58 | class Local(VCS): 59 | def __init__(self, url, dest=None): 60 | if url.startswith('dir+'): 61 | url = url[4:] 62 | self.dest, self.package_name = self.parse_url(url) 63 | 64 | def develop(self): 65 | logger.info('Running setup.py develop for {0}', self.package_name) 66 | code, output = call_setup(self.dest, ['develop']) 67 | if code != 0: 68 | logger.fatal('Error: setup.py develop did not install {0}', self.package_name) 69 | print_output(output, 'setup.py develop') 70 | raise InstallationError('setup.py did not install {0}'.format(self.package_name)) 71 | logger.info('{0} installed succesfully', self.package_name) -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import pyg 4 | 5 | try: 6 | import setuptools 7 | except ImportError: 8 | from distribute_setup import use_setuptools 9 | use_setuptools() 10 | 11 | from setuptools import setup 12 | 13 | 14 | # Remove *.pyc files, since setuptools doesn't do that (even with the 15 | # exclude_package_data keyword) 16 | for dir, subdirs, files in os.walk(os.path.abspath('.')): 17 | for file in files: 18 | if file.endswith('.pyc'): 19 | os.remove(os.path.join(dir, file)) 20 | 21 | requires = ['setuptools', 'pkgtools>=0.7.1', 'argh'] 22 | if sys.version_info[:2] < (2, 7): 23 | requires.append('argparse>=1.2.1') 24 | 25 | pyg_console_scripts = [ 26 | 'pyg = pyg:main', 27 | 'pyg{0}.{1} = pyg:main'.format(*sys.version_info[:2]) 28 | ] 29 | 30 | if '--single-exe' in sys.argv: 31 | del pyg_console_scripts[0] 32 | sys.argv.remove('--single-exe') 33 | 34 | with open('README.rst') as f: 35 | long_desc = f.read() 36 | 37 | 38 | setup(name='pyg', 39 | version=pyg.__version__, 40 | url='http://pyg.readthedocs.org/', 41 | download_url='http://pypi.python.org/pypi/pyg', 42 | license='MIT', 43 | author='Michele Lacchia & Fabien Devaux', 44 | author_email='michelelacchia@gmail.com', 45 | maintainer='Michele Lacchia', 46 | maintainer_email='michelelacchia@gmail.com', 47 | description='Python Package Manager', 48 | long_description=long_desc, 49 | classifiers=[ 50 | 'Development Status :: 4 - Beta', 51 | 'Intended Audience :: Developers', 52 | 'License :: OSI Approved :: MIT License', 53 | 'Programming Language :: Python :: 2.6', 54 | 'Programming Language :: Python :: 2.7', 55 | 'Topic :: Software Development :: Build Tools' 56 | ], 57 | platforms='any', 58 | packages=['pyg'], 59 | include_package_data=True, 60 | keywords=['python', 'easy_install', 'pip', 'setuptools', 'package manager', \ 61 | 'command line', 'CLI'], 62 | install_requires=requires, 63 | provides=['pyg'], 64 | zip_safe=False, 65 | entry_points={ 66 | 'console_scripts': pyg_console_scripts 67 | } 68 | ) 69 | 70 | -------------------------------------------------------------------------------- /tests/README.txt: -------------------------------------------------------------------------------- 1 | Run "lettuce" in this folder to run tests 2 | If you want to keep the same virtualenv between runs, 3 | set the KEEPENV environment variable to a folder of your choice. 4 | -------------------------------------------------------------------------------- /tests/features/check.feature: -------------------------------------------------------------------------------- 1 | Feature: check 2 | 3 | Scenario: Install grin and bottle 4 | Given I use "standard-2.7" environment 5 | Given I execute pyg install -U bottle 6 | Then the return code is 0 7 | Given I execute pyg install -U grin 8 | Then the return code is 0 9 | 10 | Scenario: Check Uninstalled packages 11 | Given I use "standard-2.7" environment 12 | When I execute pyg check 13 | Then one line matches False 14 | Then the return code is 0 15 | 16 | Examples: 17 | | pkg | 18 | | invalid | 19 | | flask | 20 | | ZODB3 | 21 | | dulwich | 22 | 23 | Scenario: Check Installed packages 24 | Given I use "standard-2.7" environment 25 | When I execute pyg check 26 | Then one line matches True 27 | Then the return code is 0 28 | 29 | Examples: 30 | | pkg | 31 | | bottle | 32 | | grin | 33 | -------------------------------------------------------------------------------- /tests/features/help.feature: -------------------------------------------------------------------------------- 1 | Feature: Be able to get some help 2 | 3 | Scenario Outline: Global help 4 | Given I use "standard-2.7" environment 5 | 6 | Given I execute pyg 7 | Then one line matches usage: 8 | Then one line matches Available commands: 9 | Then the return code is 0 10 | 11 | Examples: 12 | 13 | | opt | 14 | | -h | 15 | | help | 16 | | --help | 17 | 18 | Scenario Outline: Specific help 19 | Given I use "standard-2.7" environment 20 | Given I execute pyg 21 | Then one line matches usage: 22 | Then one line matches optional arguments: 23 | Then the return code is 0 24 | 25 | Examples: 26 | | cmd | opt | 27 | | install | -h | 28 | | install | --help | 29 | | search | --help | 30 | | list | --help | 31 | | update | --help | 32 | | shell | --help | 33 | | help | --help | 34 | | check | --help | 35 | | site | --help | 36 | | pack | --help | 37 | | remove | --help | 38 | | download | --help | 39 | | bundle | --help | 40 | 41 | Scenario Outline: Version 42 | Given I use "standard-2.7" environment 43 | Given I execute pyg 44 | Then one line matches 0.8a 45 | Then the return code is 0 46 | 47 | Examples 48 | | cmd | 49 | | -v | 50 | | --version | 51 | -------------------------------------------------------------------------------- /tests/features/install.feature: -------------------------------------------------------------------------------- 1 | Feature: Some basic installation scenarios 2 | 3 | Scenario: Install hg-git 4 | Given I use "standard-2.7" environment 5 | When I execute pyg check hg-git 6 | Then one line matches False 7 | Then the return code is 0 8 | When I execute pyg install hg-git 9 | Then the return code is 0 10 | When I execute pyg check hg-git 11 | Then one line matches True 12 | Then the return code is 0 13 | 14 | Scenario Outline: Install misc packages 15 | Given I use "standard-2.7" environment 16 | When I execute pyg install 17 | Then the return code is 0 18 | 19 | Examples: 20 | | pkg | 21 | | bottle | 22 | | mercurial | 23 | | lk | 24 | | grin | 25 | | gevent | 26 | | buzhug | 27 | 28 | Scenario: Pack & bundle them individually 29 | Given I use "standard-2.7" environment 30 | When I remove "foobar.zip" 31 | When I execute pyg pack foobar.zip 32 | Then the return code is 0 33 | 34 | Examples: 35 | | pkg | 36 | | bottle | 37 | | mercurial | 38 | | lk | 39 | | grin | 40 | | gevent | 41 | | buzhug | 42 | 43 | Scenario: Dump installed packages list 44 | Given I use "standard-2.7" environment 45 | Given I use "tmp_install" temporary folder 46 | When I execute pyg site -f requirements 47 | Then many lines match [a-zA-Z_]+==\d+[\d.]*.* 48 | Then the return code is 0 49 | 50 | Scenario Outline: Upgrade all packages 51 | Given I use "standard-2.7" environment 52 | When I execute pyg install -U 53 | Then the return code is 0 54 | 55 | Examples: 56 | | pkg | 57 | | bottle | 58 | | mercurial | 59 | | lk | 60 | | grin | 61 | | gevent | 62 | | buzhug | 63 | 64 | 65 | Scenario: Bundle all packages 66 | Given I use "standard-2.7" environment 67 | Given I use "tmp_install" temporary folder 68 | When I execute pyg bundle -r requirements mybundle 69 | Then the return code is 0 70 | 71 | 72 | Scenario Outline: Remove all packages 73 | Given I use "standard-2.7" environment 74 | When I execute pyg remove -y 75 | Then the return code is 0 76 | 77 | Examples: 78 | | pkg | 79 | | bottle | 80 | | mercurial | 81 | | lk | 82 | | grin | 83 | | gevent | 84 | | buzhug | 85 | 86 | 87 | Scenario: UnBundle all packages 88 | Given I use "standard-2.7" environment 89 | Given I use "tmp_install" temporary folder 90 | When I execute pyg install -r requirements mybundle 91 | Then the return code is 0 92 | 93 | 94 | Scenario Outline: Install misc packages (with operators) 95 | Given I use "standard-2.7" environment 96 | Given I use "tmp_install" temporary folder 97 | When I execute pyg install -U "" 98 | Then the return code is 0 99 | 100 | Examples: 101 | | pkg | 102 | | bottle==0.9.5 | 103 | | dulwich!=0.7.1 | 104 | | grin<=1.2.1 | 105 | | buzhug>1.6 | 106 | | gevent<0.13.6 | 107 | | lk>=1 | 108 | 109 | 110 | Scenario Outline: Install packages from VCS 111 | Given I use "standard-2.7" environment 112 | Given I use "tmp_install" temporary folder 113 | When I execute pyg install 114 | Then the return code is 0 115 | 116 | Examples: 117 | | url | 118 | | git+git@github.com:rubik/pkgtools.git#egg=pkgtools | 119 | | git+https://github.com/fdev31/zicbee.git#egg=zicbee | 120 | | hg+https://rubik@bitbucket.org/neithere/argh#egg=argh | 121 | | hg+https://rubik@bitbucket.org/birkenfeld/sphinx#egg=sphinx | 122 | | bzr+lp:wadllib#egg=wadllib | 123 | 124 | Scenario Outline: Install misc packages (with multiple versions) 125 | Given I use "standard-2.7" environment 126 | Given I use "tmp_install" temporary folder 127 | When I execute pyg install -U "" 128 | Then the return code is 0 129 | 130 | Examples: 131 | | pkg | 132 | | pkgtools>=0.4,!=0.5,<0.6.3 | 133 | | zicbee>0.7,!=0.8,<1 | 134 | 135 | Scenario Outline: Install pyg_testsource and pyg_testegg packages 136 | Given I use "standard-2.7" environment 137 | Given I use "tmp_install" temporary folder 138 | When I execute pyg install 139 | Then the return code is 0 140 | Then is installed and works properly 141 | 142 | Examples: 143 | | pkg | 144 | | pyg_testsource | 145 | | pyg_testegg | 146 | 147 | 148 | #Scenario: Install a dev package [not supported yet, see #78] 149 | # Given I use "standard-2.7" environment 150 | # When I execute pyg install bottle==dev 151 | # Then the return code is 255 152 | -------------------------------------------------------------------------------- /tests/features/remove.feature: -------------------------------------------------------------------------------- 1 | Feature: Be able to remove packages 2 | 3 | Scenario: Install then Uninstall 4 | Given I use "standard-2.7" environment 5 | Given I execute pyg check 6 | Then a single line matches False 7 | Then the return code is 0 8 | Then no trace of * is found 9 | When I execute pyg install 10 | Then the return code is 0 11 | When I execute pyg check 12 | Then a single line matches True 13 | Then the return code is 0 14 | When I execute pyg remove -y 15 | Then the return code is 0 16 | When I execute pyg check 17 | Then one line matches False 18 | Then the return code is 0 19 | Then no trace of * is found 20 | 21 | Examples: 22 | | pkg | 23 | | gevent | 24 | | bottle | 25 | | grin | 26 | | lk | 27 | | mercurial | 28 | -------------------------------------------------------------------------------- /tests/features/search.feature: -------------------------------------------------------------------------------- 1 | Feature: Use search functionality 2 | 3 | Scenario: search for popular package bottle 4 | Given I use "standard-2.7" environment 5 | When I execute pyg search bottle 6 | Then many lines matches bottle 7 | When I execute pyg search -e bottle 8 | Then one line matches bottle 9 | When I execute pyg search -ae bottle 10 | Then many line matches bottle\s+ 11 | 12 | Scenario: search for unique package "zicbee-mplayer" 13 | Given I use "standard-2.7" environment 14 | When I execute pyg search zicbee-mplayer 15 | Then all lines matches zicbee-mplayer 16 | When I execute pyg search -e zicbee-mplayer 17 | Then one line matches zicbee-mplayer 18 | When I execute pyg search -ae zicbee-mplayer 19 | Then all lines matches zicbee-mplayer\s+ 20 | -------------------------------------------------------------------------------- /tests/features/steps.py: -------------------------------------------------------------------------------- 1 | from lettuce import * 2 | import os 3 | import re 4 | import sys 5 | import json 6 | import shutil 7 | import tempfile 8 | import itertools 9 | from glob import glob 10 | from subprocess import Popen, PIPE, call, check_call 11 | # FIXME: Can we do this? 12 | from pyg.utils import check_output, CalledProcessError # for Python 2.6 13 | 14 | USE_PROXY = False 15 | 16 | VENV_DIR = os.getenv('KEEPENV', None) or tempfile.mkdtemp(prefix='pyg_env_') 17 | 18 | if VENV_DIR.isdigit() or VENV_DIR.lower() in ('true', 'yes'): 19 | VENV_DIR = tempfile.mkdtemp(prefix='pyg_env_') 20 | 21 | try: 22 | ENVIRONMENTS = dict((p, os.path.join(VENV_DIR, p)) 23 | for p in os.listdir(VENV_DIR) 24 | if os.path.isdir(os.path.join(VENV_DIR, p)) 25 | ) 26 | except OSError: 27 | ENVIRONMENTS = {} 28 | 29 | def refresh_json(): 30 | if 'KEEPENV' in os.environ: 31 | info_path = os.path.join(VENV_DIR, 'infos.js') 32 | try: 33 | d = json.load(open(info_path)) 34 | d.update(ENVIRONMENTS) 35 | except (OSError, IOError), e: 36 | d = ENVIRONMENTS 37 | print "Warn: can't read js file: %r"%e 38 | print "env: ", d 39 | json.dump(d, open(info_path, 'w')) 40 | 41 | @before.each_feature 42 | def remove_std_packages(*a): 43 | for env, path in ENVIRONMENTS.iteritems(): 44 | try: 45 | call([os.path.join(path, 'bin', 'pyg'), 46 | 'remove', 47 | '-y', 48 | 'bottle', 49 | 'buzhug', 50 | 'mercurial', 51 | 'lk', 52 | 'grin', 53 | 'hg-git', 54 | 'gevent']) 55 | except OSError: 56 | pass 57 | 58 | @before.all 59 | def init_env(*a): 60 | """ Ensure VENV_DIR folder exists and is empty (unless KEEPENV is used) """ 61 | tmp_folder = '/tmp' 62 | world.temp_content = set(os.listdir(tmp_folder)) 63 | 64 | if 'KEEPENV' in os.environ and os.path.exists(VENV_DIR): 65 | return 66 | try: 67 | print "removing old venv..." 68 | shutil.rmtree(VENV_DIR) 69 | except OSError, e: 70 | print "Unable to remove %r : %r"%(VENV_DIR, e) 71 | os.mkdir(VENV_DIR) 72 | 73 | @after.all 74 | def destroy_env(*a): 75 | """ Ensure VENV_DIR folder is destroyed, set KEEPENV=/some/folder to disable. """ 76 | tmp_folder = '/tmp' 77 | current_temp_content = set(os.listdir(tmp_folder)) 78 | if current_temp_content != world.temp_content: 79 | print "WARNING: Stale temporary files:" 80 | for name in current_temp_content.difference(world.temp_content): 81 | print "* ", name 82 | 83 | if 'KEEPENV' in os.environ: 84 | print "Env. path: %s" % VENV_DIR 85 | else: 86 | print "removing venv..." 87 | shutil.rmtree(VENV_DIR) 88 | ENVIRONMENTS.clear() 89 | refresh_json() 90 | 91 | @step('I use "(.*)" temporary folder') 92 | def given_i_use_tmp_folder(step, folder_name): 93 | p = os.path.join(VENV_DIR, folder_name) 94 | try: 95 | os.mkdir(p) 96 | except: 97 | pass 98 | 99 | os.chdir(p) 100 | world.cur_dir = folder_name 101 | 102 | @step('I remove "(.*)"') 103 | def i_remove_xxx(step, fname): 104 | if os.path.isdir(fname): 105 | shutil.rmtree(fname) 106 | elif os.path.exists(fname): 107 | os.unlink(fname) 108 | 109 | @step('there is (\d+) files') 110 | def there_is_xx_files(self, num): 111 | current = len(os.listdir(os.path.curdir))-2 112 | if current != int(num): 113 | raise AssertionError('Wrong number of files in %s, expected: %s, got: %s.'%( 114 | world.cur_dir, 115 | num, 116 | current)) 117 | 118 | @step('I use "(.*)" environment') 119 | def given_i_use_venv(step, env_name): 120 | world.env_path = os.path.join(VENV_DIR, env_name) 121 | # build python version from environment name 122 | py_version = re.compile('.*?(\d+\.?\d*)$').match(env_name) 123 | 124 | if py_version: 125 | py_version = py_version.groups()[0] 126 | 127 | # Create Venv if it can't be found 128 | if not os.path.exists(world.env_path): 129 | ENVIRONMENTS[env_name] = world.env_path 130 | if py_version: 131 | args = ['--python', 'python' + py_version] 132 | else: 133 | args = [] 134 | 135 | print "Installing venv..." 136 | call(['virtualenv'] + args + ['--no-site-packages', world.env_path]) 137 | 138 | # Install pyg if not found 139 | if not os.path.exists(os.path.join(world.env_path, 'bin', 'pyg')): 140 | print "Copying pyg..." 141 | dn = os.path.dirname 142 | pyg_dir = os.path.abspath(dn(dn(dn(__file__))) + '/') 143 | os.chdir(pyg_dir) 144 | install_dir = os.path.abspath(os.path.join(world.env_path, 'pyg-current')) 145 | if install_dir.startswith(pyg_dir): 146 | sys.exit("Can't use a virtual environment inside sources folder !") 147 | if os.path.exists(install_dir): 148 | shutil.rmtree(install_dir) 149 | shutil.copytree(os.path.curdir, install_dir) 150 | os.chdir(install_dir) 151 | print "Installing pyg..." 152 | call('. %s ; python %s develop' % ( 153 | os.path.join(world.env_path, 'bin', 'activate'), 154 | os.path.join(pyg_dir, 'setup.py'), 155 | ), shell=True) 156 | 157 | os.chdir(world.env_path) 158 | 159 | def wait_and_set(): 160 | if world.proc: 161 | world.stdout, world.stderr = world.proc.communicate() 162 | world.returncode = world.proc.returncode 163 | world.proc = None 164 | return (world.returncode, world.stdout, world.stderr) 165 | 166 | @step('I execute pyg\s+(.*)') 167 | def i_execute(step, cmd): 168 | if ' ' in cmd: 169 | cmd, args = (x.strip() for x in cmd.split(None, 1)) 170 | else: 171 | args = '' 172 | 173 | if cmd != 'help' and not cmd.startswith('-'): 174 | if USE_PROXY and cmd in ( 175 | "install", 176 | "bundle", 177 | "download", 178 | "pack", 179 | "search", 180 | "list", 181 | "update"): 182 | cmd = "pyg -d %s -i http://localhost:8080 %s"%(cmd, args) 183 | else: 184 | cmd = "pyg -d %s %s"%(cmd, args) 185 | else: 186 | cmd = "pyg %s %s"%(cmd, args) 187 | 188 | prefixed_cmd = os.path.join(world.env_path, 'bin', cmd) 189 | world.last_cmd = prefixed_cmd 190 | world.proc = Popen(prefixed_cmd, 191 | shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE) 192 | 193 | @step('no trace of \s*(\w+).*') 194 | def no_trace_of(step, what): 195 | matches = glob(os.path.join(world.env_path, 'lib', 'python*', 'site-packages', what)) 196 | if 0 != len(matches): 197 | pfx_len = 1 + len(glob(os.path.join(world.env_path, 'lib', 'python*', 'site-packages'))[0]) 198 | raise AssertionError('Found stale files : %s' % ', '.join(x[pfx_len:] for x in matches)) 199 | 200 | @step('the return code is (\d+)') 201 | def the_return_code_is(step, given_code): 202 | code, out, err = wait_and_set() 203 | if int(given_code) != code: 204 | out_desc = "cmd: %s\nstdout:\n%s\nstderr:\n%s\n-EOF-\n" % ( 205 | world.last_cmd, out, err) 206 | raise AssertionError('Invalid code, got %s, expected %s\n%s' % ( 207 | code, given_code, out_desc)) 208 | 209 | @step('(?Ppyg_testsource|pyg_testegg) is installed and works properly') 210 | def is_installed_and_works_properly(step, pkg): 211 | bin = os.path.join(world.env_path, 'bin') 212 | try: 213 | python_bin = os.path.join(bin, 'python') 214 | check_call([python_bin, '-c', 'import {0};assert {0}.PKG_TYPE=={0!r}'.format(pkg)]) 215 | except CalledProcessError: 216 | raise AssertionError('Cannot import %s, package not installed correctly' % (pkg)) 217 | # test command line output 218 | output = check_output([os.path.join(bin, pkg)]) 219 | assert output == 'Pyg test', 'Expected %s, got %s' % (pkg, output) 220 | 221 | @step('(?P\d+|many|all|one|a single|no)\s*(?P\w+)?\s*lines? matche?s?\s+(?P.*)') 222 | def x_line_matches(step, num, expression, what): 223 | # prepare arguments 224 | if num in ('one', 'a single'): 225 | num = 1 226 | if not what: 227 | what = 'stdout' 228 | 229 | # get result & count matching lines 230 | cnt = itertools.count() 231 | total = itertools.count() 232 | code, out, err = wait_and_set() 233 | r = re.compile(expression) 234 | for line in (err if 'err' in what else out).split('\n'): 235 | if not line.strip(): 236 | continue 237 | 238 | if r.match(line): 239 | cnt.next() 240 | total.next() 241 | 242 | # handle result 243 | 244 | cnt = cnt.next() 245 | total = total.next() 246 | 247 | err_desc = "\ncmd:%s\nINFO:\nExpect %s \"%s\" (%s) \nstdout: %s\nstderr: %s\n" % ( 248 | world.last_cmd, num, expression, what, out, err) 249 | 250 | if num == 'all': 251 | if cnt != total: 252 | if abs(cnt-total) < 2: 253 | print "Warning ! Log file tolerance, got %s instead of %s"%(cnt, total) 254 | else: 255 | raise AssertionError('Some line mismatch!'+err_desc) 256 | elif num == 'no': 257 | if cnt: 258 | raise AssertionError('Got matches!'+err_desc) 259 | elif num == 'many': 260 | if cnt == 0: 261 | raise AssertionError('No match!'+err_desc) 262 | elif cnt == 1: 263 | raise AssertionError('Single match!'+err_desc) 264 | elif cnt != int(num): 265 | if cnt == 0: 266 | raise AssertionError('No match!'+err_desc) 267 | else: 268 | raise AssertionError('Expected %s matches, got %d%s' % (num, cnt, err_desc)) 269 | -------------------------------------------------------------------------------- /tests/features/test_errors.feature: -------------------------------------------------------------------------------- 1 | Feature: Test if errors are reported correctly 2 | Scenario: install not existing package 3 | Given I use "standard-2.7" environment 4 | When I execute pyg install thispackageiscertainlynotinpypi 5 | Then no stderr line matches \s*Traceback 6 | Then the return code is 1 7 | 8 | 9 | -------------------------------------------------------------------------------- /tests/pypi_cache_server.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Pypi cache server 3 | Original author: Victor-mortal 4 | ''' 5 | 6 | import os 7 | import httplib 8 | import urlparse 9 | import logging 10 | import locale 11 | import json 12 | import hashlib 13 | import webob 14 | import gevent 15 | from gevent import wsgi as wsgi_fast, pywsgi as wsgi, monkey 16 | 17 | CACHE_DIR = '.cache' 18 | wsgi = wsgi_fast # comment to use pywsgi 19 | 20 | host = '0.0.0.0' 21 | port = 8080 22 | 23 | 24 | class Proxy(object): 25 | """A WSGI based web proxy application 26 | """ 27 | 28 | def __init__(self, chunkSize=4096, timeout=60, dropHeaders=['transfer-encoding'], pypiHost=None, log=None): 29 | """ 30 | @param log: logger of logging library 31 | """ 32 | self.log = log 33 | if self.log is None: 34 | self.log = logging.getLogger('proxy') 35 | 36 | self.chunkSize = chunkSize 37 | self.timeout = timeout 38 | self.dropHeaders = dropHeaders 39 | self.pypiHost = pypiHost 40 | 41 | def yieldData(self, response, cache_file=None): 42 | while True: 43 | data = response.read(self.chunkSize) 44 | yield data 45 | if cache_file: 46 | cache_file.write(data) 47 | if len(data) < self.chunkSize: 48 | break 49 | if cache_file: 50 | cache_file.close() 51 | 52 | def _rewrite(self, req, start_response): 53 | path = req.path_info 54 | if req.query_string: 55 | path += '?' + req.query_string 56 | parts = urlparse.urlparse(path) 57 | headers = req.headers 58 | 59 | md = hashlib.md5() 60 | md.update(' '.join('%s:%s'%v for v in headers.iteritems())) 61 | md.update(path) 62 | 63 | cache_file = os.path.join(CACHE_DIR, md.hexdigest()) 64 | if os.path.exists(cache_file): 65 | o = json.load( open(cache_file+'.js', 'rb') ) 66 | start_response(o['response'], o['headers']) 67 | return self.yieldData( open(cache_file) ) 68 | 69 | self.log.debug('Request from %s to %s', req.remote_addr, path) 70 | 71 | url = path 72 | conn = httplib.HTTPConnection(self.pypiHost, timeout=self.timeout) 73 | #headers['X-Forwarded-For'] = req.remote_addr 74 | #headers['X-Real-IP'] = req.remote_addr 75 | 76 | try: 77 | conn.request(req.method, url, headers=headers, body=req.body) 78 | response = conn.getresponse() 79 | except Exception, e: 80 | msg = str(e) 81 | if os.name == 'nt': 82 | _, encoding = locale.getdefaultlocale() 83 | msg = msg.decode(encoding) 84 | self.log.warn('Bad gateway with reason: %s', msg, exc_info=True) 85 | start_response('502 Bad gateway', []) 86 | return ['Bad gateway'] 87 | 88 | headers = [(k, v) for (k, v) in response.getheaders()\ 89 | if k not in self.dropHeaders] 90 | start_response('%s %s' % (response.status, response.reason), 91 | headers) 92 | json.dump( {'headers': headers, 'response': '%s %s' % (response.status, response.reason)}, open(cache_file+'.js', 'wb')) 93 | return self.yieldData(response, cache_file=open(cache_file, 'wb')) 94 | 95 | def __call__(self, env, start_response): 96 | req = webob.Request(env) 97 | return self._rewrite(req, start_response) 98 | 99 | 100 | if __name__ == '__main__': 101 | if not os.path.isdir(CACHE_DIR): 102 | os.mkdir(CACHE_DIR) 103 | 104 | monkey.patch_all() 105 | handler = Proxy(pypiHost='pypi.python.org:80') 106 | 107 | wsgi.WSGIServer((host, port), handler).serve_forever() 108 | run() 109 | -------------------------------------------------------------------------------- /tests/run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | import subprocess 5 | 6 | cache_server = subprocess.Popen([sys.executable, 'pypi_cache_server.py']) 7 | 8 | try: 9 | from lettuce.lettuce_cli import main 10 | except ImportError: 11 | want_install = raw_input("Lettuce package is not detected,\nauto-install (ONLY WORKS AS ADMIN) ? ") in 'yY' 12 | else: 13 | want_install = False 14 | try: 15 | import pyg 16 | except ImportError: 17 | sys.path.insert(0, os.path.abspath(os.path.pardir)) 18 | import pyg 19 | 20 | if want_install: 21 | # now the path is altered, import pyg 22 | import pyg 23 | # try to run lettuce's installation or upgrade 24 | try: 25 | r = pyg.main(['install', '-A', 'lettuce']) 26 | except (Exception, SystemExit) as e: 27 | print("Install failed ! Code:", e) 28 | else: 29 | if r: 30 | print "Install failed ! Code:", r 31 | 32 | from lettuce.lettuce_cli import main 33 | 34 | # go to that file's folder and set KEEPENV if not set 35 | os.chdir(os.path.dirname(os.path.abspath(__file__))) 36 | if 'KEEPENV' not in os.environ: 37 | from tempfile import mkdtemp 38 | os.environ['KEEPENV'] = mkdtemp('_test_env', 'pyg_') 39 | 40 | # start lettuce ! 41 | try: 42 | r = main() 43 | except: 44 | r = 1 45 | finally: 46 | os.kill(cache_server.pid, 15) 47 | print "waiting for http server to shut down" 48 | cache_server.wait() 49 | sys.exit(r) 50 | -------------------------------------------------------------------------------- /tests/test26.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2.6 2 | import os 3 | from compileall import compile_dir 4 | 5 | src_dir = os.path.join(os.path.pardir, 'pyg') 6 | 7 | for root, dirs, files in os.walk(src_dir): 8 | for fname in files: 9 | if fname.endswith('.pyc') or fname.endswith('.pyo'): 10 | os.unlink( os.path.join(root, fname) ) 11 | 12 | compile_dir(src_dir) 13 | 14 | --------------------------------------------------------------------------------