├── 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 |
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 |
--------------------------------------------------------------------------------