├── VERSION ├── doc ├── .themes │ ├── .gitignore │ ├── flask │ │ ├── theme.conf │ │ ├── relations.html │ │ ├── layout.html │ │ └── static │ │ │ ├── small_flask.css │ │ │ └── flasky.css_t │ ├── flask_small │ │ ├── theme.conf │ │ ├── layout.html │ │ └── static │ │ │ └── flasky.css_t │ ├── README │ ├── LICENSE │ └── flask_theme_support.py ├── source │ ├── pipeline.rst │ ├── common.rst │ └── index.rst ├── Makefile └── conf.py ├── MANIFEST.in ├── .gitignore ├── setup.py ├── UNLICENSE ├── src └── calabash │ ├── __init__.py │ ├── common.py │ └── pipeline.py ├── README.rst └── distribute_setup.py /VERSION: -------------------------------------------------------------------------------- 1 | 0.0.3 2 | -------------------------------------------------------------------------------- /doc/.themes/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.pyo 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include distribute_setup.py 2 | include VERSION 3 | -------------------------------------------------------------------------------- /doc/.themes/flask/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = flasky.css 4 | pygments_style = flask_theme_support.FlaskyStyle 5 | 6 | [options] 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info 2 | *.pyc 3 | *.pyo 4 | .DS_Store 5 | build 6 | dist 7 | MANIFEST 8 | test/example/*.sqlite3 9 | doc/.build 10 | distribute-*.egg 11 | distribute-*.tar.gz 12 | -------------------------------------------------------------------------------- /doc/.themes/flask_small/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = flasky.css 4 | nosidebar = true 5 | pygments_style = flask_theme_support.FlaskyStyle 6 | 7 | [options] 8 | index_logo = '' 9 | index_logo_height = 120px 10 | github_fork = '' 11 | -------------------------------------------------------------------------------- /doc/source/pipeline.rst: -------------------------------------------------------------------------------- 1 | :mod:`~calabash.pipeline` 2 | ========================= 3 | 4 | :mod:`calabash.pipeline` contains the main implementation of calabash: the 5 | :class:`PipeLine` class. It also features the :func:`pipe` decorator to allow 6 | you to create your own pipeline components from simple Python generator 7 | functions. 8 | 9 | .. automodule:: calabash.pipeline 10 | :members: 11 | -------------------------------------------------------------------------------- /doc/source/common.rst: -------------------------------------------------------------------------------- 1 | :mod:`~calabash.common` 2 | ========================= 3 | 4 | :mod:`calabash.common` defines some helpful pipeline components, borrowed 5 | straight from UNIX but adapted to work with arbitrary Python objects instead of 6 | just simple text streams. 7 | 8 | .. automodule:: calabash.common 9 | :members: 10 | 11 | Defined in this module: 12 | 13 | * :func:`cat` 14 | * :func:`curl` 15 | * :func:`echo` 16 | * :func:`filter` 17 | * :func:`grep` 18 | * :func:`map` 19 | * :func:`pretty_printer` 20 | * :func:`sed` 21 | * :func:`sh` 22 | -------------------------------------------------------------------------------- /doc/.themes/flask/relations.html: -------------------------------------------------------------------------------- 1 |

Related Topics

2 | 20 | -------------------------------------------------------------------------------- /doc/.themes/flask/layout.html: -------------------------------------------------------------------------------- 1 | {%- extends "basic/layout.html" %} 2 | {%- block extrahead %} 3 | {{ super() }} 4 | {% if theme_touch_icon %} 5 | 6 | {% endif %} 7 | 9 | {% endblock %} 10 | {%- block relbar2 %}{% endblock %} 11 | {%- block footer %} 12 | 16 | {%- endblock %} 17 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os.path as p 5 | 6 | from distribute_setup import use_setuptools; use_setuptools() 7 | from setuptools import setup, find_packages 8 | 9 | VERSION = open(p.join(p.dirname(p.abspath(__file__)), 'VERSION')).read().strip() 10 | 11 | setup( 12 | name = 'calabash', 13 | version = VERSION, 14 | author = "Zachary Voase", 15 | author_email = "z@zacharyvoase.com", 16 | url = 'http://github.com/zacharyvoase/calabash', 17 | description = "Bash-style pipelining syntax for Python generators.", 18 | packages = find_packages(where='src'), 19 | package_dir = {'': 'src'}, 20 | test_suite = 'calabash._get_tests', 21 | ) 22 | -------------------------------------------------------------------------------- /doc/.themes/flask_small/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "basic/layout.html" %} 2 | {% block header %} 3 | {{ super() }} 4 | {% if pagename == 'index' %} 5 |
6 | {% endif %} 7 | {% endblock %} 8 | {% block footer %} 9 | {% if pagename == 'index' %} 10 |
11 | {% endif %} 12 | {% endblock %} 13 | {# do not display relbars #} 14 | {% block relbar1 %}{% endblock %} 15 | {% block relbar2 %} 16 | {% if theme_github_fork %} 17 | Fork me on GitHub 19 | {% endif %} 20 | {% endblock %} 21 | {% block sidebar1 %}{% endblock %} 22 | {% block sidebar2 %}{% endblock %} 23 | -------------------------------------------------------------------------------- /doc/source/index.rst: -------------------------------------------------------------------------------- 1 | Calabash Documentation 2 | ====================== 3 | 4 | Calabash aims to bring bash-style pipelining to Python generators. 5 | 6 | A short example:: 7 | 8 | >>> from calabash.common import grep 9 | >>> pl = iter(['python', 'ruby', 'jython']) | grep(r'yt') 10 | >>> pl 11 | 12 | >>> for item in pl: 13 | ... print item 14 | python 15 | jython 16 | 17 | To see some examples of simple but useful pipeline components, check out the 18 | :mod:`calabash.common` module. To get started writing your own, read the 19 | :class:`~calabash.pipeline` documentation. 20 | 21 | Installation 22 | ------------ 23 | 24 | You can get the module from PyPI:: 25 | 26 | pip install calabash 27 | 28 | 29 | Table of Contents 30 | ----------------- 31 | 32 | .. toctree:: 33 | :maxdepth: 2 34 | 35 | pipeline 36 | common 37 | -------------------------------------------------------------------------------- /doc/.themes/README: -------------------------------------------------------------------------------- 1 | Flask Sphinx Styles 2 | =================== 3 | 4 | This repository contains sphinx styles for Flask and Flask related 5 | projects. To use this style in your Sphinx documentation, follow 6 | this guide: 7 | 8 | 1. put this folder as _themes into your docs folder. Alternatively 9 | you can also use git submodules to check out the contents there. 10 | 2. add this to your conf.py: 11 | 12 | sys.path.append(os.path.abspath('_themes')) 13 | html_theme_path = ['_themes'] 14 | html_theme = 'flask' 15 | 16 | The following themes exist: 17 | 18 | - 'flask' - the standard flask documentation theme for large 19 | projects 20 | - 'flask_small' - small one-page theme. Intended to be used by 21 | very small addon libraries for flask. 22 | 23 | The following options exist for the flask_small theme: 24 | 25 | [options] 26 | index_logo = '' filename of a picture in _static 27 | to be used as replacement for the 28 | h1 in the index.rst file. 29 | index_logo_height = 120px height of the index logo 30 | github_fork = '' repository name on github for the 31 | "fork me" badge 32 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to -------------------------------------------------------------------------------- /doc/.themes/flask/static/small_flask.css: -------------------------------------------------------------------------------- 1 | /* 2 | * small_flask.css_t 3 | * ~~~~~~~~~~~~~~~~~ 4 | * 5 | * :copyright: Copyright 2010 by Armin Ronacher. 6 | * :license: Flask Design License, see LICENSE for details. 7 | */ 8 | 9 | body { 10 | margin: 0; 11 | padding: 20px 30px; 12 | } 13 | 14 | div.documentwrapper { 15 | float: none; 16 | background: white; 17 | } 18 | 19 | div.sphinxsidebar { 20 | display: block; 21 | float: none; 22 | width: 102.5%; 23 | margin: 50px -30px -20px -30px; 24 | padding: 10px 20px; 25 | background: #333; 26 | color: white; 27 | } 28 | 29 | div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, 30 | div.sphinxsidebar h3 a { 31 | color: white; 32 | } 33 | 34 | div.sphinxsidebar a { 35 | color: #aaa; 36 | } 37 | 38 | div.sphinxsidebar p.logo { 39 | display: none; 40 | } 41 | 42 | div.document { 43 | width: 100%; 44 | margin: 0; 45 | } 46 | 47 | div.related { 48 | display: block; 49 | margin: 0; 50 | padding: 10px 0 20px 0; 51 | } 52 | 53 | div.related ul, 54 | div.related ul li { 55 | margin: 0; 56 | padding: 0; 57 | } 58 | 59 | div.footer { 60 | display: none; 61 | } 62 | 63 | div.bodywrapper { 64 | margin: 0; 65 | } 66 | 67 | div.body { 68 | min-height: 0; 69 | padding: 0; 70 | } 71 | -------------------------------------------------------------------------------- /src/calabash/__init__.py: -------------------------------------------------------------------------------- 1 | from pipeline import pipe 2 | import common 3 | 4 | 5 | def _get_tests(): 6 | import doctest 7 | import inspect 8 | import sys 9 | import unittest 10 | 11 | def _from_module(module, object): 12 | """Backported fix for http://bugs.python.org/issue1108.""" 13 | if module is None: 14 | return True 15 | elif inspect.getmodule(object) is not None: 16 | return module is inspect.getmodule(object) 17 | elif inspect.isfunction(object): 18 | return module.__dict__ is object.func_globals 19 | elif inspect.isclass(object): 20 | return module.__name__ == object.__module__ 21 | elif hasattr(object, '__module__'): 22 | return module.__name__ == object.__module__ 23 | elif isinstance(object, property): 24 | return True # [XX] no way not be sure. 25 | else: 26 | raise ValueError("object must be a class or function") 27 | finder = doctest.DocTestFinder() 28 | finder._from_module = _from_module 29 | 30 | suite = unittest.TestSuite() 31 | for name, module in sys.modules.iteritems(): 32 | if name.startswith('calabash'): 33 | try: 34 | mod_suite = doctest.DocTestSuite(module, test_finder=finder) 35 | except ValueError: 36 | continue 37 | suite.addTests(mod_suite) 38 | return suite 39 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | calabash 2 | ======== 3 | 4 | Calabash aims to bring bash-style pipelining to Python generators. 5 | 6 | A short example:: 7 | 8 | >>> from calabash.common import grep 9 | >>> pl = iter(['python', 'ruby', 'jython']) | grep(r'yt') 10 | >>> pl 11 | 12 | >>> for item in pl: 13 | ... print item 14 | python 15 | jython 16 | 17 | Installation 18 | ------------ 19 | 20 | You can get the module from PyPI:: 21 | 22 | pip install calabash 23 | 24 | (Un)license 25 | =========== 26 | 27 | This is free and unencumbered software released into the public domain. 28 | 29 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 30 | software, either in source code form or as a compiled binary, for any purpose, 31 | commercial or non-commercial, and by any means. 32 | 33 | In jurisdictions that recognize copyright laws, the author or authors of this 34 | software dedicate any and all copyright interest in the software to the public 35 | domain. We make this dedication for the benefit of the public at large and to 36 | the detriment of our heirs and successors. We intend this dedication to be an 37 | overt act of relinquishment in perpetuity of all present and future rights to 38 | this software under copyright law. 39 | 40 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 41 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 42 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 43 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 44 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 45 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 46 | 47 | For more information, please refer to 48 | -------------------------------------------------------------------------------- /doc/.themes/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 by Armin Ronacher. 2 | 3 | Some rights reserved. 4 | 5 | Redistribution and use in source and binary forms of the theme, with or 6 | without modification, are permitted provided that the following conditions 7 | are met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above 13 | copyright notice, this list of conditions and the following 14 | disclaimer in the documentation and/or other materials provided 15 | with the distribution. 16 | 17 | * The names of the contributors may not be used to endorse or 18 | promote products derived from this software without specific 19 | prior written permission. 20 | 21 | We kindly ask you to only use these themes in an unmodified manner just 22 | for Flask and Flask-related products, not for unrelated projects. If you 23 | like the visual style and want to use it for your own projects, please 24 | consider making some larger changes to the themes (such as changing 25 | font faces, sizes, colors or margins). 26 | 27 | THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 28 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 29 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 30 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 31 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 32 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 33 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 34 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 35 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 36 | ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE 37 | POSSIBILITY OF SUCH DAMAGE. 38 | -------------------------------------------------------------------------------- /doc/.themes/flask_theme_support.py: -------------------------------------------------------------------------------- 1 | # flasky extensions. flasky pygments style based on tango style 2 | from pygments.style import Style 3 | from pygments.token import Keyword, Name, Comment, String, Error, \ 4 | Number, Operator, Generic, Whitespace, Punctuation, Other, Literal 5 | 6 | 7 | class FlaskyStyle(Style): 8 | background_color = "#f8f8f8" 9 | default_style = "" 10 | 11 | styles = { 12 | # No corresponding class for the following: 13 | #Text: "", # class: '' 14 | Whitespace: "underline #f8f8f8", # class: 'w' 15 | Error: "#a40000 border:#ef2929", # class: 'err' 16 | Other: "#000000", # class 'x' 17 | 18 | Comment: "italic #8f5902", # class: 'c' 19 | Comment.Preproc: "noitalic", # class: 'cp' 20 | 21 | Keyword: "bold #004461", # class: 'k' 22 | Keyword.Constant: "bold #004461", # class: 'kc' 23 | Keyword.Declaration: "bold #004461", # class: 'kd' 24 | Keyword.Namespace: "bold #004461", # class: 'kn' 25 | Keyword.Pseudo: "bold #004461", # class: 'kp' 26 | Keyword.Reserved: "bold #004461", # class: 'kr' 27 | Keyword.Type: "bold #004461", # class: 'kt' 28 | 29 | Operator: "#582800", # class: 'o' 30 | Operator.Word: "bold #004461", # class: 'ow' - like keywords 31 | 32 | Punctuation: "bold #000000", # class: 'p' 33 | 34 | # because special names such as Name.Class, Name.Function, etc. 35 | # are not recognized as such later in the parsing, we choose them 36 | # to look the same as ordinary variables. 37 | Name: "#000000", # class: 'n' 38 | Name.Attribute: "#c4a000", # class: 'na' - to be revised 39 | Name.Builtin: "#004461", # class: 'nb' 40 | Name.Builtin.Pseudo: "#3465a4", # class: 'bp' 41 | Name.Class: "#000000", # class: 'nc' - to be revised 42 | Name.Constant: "#000000", # class: 'no' - to be revised 43 | Name.Decorator: "#888", # class: 'nd' - to be revised 44 | Name.Entity: "#ce5c00", # class: 'ni' 45 | Name.Exception: "bold #cc0000", # class: 'ne' 46 | Name.Function: "#000000", # class: 'nf' 47 | Name.Property: "#000000", # class: 'py' 48 | Name.Label: "#f57900", # class: 'nl' 49 | Name.Namespace: "#000000", # class: 'nn' - to be revised 50 | Name.Other: "#000000", # class: 'nx' 51 | Name.Tag: "bold #004461", # class: 'nt' - like a keyword 52 | Name.Variable: "#000000", # class: 'nv' - to be revised 53 | Name.Variable.Class: "#000000", # class: 'vc' - to be revised 54 | Name.Variable.Global: "#000000", # class: 'vg' - to be revised 55 | Name.Variable.Instance: "#000000", # class: 'vi' - to be revised 56 | 57 | Number: "#990000", # class: 'm' 58 | 59 | Literal: "#000000", # class: 'l' 60 | Literal.Date: "#000000", # class: 'ld' 61 | 62 | String: "#4e9a06", # class: 's' 63 | String.Backtick: "#4e9a06", # class: 'sb' 64 | String.Char: "#4e9a06", # class: 'sc' 65 | String.Doc: "italic #8f5902", # class: 'sd' - like a comment 66 | String.Double: "#4e9a06", # class: 's2' 67 | String.Escape: "#4e9a06", # class: 'se' 68 | String.Heredoc: "#4e9a06", # class: 'sh' 69 | String.Interpol: "#4e9a06", # class: 'si' 70 | String.Other: "#4e9a06", # class: 'sx' 71 | String.Regex: "#4e9a06", # class: 'sr' 72 | String.Single: "#4e9a06", # class: 's1' 73 | String.Symbol: "#4e9a06", # class: 'ss' 74 | 75 | Generic: "#000000", # class: 'g' 76 | Generic.Deleted: "#a40000", # class: 'gd' 77 | Generic.Emph: "italic #000000", # class: 'ge' 78 | Generic.Error: "#ef2929", # class: 'gr' 79 | Generic.Heading: "bold #000080", # class: 'gh' 80 | Generic.Inserted: "#00A000", # class: 'gi' 81 | Generic.Output: "#888", # class: 'go' 82 | Generic.Prompt: "#745334", # class: 'gp' 83 | Generic.Strong: "bold #000000", # class: 'gs' 84 | Generic.Subheading: "bold #800080", # class: 'gu' 85 | Generic.Traceback: "bold #a40000", # class: 'gt' 86 | } 87 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = -c . 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) source 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/Calabash.qhcp" 76 | @echo "To view the help file:" 77 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Calabash.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/Calabash" 85 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Calabash" 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 | -------------------------------------------------------------------------------- /src/calabash/common.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import re 4 | 5 | from calabash.pipeline import pipe 6 | 7 | 8 | @pipe 9 | def echo(item): 10 | """ 11 | Yield a single item. Equivalent to ``iter([item])``, but nicer-looking. 12 | 13 | >>> list(echo(1)) 14 | [1] 15 | >>> list(echo('hello')) 16 | ['hello'] 17 | """ 18 | yield item 19 | 20 | @pipe 21 | def cat(*args, **kwargs): 22 | r""" 23 | Read a file. Passes directly through to a call to `open()`. 24 | 25 | >>> src_file = __file__.replace('.pyc', '.py') 26 | >>> for line in cat(src_file): 27 | ... if line.startswith('def cat'): 28 | ... print repr(line) 29 | 'def cat(*args, **kwargs):\n' 30 | """ 31 | return iter(open(*args, **kwargs)) 32 | 33 | 34 | @pipe 35 | def curl(url): 36 | """ 37 | Fetch a URL, yielding output line-by-line. 38 | 39 | >>> UNLICENSE = 'http://unlicense.org/UNLICENSE' 40 | >>> for line in curl(UNLICENSE): # doctest: +SKIP 41 | ... print line, 42 | This is free and unencumbered software released into the public domain. 43 | ... 44 | """ 45 | import urllib2 46 | conn = urllib2.urlopen(url) 47 | try: 48 | line = conn.readline() 49 | while line: 50 | yield line 51 | line = conn.readline() 52 | finally: 53 | conn.close() 54 | 55 | 56 | @pipe 57 | def grep(stdin, pattern_src): 58 | """ 59 | Filter strings on stdin for the given regex (uses :func:`re.search`). 60 | 61 | >>> list(iter(['cat', 'cabbage', 'conundrum', 'cathedral']) | grep(r'^ca')) 62 | ['cat', 'cabbage', 'cathedral'] 63 | """ 64 | pattern = re.compile(pattern_src) 65 | for line in stdin: 66 | if pattern.search(line): 67 | yield line 68 | 69 | 70 | @pipe 71 | def sed(stdin, pattern_src, replacement, exclusive=False): 72 | """ 73 | Apply :func:`re.sub` to each line on stdin with the given pattern/repl. 74 | 75 | >>> list(iter(['cat', 'cabbage']) | sed(r'^ca', 'fu')) 76 | ['fut', 'fubbage'] 77 | 78 | Upon encountering a non-matching line of input, :func:`sed` will pass it 79 | through as-is. If you want to change this behaviour to only yield lines 80 | which match the given pattern, pass `exclusive=True`:: 81 | 82 | >>> list(iter(['cat', 'nomatch']) | sed(r'^ca', 'fu')) 83 | ['fut', 'nomatch'] 84 | >>> list(iter(['cat', 'nomatch']) | sed(r'^ca', 'fu', exclusive=True)) 85 | ['fut'] 86 | """ 87 | pattern = re.compile(pattern_src) 88 | for line in stdin: 89 | match = pattern.search(line) 90 | if match: 91 | yield (line[:match.start()] + 92 | match.expand(replacement) + 93 | line[match.end():]) 94 | elif not exclusive: 95 | yield line 96 | 97 | 98 | @pipe 99 | def pretty_printer(stdin, **kwargs): 100 | """ 101 | Pretty print each item on stdin and pass it straight through. 102 | 103 | >>> for item in iter([{'a': 1}, ['b', 'c', 3]]) | pretty_printer(): 104 | ... pass 105 | {'a': 1} 106 | ['b', 'c', 3] 107 | """ 108 | import pprint 109 | for item in stdin: 110 | pprint.pprint(item, **kwargs) 111 | yield item 112 | 113 | 114 | @pipe 115 | def map(stdin, func): 116 | """ 117 | Map each item on stdin through the given function. 118 | 119 | >>> list(xrange(5) | map(lambda x: x + 2)) 120 | [2, 3, 4, 5, 6] 121 | """ 122 | for item in stdin: 123 | yield func(item) 124 | 125 | 126 | @pipe 127 | def filter(stdin, predicate): 128 | """ 129 | Only pass through items for which `predicate(item)` is truthy. 130 | 131 | >>> list(xrange(5) | filter(lambda x: x % 2 == 0)) 132 | [0, 2, 4] 133 | """ 134 | for item in stdin: 135 | if predicate(item): 136 | yield item 137 | 138 | 139 | @pipe 140 | def sh(stdin, command=None, check_success=False): 141 | r""" 142 | Run a shell command, send it input, and produce its output. 143 | 144 | >>> print ''.join(echo("h\ne\nl\nl\no") | sh('sort -u')) 145 | e 146 | h 147 | l 148 | o 149 | 150 | >>> for line in sh('echo Hello World'): 151 | ... print line, 152 | Hello World 153 | >>> for line in sh('false', check_success=True): 154 | ... print line, # doctest: +ELLIPSIS 155 | Traceback (most recent call last): 156 | ... 157 | CalledProcessError: Command '['false']' returned non-zero exit status 1 158 | """ 159 | import subprocess 160 | import shlex 161 | 162 | if command is None: 163 | stdin, command = (), stdin 164 | 165 | if isinstance(command, basestring): 166 | command = shlex.split(command) 167 | 168 | pipe = subprocess.Popen(command, 169 | stdin=subprocess.PIPE, 170 | stdout=subprocess.PIPE) 171 | 172 | try: 173 | for line in stdin: 174 | pipe.stdin.write(line) 175 | pipe.stdin.close() 176 | for line in pipe.stdout: 177 | yield line 178 | finally: 179 | result = pipe.wait() 180 | if check_success and result != 0: 181 | raise subprocess.CalledProcessError(result, command) 182 | -------------------------------------------------------------------------------- /doc/.themes/flask_small/static/flasky.css_t: -------------------------------------------------------------------------------- 1 | /* 2 | * flasky.css_t 3 | * ~~~~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- flasky theme based on nature theme. 6 | * 7 | * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | @import url("basic.css"); 13 | 14 | /* -- page layout ----------------------------------------------------------- */ 15 | 16 | body { 17 | font-family: 'Georgia', serif; 18 | font-size: 17px; 19 | color: #000; 20 | background: white; 21 | margin: 0; 22 | padding: 0; 23 | } 24 | 25 | div.documentwrapper { 26 | float: left; 27 | width: 100%; 28 | } 29 | 30 | div.bodywrapper { 31 | margin: 40px auto 0 auto; 32 | width: 700px; 33 | } 34 | 35 | hr { 36 | border: 1px solid #B1B4B6; 37 | } 38 | 39 | div.body { 40 | background-color: #ffffff; 41 | color: #3E4349; 42 | padding: 0 30px 30px 30px; 43 | } 44 | 45 | img.floatingflask { 46 | padding: 0 0 10px 10px; 47 | float: right; 48 | } 49 | 50 | div.footer { 51 | text-align: right; 52 | color: #888; 53 | padding: 10px; 54 | font-size: 14px; 55 | width: 650px; 56 | margin: 0 auto 40px auto; 57 | } 58 | 59 | div.footer a { 60 | color: #888; 61 | text-decoration: underline; 62 | } 63 | 64 | div.related { 65 | line-height: 32px; 66 | color: #888; 67 | } 68 | 69 | div.related ul { 70 | padding: 0 0 0 10px; 71 | } 72 | 73 | div.related a { 74 | color: #444; 75 | } 76 | 77 | /* -- body styles ----------------------------------------------------------- */ 78 | 79 | a { 80 | color: #004B6B; 81 | text-decoration: underline; 82 | } 83 | 84 | a:hover { 85 | color: #6D4100; 86 | text-decoration: underline; 87 | } 88 | 89 | div.body { 90 | padding-bottom: 40px; /* saved for footer */ 91 | } 92 | 93 | div.body h1, 94 | div.body h2, 95 | div.body h3, 96 | div.body h4, 97 | div.body h5, 98 | div.body h6 { 99 | font-family: 'Garamond', 'Georgia', serif; 100 | font-weight: normal; 101 | margin: 30px 0px 10px 0px; 102 | padding: 0; 103 | } 104 | 105 | {% if theme_index_logo %} 106 | div.indexwrapper h1 { 107 | text-indent: -999999px; 108 | background: url({{ theme_index_logo }}) no-repeat center center; 109 | height: {{ theme_index_logo_height }}; 110 | } 111 | {% endif %} 112 | 113 | div.body h2 { font-size: 180%; } 114 | div.body h3 { font-size: 150%; } 115 | div.body h4 { font-size: 130%; } 116 | div.body h5 { font-size: 100%; } 117 | div.body h6 { font-size: 100%; } 118 | 119 | a.headerlink { 120 | color: white; 121 | padding: 0 4px; 122 | text-decoration: none; 123 | } 124 | 125 | a.headerlink:hover { 126 | color: #444; 127 | background: #eaeaea; 128 | } 129 | 130 | div.body p, div.body dd, div.body li { 131 | line-height: 1.4em; 132 | } 133 | 134 | div.admonition { 135 | background: #fafafa; 136 | margin: 20px -30px; 137 | padding: 10px 30px; 138 | border-top: 1px solid #ccc; 139 | border-bottom: 1px solid #ccc; 140 | } 141 | 142 | div.admonition p.admonition-title { 143 | font-family: 'Garamond', 'Georgia', serif; 144 | font-weight: normal; 145 | font-size: 24px; 146 | margin: 0 0 10px 0; 147 | padding: 0; 148 | line-height: 1; 149 | } 150 | 151 | div.admonition p.last { 152 | margin-bottom: 0; 153 | } 154 | 155 | div.highlight{ 156 | background-color: white; 157 | } 158 | 159 | dt:target, .highlight { 160 | background: #FAF3E8; 161 | } 162 | 163 | div.note { 164 | background-color: #eee; 165 | border: 1px solid #ccc; 166 | } 167 | 168 | div.seealso { 169 | background-color: #ffc; 170 | border: 1px solid #ff6; 171 | } 172 | 173 | div.topic { 174 | background-color: #eee; 175 | } 176 | 177 | div.warning { 178 | background-color: #ffe4e4; 179 | border: 1px solid #f66; 180 | } 181 | 182 | p.admonition-title { 183 | display: inline; 184 | } 185 | 186 | p.admonition-title:after { 187 | content: ":"; 188 | } 189 | 190 | pre, tt { 191 | font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; 192 | font-size: 0.85em; 193 | } 194 | 195 | img.screenshot { 196 | } 197 | 198 | tt.descname, tt.descclassname { 199 | font-size: 0.95em; 200 | } 201 | 202 | tt.descname { 203 | padding-right: 0.08em; 204 | } 205 | 206 | img.screenshot { 207 | -moz-box-shadow: 2px 2px 4px #eee; 208 | -webkit-box-shadow: 2px 2px 4px #eee; 209 | box-shadow: 2px 2px 4px #eee; 210 | } 211 | 212 | table.docutils { 213 | border: 1px solid #888; 214 | -moz-box-shadow: 2px 2px 4px #eee; 215 | -webkit-box-shadow: 2px 2px 4px #eee; 216 | box-shadow: 2px 2px 4px #eee; 217 | } 218 | 219 | table.docutils td, table.docutils th { 220 | border: 1px solid #888; 221 | padding: 0.25em 0.7em; 222 | } 223 | 224 | table.field-list, table.footnote { 225 | border: none; 226 | -moz-box-shadow: none; 227 | -webkit-box-shadow: none; 228 | box-shadow: none; 229 | } 230 | 231 | table.footnote { 232 | margin: 15px 0; 233 | width: 100%; 234 | border: 1px solid #eee; 235 | } 236 | 237 | table.field-list th { 238 | padding: 0 0.8em 0 0; 239 | } 240 | 241 | table.field-list td { 242 | padding: 0; 243 | } 244 | 245 | table.footnote td { 246 | padding: 0.5em; 247 | } 248 | 249 | dl { 250 | margin: 0; 251 | padding: 0; 252 | } 253 | 254 | dl dd { 255 | margin-left: 30px; 256 | } 257 | 258 | pre { 259 | padding: 0; 260 | margin: 15px -30px; 261 | padding: 8px; 262 | line-height: 1.3em; 263 | padding: 7px 30px; 264 | background: #eee; 265 | border-radius: 2px; 266 | -moz-border-radius: 2px; 267 | -webkit-border-radius: 2px; 268 | } 269 | 270 | dl pre { 271 | margin-left: -60px; 272 | padding-left: 60px; 273 | } 274 | 275 | tt { 276 | background-color: #ecf0f3; 277 | color: #222; 278 | /* padding: 1px 2px; */ 279 | } 280 | 281 | tt.xref, a tt { 282 | background-color: #FBFBFB; 283 | } 284 | 285 | a:hover tt { 286 | background: #EEE; 287 | } 288 | -------------------------------------------------------------------------------- /src/calabash/pipeline.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from functools import wraps 4 | import itertools 5 | 6 | 7 | class PipeLine(object): 8 | 9 | """ 10 | A coroutine wrapper which enables pipelining syntax. 11 | 12 | :class:`PipeLine` allows you to flatten once-nested code just by wrapping 13 | your generators. The class provides combinators in the form of operators, 14 | allowing you to plug two generators together without having to nest lots of 15 | function calls. For example:: 16 | 17 | >>> def summer(stdin): 18 | ... sum = 0 19 | ... for item in stdin: 20 | ... sum += item 21 | ... yield sum 22 | >>> pipeline = PipeLine(lambda: iter([1, 2, 3, 4])) | PipeLine(summer) 23 | >>> pipeline 24 | | summer> 25 | >>> for item in pipeline: 26 | ... print item 27 | 1 28 | 3 29 | 6 30 | 10 31 | 32 | The yielded output of each generator in the chain becomes the input for the 33 | next. The rules for writing a pipeline function are simple: 34 | :class:`PipeLine` requires a callable which accepts a single argument (the 35 | input), and returns an iterator. The only exception is the first part of 36 | the pipeline, which should accept no arguments (as there will be no input). 37 | 38 | To create pipeline functions, use the :func:`pipe` decorator:: 39 | 40 | >>> @pipe 41 | ... def my_generator(): 42 | ... yield 1 43 | ... yield 2 44 | ... yield 3 45 | >>> pl = my_generator() 46 | >>> pl 47 | 48 | >>> for item in pl: 49 | ... print item 50 | 1 51 | 2 52 | 3 53 | 54 | If your pipeline accepts input, an iterator will be provided as the first 55 | argument to the function:: 56 | 57 | >>> @pipe 58 | ... def add_one(input): 59 | ... for item in input: 60 | ... yield item + 1 61 | >>> pl = my_generator() | add_one() 62 | >>> pl 63 | 64 | >>> for item in pl: 65 | ... print item 66 | 2 67 | 3 68 | 4 69 | 70 | Even with input, your functions can still accept other parameters:: 71 | 72 | >>> @pipe 73 | ... def adder(input, amount): 74 | ... for item in input: 75 | ... yield item + amount 76 | >>> pl = my_generator() | adder(3) 77 | >>> pl 78 | 79 | >>> for item in pl: 80 | ... print item 81 | 4 82 | 5 83 | 6 84 | 85 | Some operators are overridden to provide pipeline combinators (methods 86 | which take multiple pipelines and return a new pipeline). For example, 87 | multiplying two pipelines gets you their cross product:: 88 | 89 | >>> pl = my_generator() | (adder(3) * adder(6)) 90 | >>> pl 91 | 92 | >>> for item in pl: 93 | ... print item 94 | (4, 7) 95 | (4, 8) 96 | (4, 9) 97 | (5, 7) 98 | (5, 8) 99 | (5, 9) 100 | (6, 7) 101 | (6, 8) 102 | (6, 9) 103 | 104 | Adding two pipelines will chain the same input through both:: 105 | 106 | >>> pl = my_generator() | (adder(3) + adder(12)) 107 | >>> pl 108 | 109 | >>> for item in pl: 110 | ... print item 111 | 4 112 | 5 113 | 6 114 | 13 115 | 14 116 | 15 117 | """ 118 | 119 | __slots__ = ('coro_func',) 120 | 121 | def __init__(self, coro_func): 122 | self.coro_func = coro_func 123 | 124 | @property 125 | def __name__(self): 126 | return self.coro_func.__name__ 127 | 128 | def __repr__(self): 129 | return '' % getattr(self.coro_func, '__name__', repr(self.coro_func)) 130 | 131 | def __or__(self, target): 132 | return target.__ror__(self) 133 | 134 | def __ror__(self, source): 135 | r""" 136 | Connect two pipes so that one's output becomes the other's input. 137 | 138 | A simple example:: 139 | 140 | >>> from itertools import imap 141 | >>> p = (PipeLine(lambda: iter([1, 2, 3, 4])) | 142 | ... PipeLine(lambda stdin: imap(lambda x: x + 3, stdin))) 143 | >>> p 144 | | > 145 | >>> list(p) 146 | [4, 5, 6, 7] 147 | """ 148 | def pipe(): 149 | return self.coro_func(iter(source)) 150 | pipe.__name__ = '%s | %s' % ( 151 | getattr(source, '__name__', repr(source)), 152 | getattr(self.coro_func, '__name__', repr(self.coro_func))) 153 | return PipeLine(pipe) 154 | 155 | def __mul__(self, other): 156 | """ 157 | Yield the cross product between two alternative pipes. 158 | 159 | A simple example:: 160 | 161 | >>> @pipe 162 | ... def echo(values): 163 | ... for x in values: 164 | ... yield x 165 | >>> list(echo([0, 1]) * echo([9, 10])) 166 | [(0, 9), (0, 10), (1, 9), (1, 10)] 167 | """ 168 | def product(stdin=None): 169 | if stdin is None: 170 | return itertools.product(self, other) 171 | stdin1, stdin2 = itertools.tee(stdin, 2) 172 | return itertools.product((stdin1 | self), (stdin2 | other)) 173 | product.__name__ = '%s * %s' % ( 174 | getattr(self.coro_func, '__name__', repr(self.coro_func)), 175 | getattr(other, '__name__', repr(other))) 176 | return pipe(product)() 177 | 178 | def __add__(self, other): 179 | """ 180 | Yield the chained output of two alternative pipes. 181 | 182 | Example:: 183 | 184 | >>> @pipe 185 | ... def echo(values): 186 | ... for x in values: 187 | ... yield x 188 | >>> list(echo([1, 2, 3]) + echo([4, 5, 6])) 189 | [1, 2, 3, 4, 5, 6] 190 | """ 191 | def concat(stdin=None): 192 | if stdin is None: 193 | return itertools.chain(self, other) 194 | stdin1, stdin2 = itertools.tee(stdin, 2) 195 | return itertools.chain((stdin1 | self), (stdin2 | other)) 196 | concat.__name__ = '%s + %s' % ( 197 | getattr(self.coro_func, '__name__', repr(self.coro_func)), 198 | getattr(other, '__name__', repr(other))) 199 | return pipe(concat)() 200 | 201 | def __iter__(self): 202 | return self.coro_func() 203 | 204 | 205 | def pipe(func): 206 | """ 207 | Wrap a function as a pipeline. 208 | 209 | >>> @pipe 210 | ... def printer(stdin, outfile=None): 211 | ... for item in stdin: 212 | ... print >>outfile, item 213 | ... yield item 214 | >>> @pipe 215 | ... def echo(*values): 216 | ... for value in values: 217 | ... yield value 218 | >>> p = printer() 219 | >>> p 220 | 221 | >>> p = echo(1, 2, 3) | p 222 | >>> p 223 | 224 | >>> output = list(p) 225 | 1 226 | 2 227 | 3 228 | >>> output 229 | [1, 2, 3] 230 | """ 231 | @wraps(func) 232 | def wrapper(*args, **kwargs): 233 | @wraps(func) 234 | def coro_func(stdin=None): 235 | if stdin is None: 236 | return func(*args, **kwargs) 237 | return func(stdin, *args, **kwargs) 238 | return PipeLine(coro_func) 239 | return wrapper 240 | -------------------------------------------------------------------------------- /doc/.themes/flask/static/flasky.css_t: -------------------------------------------------------------------------------- 1 | /* 2 | * flasky.css_t 3 | * ~~~~~~~~~~~~ 4 | * 5 | * :copyright: Copyright 2010 by Armin Ronacher. 6 | * :license: Flask Design License, see LICENSE for details. 7 | */ 8 | 9 | {% set page_width = '940px' %} 10 | {% set sidebar_width = '220px' %} 11 | 12 | @import url("basic.css"); 13 | 14 | /* -- page layout ----------------------------------------------------------- */ 15 | 16 | body { 17 | font-family: 'Georgia', serif; 18 | font-size: 17px; 19 | background-color: white; 20 | color: #000; 21 | margin: 0; 22 | padding: 0; 23 | } 24 | 25 | div.document { 26 | width: {{ page_width }}; 27 | margin: 30px auto 0 auto; 28 | } 29 | 30 | div.documentwrapper { 31 | float: left; 32 | width: 100%; 33 | } 34 | 35 | div.bodywrapper { 36 | margin: 0 0 0 {{ sidebar_width }}; 37 | } 38 | 39 | div.sphinxsidebar { 40 | width: {{ sidebar_width }}; 41 | } 42 | 43 | hr { 44 | border: 1px solid #B1B4B6; 45 | } 46 | 47 | div.body { 48 | background-color: #ffffff; 49 | color: #3E4349; 50 | padding: 0 30px 0 30px; 51 | } 52 | 53 | img.floatingflask { 54 | padding: 0 0 10px 10px; 55 | float: right; 56 | } 57 | 58 | div.footer { 59 | width: {{ page_width }}; 60 | margin: 20px auto 30px auto; 61 | font-size: 14px; 62 | color: #888; 63 | text-align: right; 64 | } 65 | 66 | div.footer a { 67 | color: #888; 68 | } 69 | 70 | div.related { 71 | display: none; 72 | } 73 | 74 | div.sphinxsidebar a { 75 | color: #444; 76 | text-decoration: none; 77 | border-bottom: 1px dotted #999; 78 | } 79 | 80 | div.sphinxsidebar a:hover { 81 | border-bottom: 1px solid #999; 82 | } 83 | 84 | div.sphinxsidebar { 85 | font-size: 14px; 86 | line-height: 1.5; 87 | } 88 | 89 | div.sphinxsidebarwrapper { 90 | padding: 18px 10px; 91 | } 92 | 93 | div.sphinxsidebarwrapper p.logo { 94 | padding: 0 0 20px 0; 95 | margin: 0; 96 | text-align: center; 97 | } 98 | 99 | div.sphinxsidebar h3, 100 | div.sphinxsidebar h4 { 101 | font-family: 'Garamond', 'Georgia', serif; 102 | color: #444; 103 | font-size: 24px; 104 | font-weight: normal; 105 | margin: 0 0 5px 0; 106 | padding: 0; 107 | } 108 | 109 | div.sphinxsidebar h4 { 110 | font-size: 20px; 111 | } 112 | 113 | div.sphinxsidebar h3 a { 114 | color: #444; 115 | } 116 | 117 | div.sphinxsidebar p.logo a, 118 | div.sphinxsidebar h3 a, 119 | div.sphinxsidebar p.logo a:hover, 120 | div.sphinxsidebar h3 a:hover { 121 | border: none; 122 | } 123 | 124 | div.sphinxsidebar p { 125 | color: #555; 126 | margin: 10px 0; 127 | } 128 | 129 | div.sphinxsidebar ul { 130 | margin: 10px 0; 131 | padding: 0; 132 | color: #000; 133 | } 134 | 135 | div.sphinxsidebar input { 136 | border: 1px solid #ccc; 137 | font-family: 'Georgia', serif; 138 | font-size: 1em; 139 | } 140 | 141 | /* -- body styles ----------------------------------------------------------- */ 142 | 143 | a { 144 | color: #004B6B; 145 | text-decoration: underline; 146 | } 147 | 148 | a:hover { 149 | color: #6D4100; 150 | text-decoration: underline; 151 | } 152 | 153 | div.body h1, 154 | div.body h2, 155 | div.body h3, 156 | div.body h4, 157 | div.body h5, 158 | div.body h6 { 159 | font-family: 'Garamond', 'Georgia', serif; 160 | font-weight: normal; 161 | margin: 30px 0px 10px 0px; 162 | padding: 0; 163 | } 164 | 165 | div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } 166 | div.body h2 { font-size: 180%; } 167 | div.body h3 { font-size: 150%; } 168 | div.body h4 { font-size: 130%; } 169 | div.body h5 { font-size: 100%; } 170 | div.body h6 { font-size: 100%; } 171 | 172 | a.headerlink { 173 | color: #ddd; 174 | padding: 0 4px; 175 | text-decoration: none; 176 | } 177 | 178 | a.headerlink:hover { 179 | color: #444; 180 | background: #eaeaea; 181 | } 182 | 183 | div.body p, div.body dd, div.body li { 184 | line-height: 1.4em; 185 | } 186 | 187 | div.admonition { 188 | background: #fafafa; 189 | margin: 20px -30px; 190 | padding: 10px 30px; 191 | border-top: 1px solid #ccc; 192 | border-bottom: 1px solid #ccc; 193 | } 194 | 195 | div.admonition tt.xref, div.admonition a tt { 196 | border-bottom: 1px solid #fafafa; 197 | } 198 | 199 | dd div.admonition { 200 | margin-left: -60px; 201 | padding-left: 60px; 202 | } 203 | 204 | div.admonition p.admonition-title { 205 | font-family: 'Garamond', 'Georgia', serif; 206 | font-weight: normal; 207 | font-size: 24px; 208 | margin: 0 0 10px 0; 209 | padding: 0; 210 | line-height: 1; 211 | } 212 | 213 | div.admonition p.last { 214 | margin-bottom: 0; 215 | } 216 | 217 | div.highlight { 218 | background-color: white; 219 | } 220 | 221 | dt:target, .highlight { 222 | background: #FAF3E8; 223 | } 224 | 225 | div.note { 226 | background-color: #eee; 227 | border: 1px solid #ccc; 228 | } 229 | 230 | div.seealso { 231 | background-color: #ffc; 232 | border: 1px solid #ff6; 233 | } 234 | 235 | div.topic { 236 | background-color: #eee; 237 | } 238 | 239 | p.admonition-title { 240 | display: inline; 241 | } 242 | 243 | p.admonition-title:after { 244 | content: ":"; 245 | } 246 | 247 | pre, tt { 248 | font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; 249 | font-size: 0.9em; 250 | } 251 | 252 | img.screenshot { 253 | } 254 | 255 | tt.descname, tt.descclassname { 256 | font-size: 0.95em; 257 | } 258 | 259 | tt.descname { 260 | padding-right: 0.08em; 261 | } 262 | 263 | img.screenshot { 264 | -moz-box-shadow: 2px 2px 4px #eee; 265 | -webkit-box-shadow: 2px 2px 4px #eee; 266 | box-shadow: 2px 2px 4px #eee; 267 | } 268 | 269 | table.docutils { 270 | border: 1px solid #888; 271 | -moz-box-shadow: 2px 2px 4px #eee; 272 | -webkit-box-shadow: 2px 2px 4px #eee; 273 | box-shadow: 2px 2px 4px #eee; 274 | } 275 | 276 | table.docutils td, table.docutils th { 277 | border: 1px solid #888; 278 | padding: 0.25em 0.7em; 279 | } 280 | 281 | table.field-list, table.footnote { 282 | border: none; 283 | -moz-box-shadow: none; 284 | -webkit-box-shadow: none; 285 | box-shadow: none; 286 | } 287 | 288 | table.footnote { 289 | margin: 15px 0; 290 | width: 100%; 291 | border: 1px solid #eee; 292 | background: #fdfdfd; 293 | font-size: 0.9em; 294 | } 295 | 296 | table.footnote + table.footnote { 297 | margin-top: -15px; 298 | border-top: none; 299 | } 300 | 301 | table.field-list th { 302 | padding: 0 0.8em 0 0; 303 | } 304 | 305 | table.field-list td { 306 | padding: 0; 307 | } 308 | 309 | table.footnote td.label { 310 | width: 0px; 311 | padding: 0.3em 0 0.3em 0.5em; 312 | } 313 | 314 | table.footnote td { 315 | padding: 0.3em 0.5em; 316 | } 317 | 318 | dl { 319 | margin: 0; 320 | padding: 0; 321 | } 322 | 323 | dl dd { 324 | margin-left: 30px; 325 | } 326 | 327 | blockquote { 328 | margin: 0 0 0 30px; 329 | padding: 0; 330 | } 331 | 332 | ul, ol { 333 | margin: 10px 0 10px 30px; 334 | padding: 0; 335 | } 336 | 337 | pre { 338 | background: #eee; 339 | padding: 7px 30px; 340 | margin: 15px -30px; 341 | line-height: 1.3em; 342 | } 343 | 344 | dl pre, blockquote pre, li pre { 345 | margin-left: -60px; 346 | padding-left: 60px; 347 | } 348 | 349 | dl dl pre { 350 | margin-left: -90px; 351 | padding-left: 90px; 352 | } 353 | 354 | tt { 355 | background-color: #ecf0f3; 356 | color: #222; 357 | /* padding: 1px 2px; */ 358 | } 359 | 360 | tt.xref, a tt { 361 | background-color: #FBFBFB; 362 | border-bottom: 1px solid white; 363 | } 364 | 365 | a.reference { 366 | text-decoration: none; 367 | border-bottom: 1px dotted #004B6B; 368 | } 369 | 370 | a.reference:hover { 371 | border-bottom: 1px solid #6D4100; 372 | } 373 | 374 | a.footnote-reference { 375 | text-decoration: none; 376 | font-size: 0.7em; 377 | vertical-align: top; 378 | border-bottom: 1px dotted #004B6B; 379 | } 380 | 381 | a.footnote-reference:hover { 382 | border-bottom: 1px solid #6D4100; 383 | } 384 | 385 | a:hover tt { 386 | background: #EEE; 387 | } 388 | -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Calabash documentation build configuration file, created by 4 | # sphinx-quickstart on Thu Feb 3 11:35:05 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 | curdir = os.path.dirname(os.path.abspath(__file__)) 17 | 18 | # If extensions (or modules to document with autodoc) are in another directory, 19 | # add these directories to sys.path here. If the directory is relative to the 20 | # documentation root, use os.path.abspath to make it absolute, like shown here. 21 | sys.path.insert(0, os.path.abspath(curdir)) 22 | sys.path.insert(0, os.path.abspath(os.path.join(curdir, '.themes'))) 23 | sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(curdir), 'src'))) 24 | 25 | 26 | # -- General configuration ----------------------------------------------------- 27 | 28 | # If your documentation needs a minimal Sphinx version, state it here. 29 | #needs_sphinx = '1.0' 30 | 31 | # Add any Sphinx extension module names here, as strings. They can be extensions 32 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 33 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] 34 | 35 | # Add any paths that contain templates here, relative to this directory. 36 | templates_path = ['.templates'] 37 | 38 | # The suffix of source filenames. 39 | source_suffix = '.rst' 40 | 41 | # The encoding of source files. 42 | #source_encoding = 'utf-8-sig' 43 | 44 | # The master toctree document. 45 | master_doc = 'index' 46 | 47 | # General information about the project. 48 | project = u'Calabash' 49 | 50 | # The version info for the project you're documenting, acts as replacement for 51 | # |version| and |release|, also used in various other places throughout the 52 | # built documents. 53 | # 54 | 55 | # The short X.Y version. 56 | version = open(os.path.join(os.path.dirname(curdir), 'VERSION')).read() 57 | # The full version, including alpha/beta/rc tags. 58 | release = version 59 | 60 | # The language for content autogenerated by Sphinx. Refer to documentation 61 | # for a list of supported languages. 62 | #language = None 63 | 64 | # There are two options for replacing |today|: either, you set today to some 65 | # non-false value, then it is used: 66 | #today = '' 67 | # Else, today_fmt is used as the format for a strftime call. 68 | #today_fmt = '%B %d, %Y' 69 | 70 | # List of patterns, relative to source directory, that match files and 71 | # directories to ignore when looking for source files. 72 | exclude_patterns = [] 73 | 74 | # The reST default role (used for this markup: `text`) to use for all documents. 75 | #default_role = None 76 | 77 | # If true, '()' will be appended to :func: etc. cross-reference text. 78 | #add_function_parentheses = True 79 | 80 | # If true, the current module name will be prepended to all description 81 | # unit titles (such as .. function::). 82 | #add_module_names = True 83 | 84 | # If true, sectionauthor and moduleauthor directives will be shown in the 85 | # output. They are ignored by default. 86 | #show_authors = False 87 | 88 | # The name of the Pygments (syntax highlighting) style to use. 89 | pygments_style = 'sphinx' 90 | 91 | # A list of ignored prefixes for module index sorting. 92 | #modindex_common_prefix = [] 93 | 94 | 95 | # -- Options for HTML output --------------------------------------------------- 96 | 97 | # The theme to use for HTML and HTML Help pages. See the documentation for 98 | # a list of builtin themes. 99 | html_theme = 'flask' 100 | 101 | # Theme options are theme-specific and customize the look and feel of a theme 102 | # further. For a list of options available for each theme, see the 103 | # documentation. 104 | #html_theme_options = {} 105 | 106 | # Add any paths that contain custom themes here, relative to this directory. 107 | sys.path.append(os.path.abspath('.themes')) 108 | html_theme_path = ['.themes'] 109 | 110 | # The name for this set of Sphinx documents. If None, it defaults to 111 | # " v documentation". 112 | #html_title = None 113 | 114 | # A shorter title for the navigation bar. Default is the same as html_title. 115 | #html_short_title = None 116 | 117 | # The name of an image file (relative to this directory) to place at the top 118 | # of the sidebar. 119 | #html_logo = None 120 | 121 | # The name of an image file (within the static path) to use as favicon of the 122 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 123 | # pixels large. 124 | #html_favicon = None 125 | 126 | # Add any paths that contain custom static files (such as style sheets) here, 127 | # relative to this directory. They are copied after the builtin static files, 128 | # so a file named "default.css" will overwrite the builtin "default.css". 129 | html_static_path = ['.static'] 130 | 131 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 132 | # using the given strftime format. 133 | #html_last_updated_fmt = '%b %d, %Y' 134 | 135 | # If true, SmartyPants will be used to convert quotes and dashes to 136 | # typographically correct entities. 137 | #html_use_smartypants = True 138 | 139 | # Custom sidebar templates, maps document names to template names. 140 | #html_sidebars = {} 141 | 142 | # Additional templates that should be rendered to pages, maps page names to 143 | # template names. 144 | #html_additional_pages = {} 145 | 146 | # If false, no module index is generated. 147 | #html_domain_indices = True 148 | 149 | # If false, no index is generated. 150 | #html_use_index = True 151 | 152 | # If true, the index is split into individual pages for each letter. 153 | #html_split_index = False 154 | 155 | # If true, links to the reST sources are added to the pages. 156 | #html_show_sourcelink = True 157 | 158 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 159 | #html_show_sphinx = True 160 | 161 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 162 | html_show_copyright = False 163 | 164 | # If true, an OpenSearch description file will be output, and all pages will 165 | # contain a tag referring to it. The value of this option must be the 166 | # base URL from which the finished HTML is served. 167 | #html_use_opensearch = '' 168 | 169 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 170 | #html_file_suffix = None 171 | 172 | # Output file base name for HTML help builder. 173 | htmlhelp_basename = 'Calabashdoc' 174 | 175 | 176 | # -- Options for LaTeX output -------------------------------------------------- 177 | 178 | # The paper size ('letter' or 'a4'). 179 | #latex_paper_size = 'letter' 180 | 181 | # The font size ('10pt', '11pt' or '12pt'). 182 | #latex_font_size = '10pt' 183 | 184 | # Grouping the document tree into LaTeX files. List of tuples 185 | # (source start file, target name, title, author, documentclass [howto/manual]). 186 | latex_documents = [ 187 | ('index', 'Calabash.tex', u'Calabash Documentation', 188 | u'Zachary Voase', 'manual'), 189 | ] 190 | 191 | # The name of an image file (relative to this directory) to place at the top of 192 | # the title page. 193 | #latex_logo = None 194 | 195 | # For "manual" documents, if this is true, then toplevel headings are parts, 196 | # not chapters. 197 | #latex_use_parts = False 198 | 199 | # If true, show page references after internal links. 200 | #latex_show_pagerefs = False 201 | 202 | # If true, show URL addresses after external links. 203 | #latex_show_urls = False 204 | 205 | # Additional stuff for the LaTeX preamble. 206 | #latex_preamble = '' 207 | 208 | # Documents to append as an appendix to all manuals. 209 | #latex_appendices = [] 210 | 211 | # If false, no module index is generated. 212 | #latex_domain_indices = True 213 | 214 | 215 | # -- Options for manual page output -------------------------------------------- 216 | 217 | # One entry per manual page. List of tuples 218 | # (source start file, name, description, authors, manual section). 219 | man_pages = [ 220 | ('index', 'Calabash', u'Calabash Documentation', 221 | [u'Zachary Voase'], 1) 222 | ] 223 | -------------------------------------------------------------------------------- /distribute_setup.py: -------------------------------------------------------------------------------- 1 | #!python 2 | """Bootstrap distribute installation 3 | 4 | If you want to use setuptools in your package's setup.py, just include this 5 | file in the same directory with it, and add this to the top of your setup.py:: 6 | 7 | from distribute_setup import use_setuptools 8 | use_setuptools() 9 | 10 | If you want to require a specific version of setuptools, set a download 11 | mirror, or use an alternate download directory, you can do so by supplying 12 | the appropriate options to ``use_setuptools()``. 13 | 14 | This file can also be run as a script to install or upgrade setuptools. 15 | """ 16 | import os 17 | import sys 18 | import time 19 | import fnmatch 20 | import tempfile 21 | import tarfile 22 | from distutils import log 23 | 24 | try: 25 | from site import USER_SITE 26 | except ImportError: 27 | USER_SITE = None 28 | 29 | try: 30 | import subprocess 31 | 32 | def _python_cmd(*args): 33 | args = (sys.executable,) + args 34 | return subprocess.call(args) == 0 35 | 36 | except ImportError: 37 | # will be used for python 2.3 38 | def _python_cmd(*args): 39 | args = (sys.executable,) + args 40 | # quoting arguments if windows 41 | if sys.platform == 'win32': 42 | def quote(arg): 43 | if ' ' in arg: 44 | return '"%s"' % arg 45 | return arg 46 | args = [quote(arg) for arg in args] 47 | return os.spawnl(os.P_WAIT, sys.executable, *args) == 0 48 | 49 | DEFAULT_VERSION = "0.6.10" 50 | DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/" 51 | SETUPTOOLS_FAKED_VERSION = "0.6c11" 52 | 53 | SETUPTOOLS_PKG_INFO = """\ 54 | Metadata-Version: 1.0 55 | Name: setuptools 56 | Version: %s 57 | Summary: xxxx 58 | Home-page: xxx 59 | Author: xxx 60 | Author-email: xxx 61 | License: xxx 62 | Description: xxx 63 | """ % SETUPTOOLS_FAKED_VERSION 64 | 65 | 66 | def _install(tarball): 67 | # extracting the tarball 68 | tmpdir = tempfile.mkdtemp() 69 | log.warn('Extracting in %s', tmpdir) 70 | old_wd = os.getcwd() 71 | try: 72 | os.chdir(tmpdir) 73 | tar = tarfile.open(tarball) 74 | _extractall(tar) 75 | tar.close() 76 | 77 | # going in the directory 78 | subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) 79 | os.chdir(subdir) 80 | log.warn('Now working in %s', subdir) 81 | 82 | # installing 83 | log.warn('Installing Distribute') 84 | if not _python_cmd('setup.py', 'install'): 85 | log.warn('Something went wrong during the installation.') 86 | log.warn('See the error message above.') 87 | finally: 88 | os.chdir(old_wd) 89 | 90 | 91 | def _build_egg(egg, tarball, to_dir): 92 | # extracting the tarball 93 | tmpdir = tempfile.mkdtemp() 94 | log.warn('Extracting in %s', tmpdir) 95 | old_wd = os.getcwd() 96 | try: 97 | os.chdir(tmpdir) 98 | tar = tarfile.open(tarball) 99 | _extractall(tar) 100 | tar.close() 101 | 102 | # going in the directory 103 | subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) 104 | os.chdir(subdir) 105 | log.warn('Now working in %s', subdir) 106 | 107 | # building an egg 108 | log.warn('Building a Distribute egg in %s', to_dir) 109 | _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) 110 | 111 | finally: 112 | os.chdir(old_wd) 113 | # returning the result 114 | log.warn(egg) 115 | if not os.path.exists(egg): 116 | raise IOError('Could not build the egg.') 117 | 118 | 119 | def _do_download(version, download_base, to_dir, download_delay): 120 | egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg' 121 | % (version, sys.version_info[0], sys.version_info[1])) 122 | if not os.path.exists(egg): 123 | tarball = download_setuptools(version, download_base, 124 | to_dir, download_delay) 125 | _build_egg(egg, tarball, to_dir) 126 | sys.path.insert(0, egg) 127 | import setuptools 128 | setuptools.bootstrap_install_from = egg 129 | 130 | 131 | def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, 132 | to_dir=os.curdir, download_delay=15, no_fake=True): 133 | # making sure we use the absolute path 134 | to_dir = os.path.abspath(to_dir) 135 | was_imported = 'pkg_resources' in sys.modules or \ 136 | 'setuptools' in sys.modules 137 | try: 138 | try: 139 | import pkg_resources 140 | if not hasattr(pkg_resources, '_distribute'): 141 | if not no_fake: 142 | _fake_setuptools() 143 | raise ImportError 144 | except ImportError: 145 | return _do_download(version, download_base, to_dir, download_delay) 146 | try: 147 | pkg_resources.require("distribute>="+version) 148 | return 149 | except pkg_resources.VersionConflict: 150 | e = sys.exc_info()[1] 151 | if was_imported: 152 | sys.stderr.write( 153 | "The required version of distribute (>=%s) is not available,\n" 154 | "and can't be installed while this script is running. Please\n" 155 | "install a more recent version first, using\n" 156 | "'easy_install -U distribute'." 157 | "\n\n(Currently using %r)\n" % (version, e.args[0])) 158 | sys.exit(2) 159 | else: 160 | del pkg_resources, sys.modules['pkg_resources'] # reload ok 161 | return _do_download(version, download_base, to_dir, 162 | download_delay) 163 | except pkg_resources.DistributionNotFound: 164 | return _do_download(version, download_base, to_dir, 165 | download_delay) 166 | finally: 167 | if not no_fake: 168 | _create_fake_setuptools_pkg_info(to_dir) 169 | 170 | def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, 171 | to_dir=os.curdir, delay=15): 172 | """Download distribute from a specified location and return its filename 173 | 174 | `version` should be a valid distribute version number that is available 175 | as an egg for download under the `download_base` URL (which should end 176 | with a '/'). `to_dir` is the directory where the egg will be downloaded. 177 | `delay` is the number of seconds to pause before an actual download 178 | attempt. 179 | """ 180 | # making sure we use the absolute path 181 | to_dir = os.path.abspath(to_dir) 182 | try: 183 | from urllib.request import urlopen 184 | except ImportError: 185 | from urllib2 import urlopen 186 | tgz_name = "distribute-%s.tar.gz" % version 187 | url = download_base + tgz_name 188 | saveto = os.path.join(to_dir, tgz_name) 189 | src = dst = None 190 | if not os.path.exists(saveto): # Avoid repeated downloads 191 | try: 192 | log.warn("Downloading %s", url) 193 | src = urlopen(url) 194 | # Read/write all in one block, so we don't create a corrupt file 195 | # if the download is interrupted. 196 | data = src.read() 197 | dst = open(saveto, "wb") 198 | dst.write(data) 199 | finally: 200 | if src: 201 | src.close() 202 | if dst: 203 | dst.close() 204 | return os.path.realpath(saveto) 205 | 206 | 207 | def _patch_file(path, content): 208 | """Will backup the file then patch it""" 209 | existing_content = open(path).read() 210 | if existing_content == content: 211 | # already patched 212 | log.warn('Already patched.') 213 | return False 214 | log.warn('Patching...') 215 | _rename_path(path) 216 | f = open(path, 'w') 217 | try: 218 | f.write(content) 219 | finally: 220 | f.close() 221 | return True 222 | 223 | 224 | def _same_content(path, content): 225 | return open(path).read() == content 226 | 227 | def _no_sandbox(function): 228 | def __no_sandbox(*args, **kw): 229 | try: 230 | from setuptools.sandbox import DirectorySandbox 231 | def violation(*args): 232 | pass 233 | DirectorySandbox._old = DirectorySandbox._violation 234 | DirectorySandbox._violation = violation 235 | patched = True 236 | except ImportError: 237 | patched = False 238 | 239 | try: 240 | return function(*args, **kw) 241 | finally: 242 | if patched: 243 | DirectorySandbox._violation = DirectorySandbox._old 244 | del DirectorySandbox._old 245 | 246 | return __no_sandbox 247 | 248 | @_no_sandbox 249 | def _rename_path(path): 250 | new_name = path + '.OLD.%s' % time.time() 251 | log.warn('Renaming %s into %s', path, new_name) 252 | os.rename(path, new_name) 253 | return new_name 254 | 255 | def _remove_flat_installation(placeholder): 256 | if not os.path.isdir(placeholder): 257 | log.warn('Unkown installation at %s', placeholder) 258 | return False 259 | found = False 260 | for file in os.listdir(placeholder): 261 | if fnmatch.fnmatch(file, 'setuptools*.egg-info'): 262 | found = True 263 | break 264 | if not found: 265 | log.warn('Could not locate setuptools*.egg-info') 266 | return 267 | 268 | log.warn('Removing elements out of the way...') 269 | pkg_info = os.path.join(placeholder, file) 270 | if os.path.isdir(pkg_info): 271 | patched = _patch_egg_dir(pkg_info) 272 | else: 273 | patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO) 274 | 275 | if not patched: 276 | log.warn('%s already patched.', pkg_info) 277 | return False 278 | # now let's move the files out of the way 279 | for element in ('setuptools', 'pkg_resources.py', 'site.py'): 280 | element = os.path.join(placeholder, element) 281 | if os.path.exists(element): 282 | _rename_path(element) 283 | else: 284 | log.warn('Could not find the %s element of the ' 285 | 'Setuptools distribution', element) 286 | return True 287 | 288 | 289 | def _after_install(dist): 290 | log.warn('After install bootstrap.') 291 | placeholder = dist.get_command_obj('install').install_purelib 292 | _create_fake_setuptools_pkg_info(placeholder) 293 | 294 | @_no_sandbox 295 | def _create_fake_setuptools_pkg_info(placeholder): 296 | if not placeholder or not os.path.exists(placeholder): 297 | log.warn('Could not find the install location') 298 | return 299 | pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1]) 300 | setuptools_file = 'setuptools-%s-py%s.egg-info' % \ 301 | (SETUPTOOLS_FAKED_VERSION, pyver) 302 | pkg_info = os.path.join(placeholder, setuptools_file) 303 | if os.path.exists(pkg_info): 304 | log.warn('%s already exists', pkg_info) 305 | return 306 | 307 | log.warn('Creating %s', pkg_info) 308 | f = open(pkg_info, 'w') 309 | try: 310 | f.write(SETUPTOOLS_PKG_INFO) 311 | finally: 312 | f.close() 313 | 314 | pth_file = os.path.join(placeholder, 'setuptools.pth') 315 | log.warn('Creating %s', pth_file) 316 | f = open(pth_file, 'w') 317 | try: 318 | f.write(os.path.join(os.curdir, setuptools_file)) 319 | finally: 320 | f.close() 321 | 322 | def _patch_egg_dir(path): 323 | # let's check if it's already patched 324 | pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') 325 | if os.path.exists(pkg_info): 326 | if _same_content(pkg_info, SETUPTOOLS_PKG_INFO): 327 | log.warn('%s already patched.', pkg_info) 328 | return False 329 | _rename_path(path) 330 | os.mkdir(path) 331 | os.mkdir(os.path.join(path, 'EGG-INFO')) 332 | pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') 333 | f = open(pkg_info, 'w') 334 | try: 335 | f.write(SETUPTOOLS_PKG_INFO) 336 | finally: 337 | f.close() 338 | return True 339 | 340 | 341 | def _before_install(): 342 | log.warn('Before install bootstrap.') 343 | _fake_setuptools() 344 | 345 | 346 | def _under_prefix(location): 347 | if 'install' not in sys.argv: 348 | return True 349 | args = sys.argv[sys.argv.index('install')+1:] 350 | for index, arg in enumerate(args): 351 | for option in ('--root', '--prefix'): 352 | if arg.startswith('%s=' % option): 353 | top_dir = arg.split('root=')[-1] 354 | return location.startswith(top_dir) 355 | elif arg == option: 356 | if len(args) > index: 357 | top_dir = args[index+1] 358 | return location.startswith(top_dir) 359 | elif option == '--user' and USER_SITE is not None: 360 | return location.startswith(USER_SITE) 361 | return True 362 | 363 | 364 | def _fake_setuptools(): 365 | log.warn('Scanning installed packages') 366 | try: 367 | import pkg_resources 368 | except ImportError: 369 | # we're cool 370 | log.warn('Setuptools or Distribute does not seem to be installed.') 371 | return 372 | ws = pkg_resources.working_set 373 | try: 374 | setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools', 375 | replacement=False)) 376 | except TypeError: 377 | # old distribute API 378 | setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools')) 379 | 380 | if setuptools_dist is None: 381 | log.warn('No setuptools distribution found') 382 | return 383 | # detecting if it was already faked 384 | setuptools_location = setuptools_dist.location 385 | log.warn('Setuptools installation detected at %s', setuptools_location) 386 | 387 | # if --root or --preix was provided, and if 388 | # setuptools is not located in them, we don't patch it 389 | if not _under_prefix(setuptools_location): 390 | log.warn('Not patching, --root or --prefix is installing Distribute' 391 | ' in another location') 392 | return 393 | 394 | # let's see if its an egg 395 | if not setuptools_location.endswith('.egg'): 396 | log.warn('Non-egg installation') 397 | res = _remove_flat_installation(setuptools_location) 398 | if not res: 399 | return 400 | else: 401 | log.warn('Egg installation') 402 | pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO') 403 | if (os.path.exists(pkg_info) and 404 | _same_content(pkg_info, SETUPTOOLS_PKG_INFO)): 405 | log.warn('Already patched.') 406 | return 407 | log.warn('Patching...') 408 | # let's create a fake egg replacing setuptools one 409 | res = _patch_egg_dir(setuptools_location) 410 | if not res: 411 | return 412 | log.warn('Patched done.') 413 | _relaunch() 414 | 415 | 416 | def _relaunch(): 417 | log.warn('Relaunching...') 418 | # we have to relaunch the process 419 | args = [sys.executable] + sys.argv 420 | sys.exit(subprocess.call(args)) 421 | 422 | 423 | def _extractall(self, path=".", members=None): 424 | """Extract all members from the archive to the current working 425 | directory and set owner, modification time and permissions on 426 | directories afterwards. `path' specifies a different directory 427 | to extract to. `members' is optional and must be a subset of the 428 | list returned by getmembers(). 429 | """ 430 | import copy 431 | import operator 432 | from tarfile import ExtractError 433 | directories = [] 434 | 435 | if members is None: 436 | members = self 437 | 438 | for tarinfo in members: 439 | if tarinfo.isdir(): 440 | # Extract directories with a safe mode. 441 | directories.append(tarinfo) 442 | tarinfo = copy.copy(tarinfo) 443 | tarinfo.mode = 448 # decimal for oct 0700 444 | self.extract(tarinfo, path) 445 | 446 | # Reverse sort directories. 447 | if sys.version_info < (2, 4): 448 | def sorter(dir1, dir2): 449 | return cmp(dir1.name, dir2.name) 450 | directories.sort(sorter) 451 | directories.reverse() 452 | else: 453 | directories.sort(key=operator.attrgetter('name'), reverse=True) 454 | 455 | # Set correct owner, mtime and filemode on directories. 456 | for tarinfo in directories: 457 | dirpath = os.path.join(path, tarinfo.name) 458 | try: 459 | self.chown(tarinfo, dirpath) 460 | self.utime(tarinfo, dirpath) 461 | self.chmod(tarinfo, dirpath) 462 | except ExtractError: 463 | e = sys.exc_info()[1] 464 | if self.errorlevel > 1: 465 | raise 466 | else: 467 | self._dbg(1, "tarfile: %s" % e) 468 | 469 | 470 | def main(argv, version=DEFAULT_VERSION): 471 | """Install or upgrade setuptools and EasyInstall""" 472 | tarball = download_setuptools() 473 | _install(tarball) 474 | 475 | 476 | if __name__ == '__main__': 477 | main(sys.argv[1:]) 478 | --------------------------------------------------------------------------------