diff --git a/.gitignore b/.gitignore index 4b57895..9e1febf 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ dist/ paramiko.egg-info/ test.log docs/ +!sites/docs +_build diff --git a/.travis.yml b/.travis.yml index c9802a8..df7c225 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,8 +5,18 @@ python: install: # Self-install for setup.py-driven deps - pip install -e . - - pip install coveralls -script: coverage run --source=paramiko test.py --verbose + # Dev (doc/test running) requirements + - pip install coveralls # For coveralls.io specifically + - pip install -r dev-requirements.txt +script: + # Main tests, with coverage! + - coverage run --source=paramiko test.py --verbose + # Ensure documentation & invoke pipeline run OK. + # Run 'docs' first since its objects.inv is referred to by 'www'. + # Also force warnings to be errors since most of them tend to be actual + # problems. + - invoke docs -o -W + - invoke www -o -W notifications: irc: channels: "irc.freenode.org#paramiko" diff --git a/dev-requirements.txt b/dev-requirements.txt index f706c46..43c21e3 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,2 +1,10 @@ +# Older junk tox>=1.4,<1.5 epydoc>=3.0,<3.1 +# For newer tasks like building Sphinx docs. +# NOTE: Requires Python >=2.6 +invoke>=0.7.0 +invocations>=0.4.4 +sphinx>=1.1.3 +alabaster>=0.2.0 +releases>=0.2.4 diff --git a/sites/_shared_static/logo.png b/sites/_shared_static/logo.png new file mode 100644 index 0000000..bc76697 Binary files /dev/null and b/sites/_shared_static/logo.png differ diff --git a/sites/docs/conf.py b/sites/docs/conf.py new file mode 100644 index 0000000..0c7ffe5 --- /dev/null +++ b/sites/docs/conf.py @@ -0,0 +1,4 @@ +# Obtain shared config values +import os, sys +sys.path.append(os.path.abspath('..')) +from shared_conf import * diff --git a/sites/docs/index.rst b/sites/docs/index.rst new file mode 100644 index 0000000..08b3432 --- /dev/null +++ b/sites/docs/index.rst @@ -0,0 +1,6 @@ +Welcome to Paramiko's documentation! +==================================== + +This site covers Paramiko's usage & API documentation. For basic info on what +Paramiko is, including its public changelog & how the project is maintained, +please see `the main website `_. diff --git a/sites/shared_conf.py b/sites/shared_conf.py new file mode 100644 index 0000000..2b98654 --- /dev/null +++ b/sites/shared_conf.py @@ -0,0 +1,40 @@ +from datetime import datetime +import os +import sys + +import alabaster + + +# Alabaster theme +html_theme_path = [alabaster.get_path()] +# Paths relative to invoking conf.py - not this shared file +html_static_path = ['../_shared_static'] +html_theme = 'alabaster' +html_theme_options = { + 'description': "A Python implementation of SSHv2.", + 'github_user': 'paramiko', + 'github_repo': 'paramiko', + 'gittip_user': 'bitprophet', + 'analytics_id': 'UA-18486793-2', + + 'link': '#3782BE', + 'link_hover': '#3782BE', +} +html_sidebars = { + '**': [ + 'about.html', + 'navigation.html', + 'searchbox.html', + 'donate.html', + ] +} + +# Regular settings +project = u'Paramiko' +year = datetime.now().year +copyright = u'2013-%d Jeff Forcier, 2003-2012 Robey Pointer' % year +master_doc = 'index' +templates_path = ['_templates'] +exclude_trees = ['_build'] +source_suffix = '.rst' +default_role = 'obj' diff --git a/sites/www/_templates/rss.xml b/sites/www/_templates/rss.xml new file mode 100644 index 0000000..f6f9cbd --- /dev/null +++ b/sites/www/_templates/rss.xml @@ -0,0 +1,19 @@ + + + + + {{ title }} + {{ link }} + {{ description }} + {{ date }} + {% for link, title, desc, date in posts %} + + {{ link }} + {{ link }} + <![CDATA[{{ title }}]]> + + {{ date }} + + {% endfor %} + + diff --git a/sites/www/blog.py b/sites/www/blog.py new file mode 100644 index 0000000..3b129eb --- /dev/null +++ b/sites/www/blog.py @@ -0,0 +1,140 @@ +from collections import namedtuple +from datetime import datetime +import time +import email.utils + +from sphinx.util.compat import Directive +from docutils import nodes + + +class BlogDateDirective(Directive): + """ + Used to parse/attach date info to blog post documents. + + No nodes generated, since none are needed. + """ + has_content = True + + def run(self): + # Tag parent document with parsed date value. + self.state.document.blog_date = datetime.strptime( + self.content[0], "%Y-%m-%d" + ) + # Don't actually insert any nodes, we're already done. + return [] + +class blog_post_list(nodes.General, nodes.Element): + pass + +class BlogPostListDirective(Directive): + """ + Simply spits out a 'blog_post_list' temporary node for replacement. + + Gets replaced at doctree-resolved time - only then will all blog post + documents be written out (& their date directives executed). + """ + def run(self): + return [blog_post_list('')] + + +Post = namedtuple('Post', 'name doc title date opener') + +def get_posts(app): + # Obtain blog posts + post_names = filter(lambda x: x.startswith('blog/'), app.env.found_docs) + posts = map(lambda x: (x, app.env.get_doctree(x)), post_names) + # Obtain common data used for list page & RSS + data = [] + for post, doc in sorted(posts, key=lambda x: x[1].blog_date, reverse=True): + # Welp. No "nice" way to get post title. Thanks Sphinx. + title = doc[0][0][0] + # Date. This may or may not end up reflecting the required + # *input* format, but doing it here gives us flexibility. + date = doc.blog_date + # 1st paragraph as opener. TODO: allow a role or something marking + # where to actually pull from? + opener = doc.traverse(nodes.paragraph)[0] + data.append(Post(post, doc, title, date, opener)) + return data + +def replace_blog_post_lists(app, doctree, fromdocname): + """ + Replace blog_post_list nodes with ordered list-o-links to posts. + """ + # Obtain blog posts + post_names = filter(lambda x: x.startswith('blog/'), app.env.found_docs) + posts = map(lambda x: (x, app.env.get_doctree(x)), post_names) + # Build "list" of links/etc + post_links = [] + for post, doc, title, date, opener in get_posts(app): + # Link itself + uri = app.builder.get_relative_uri(fromdocname, post) + link = nodes.reference('', '', refdocname=post, refuri=uri) + # Title, bolded. TODO: use 'topic' or something maybe? + link.append(nodes.strong('', title)) + date = date.strftime("%Y-%m-%d") + # Meh @ not having great docutils nodes which map to this. + html = '
%s
' % date + timestamp = nodes.raw(text=html, format='html') + # NOTE: may group these within another element later if styling + # necessitates it + group = [timestamp, nodes.paragraph('', '', link), opener] + post_links.extend(group) + + # Replace temp node(s) w/ expanded list-o-links + for node in doctree.traverse(blog_post_list): + node.replace_self(post_links) + +def rss_timestamp(timestamp): + # Use horribly inappropriate module for its magical daylight-savings-aware + # timezone madness. Props to Tinkerer for the idea. + return email.utils.formatdate( + time.mktime(timestamp.timetuple()), + localtime=True + ) + +def generate_rss(app): + # Meh at having to run this subroutine like 3x per build. Not worth trying + # to be clever for now tho. + posts_ = get_posts(app) + # LOL URLs + root = app.config.rss_link + if not root.endswith('/'): + root += '/' + # Oh boy + posts = [ + ( + root + app.builder.get_target_uri(x.name), + x.title, + str(x.opener[0]), # Grab inner text element from paragraph + rss_timestamp(x.date), + ) + for x in posts_ + ] + location = 'blog/rss.xml' + context = { + 'title': app.config.project, + 'link': root, + 'atom': root + location, + 'description': app.config.rss_description, + # 'posts' is sorted by date already + 'date': rss_timestamp(posts_[0].date), + 'posts': posts, + } + yield (location, context, 'rss.xml') + +def setup(app): + # Link in RSS feed back to main website, e.g. 'http://paramiko.org' + app.add_config_value('rss_link', None, '') + # Ditto for RSS description field + app.add_config_value('rss_description', None, '') + # Interprets date metadata in blog post documents + app.add_directive('date', BlogDateDirective) + # Inserts blog post list node (in e.g. a listing page) for replacement + # below + app.add_node(blog_post_list) + app.add_directive('blog-posts', BlogPostListDirective) + # Performs abovementioned replacement + app.connect('doctree-resolved', replace_blog_post_lists) + # Generates RSS page from whole cloth at page generation step + app.connect('html-collect-pages', generate_rss) diff --git a/sites/www/blog.rst b/sites/www/blog.rst new file mode 100644 index 0000000..af9651e --- /dev/null +++ b/sites/www/blog.rst @@ -0,0 +1,16 @@ +==== +Blog +==== + +.. blog-posts directive gets replaced with an ordered list of blog posts. + +.. blog-posts:: + + +.. The following toctree ensures blog posts get processed. + +.. toctree:: + :hidden: + :glob: + + blog/* diff --git a/sites/www/blog/first-post.rst b/sites/www/blog/first-post.rst new file mode 100644 index 0000000..7b07507 --- /dev/null +++ b/sites/www/blog/first-post.rst @@ -0,0 +1,7 @@ +=========== +First post! +=========== + +A blog post. + +.. date:: 2013-12-04 diff --git a/sites/www/blog/second-post.rst b/sites/www/blog/second-post.rst new file mode 100644 index 0000000..c4463f3 --- /dev/null +++ b/sites/www/blog/second-post.rst @@ -0,0 +1,7 @@ +=========== +Another one +=========== + +.. date:: 2013-12-05 + +Indeed! diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst new file mode 100644 index 0000000..9df85c4 --- /dev/null +++ b/sites/www/changelog.rst @@ -0,0 +1,116 @@ +========= +Changelog +========= + +* :release:`1.12.2 <2014-01-21>` +* :release:`1.11.4 <2014-01-21>` 193 +* :release:`1.10.6 <2014-01-21>` 193 +* :bug:`193` (and its attentant PRs :issue:`230` & :issue:`253`): Fix SSH agent + problems present on Windows. Thanks to David Hobbs for initial report and to + Aarni Koskela & Olle Lundberg for the patches. +* :release:`1.12.1 <2014-01-08>` +* :release:`1.11.3 <2014-01-08>` 176 +* :release:`1.10.5 <2014-01-08>` 176 +* :bug:`225` Note ecdsa requirement in README. Thanks to Amaury Rodriguez for + the catch. +* :bug:`176` Fix AttributeError bugs in known_hosts file (re)loading. Thanks + to Nathan Scowcroft for the patch & Martin Blumenstingl for the initial test + case. +* :release:`1.12.0 <2013-09-27>` +* :release:`1.11.2 <2013-09-27>` +* :release:`1.10.4 <2013-09-27>` 199, 200, 179 +* :feature:`152` Add tentative support for ECDSA keys. *This adds the ecdsa + module as a new dependency of Paramiko.* The module is available at + [warner/python-ecdsa on Github](https://github.com/warner/python-ecdsa) and + [ecdsa on PyPI](https://pypi.python.org/pypi/ecdsa). + + * Note that you might still run into problems with key negotiation -- + Paramiko picks the first key that the server offers, which might not be + what you have in your known_hosts file. + * Mega thanks to Ethan Glasser-Camp for the patch. + +* :feature:`136` Add server-side support for the SSH protocol's 'env' command. + Thanks to Benjamin Pollack for the patch. +* :bug:`156` Fix potential deadlock condition when using Channel objects as + sockets (e.g. when using SSH gatewaying). Thanks to Steven Noonan and Frank + Arnold for catch & patch. +* :bug:`179` Fix a missing variable causing errors when an ssh_config file has + a non-default AddressFamily set. Thanks to Ed Marshall & Tomaz Muraus for + catch & patch. +* :bug:`200` Fix an exception-causing typo in ``demo_simple.py``. Thanks to Alex + Buchanan for catch & Dave Foster for patch. +* :bug:`199` Typo fix in the license header cross-project. Thanks to Armin + Ronacher for catch & patch. +* :release:`1.11.1 <2013-09-20>` +* :release:`1.10.3 <2013-09-20>` +* :bug:`162` Clean up HMAC module import to avoid deadlocks in certain uses of + SSHClient. Thanks to Gernot Hillier for the catch & suggested fix. +* :bug:`36` Fix the port-forwarding demo to avoid file descriptor errors. + Thanks to Jonathan Halcrow for catch & patch. +* :bug:`168` Update config handling to properly handle multiple 'localforward' + and 'remoteforward' keys. Thanks to Emre Yılmaz for the patch. +* :release:`1.11.0 <2013-07-26>` +* :release:`1.10.2 <2013-07-26>` +* :bug:`98 major` On Windows, when interacting with the PuTTY PAgeant, Paramiko + now creates the shared memory map with explicit Security Attributes of the + user, which is the same technique employed by the canonical PuTTY library to + avoid permissions issues when Paramiko is running under a different UAC + context than the PuTTY Ageant process. Thanks to Jason R. Coombs for the + patch. +* :support:`100` Remove use of PyWin32 in ``win_pageant`` module. Module was + already dependent on ctypes for constructing appropriate structures and had + ctypes implementations of all functionality. Thanks to Jason R. Coombs for + the patch. +* :bug:`87 major` Ensure updates to ``known_hosts`` files account for any + updates to said files after Paramiko initially read them. (Includes related + fix to guard against duplicate entries during subsequent ``known_hosts`` + loads.) Thanks to ``@sunweaver`` for the contribution. +* :bug:`153` (also :issue:`67`) Warn on parse failure when reading known_hosts + file. Thanks to ``@glasserc`` for patch. +* :bug:`146` Indentation fixes for readability. Thanks to Abhinav Upadhyay for + catch & patch. +* :release:`1.10.1 <2013-04-05>` +* :bug:`142` (`Fabric #811 `_) + SFTP put of empty file will still return the attributes of the put file. + Thanks to Jason R. Coombs for the patch. +* :bug:`154` (`Fabric #876 `_) + Forwarded SSH agent connections left stale local pipes lying around, which + could cause local (and sometimes remote or network) resource starvation when + running many agent-using remote commands. Thanks to Kevin Tegtmeier for catch + & patch. +* :release:`1.10.0 <2013-03-01>` +* :feature:`66` Batch SFTP writes to help speed up file transfers. Thanks to + Olle Lundberg for the patch. +* :bug:`133 major` Fix handling of window-change events to be on-spec and not + attempt to wait for a response from the remote sshd; this fixes problems with + less common targets such as some Cisco devices. Thanks to Phillip Heller for + catch & patch. +* :feature:`93` Overhaul SSH config parsing to be in line with ``man + ssh_config`` (& the behavior of ``ssh`` itself), including addition of parameter + expansion within config values. Thanks to Olle Lundberg for the patch. +* :feature:`110` Honor SSH config ``AddressFamily`` setting when looking up + local host's FQDN. Thanks to John Hensley for the patch. +* :feature:`128` Defer FQDN resolution until needed, when parsing SSH config + files. Thanks to Parantapa Bhattacharya for catch & patch. +* :bug:`102 major` Forego random padding for packets when running under + ``*-ctr`` ciphers. This corrects some slowdowns on platforms where random + byte generation is inefficient (e.g. Windows). Thanks to ``@warthog618`` for + catch & patch, and Michael van der Kolff for code/technique review. +* :feature:`127` Turn ``SFTPFile`` into a context manager. Thanks to Michael + Williamson for the patch. +* :feature:`116` Limit ``Message.get_bytes`` to an upper bound of 1MB to protect + against potential DoS vectors. Thanks to ``@mvschaik`` for catch & patch. +* :feature:`115` Add convenience ``get_pty`` kwarg to ``Client.exec_command`` so + users not manually controlling a channel object can still toggle PTY + creation. Thanks to Michael van der Kolff for the patch. +* :feature:`71` Add ``SFTPClient.putfo`` and ``.getfo`` methods to allow direct + uploading/downloading of file-like objects. Thanks to Eric Buehl for the + patch. +* :feature:`113` Add ``timeout`` parameter to ``SSHClient.exec_command`` for + easier setting of the command's internal channel object's timeout. Thanks to + Cernov Vladimir for the patch. +* :support:`94` Remove duplication of SSH port constant. Thanks to Olle + Lundberg for the catch. +* :feature:`80` Expose the internal "is closed" property of the file transfer + class ``BufferedFile`` as ``.closed``, better conforming to Python's file + interface. Thanks to ``@smunaut`` and James Hiscock for catch & patch. diff --git a/sites/www/conf.py b/sites/www/conf.py new file mode 100644 index 0000000..1f11c1e --- /dev/null +++ b/sites/www/conf.py @@ -0,0 +1,35 @@ +# Obtain shared config values +import sys +import os +from os.path import abspath, join, dirname + +sys.path.append(abspath(join(dirname(__file__), '..'))) +from shared_conf import * + +# Local blog extension +sys.path.append(abspath('.')) +extensions = ['blog'] +rss_link = 'http://paramiko.org' +rss_description = 'Paramiko project news' + +# Releases changelog extension +extensions.append('releases') +releases_release_uri = "https://github.com/paramiko/paramiko/tree/%s" +releases_issue_uri = "https://github.com/paramiko/paramiko/issues/%s" + +# Intersphinx for referencing API/usage docs +extensions.append('sphinx.ext.intersphinx') +# Default is 'local' building, but reference the public docs site when building +# under RTD. +target = join(dirname(__file__), '..', 'docs', '_build') +if os.environ.get('READTHEDOCS') == 'True': + # TODO: switch to docs.paramiko.org post go-live of sphinx API docs + target = 'http://paramiko-docs.readthedocs.org/en/latest/' +#intersphinx_mapping = { +# 'docs': (target, None), +#} + +# Sister-site links to API docs +html_theme_options['extra_nav_links'] = { + "API Docs": 'http://docs.paramiko.org', +} diff --git a/sites/www/contact.rst b/sites/www/contact.rst new file mode 100644 index 0000000..b479f17 --- /dev/null +++ b/sites/www/contact.rst @@ -0,0 +1,11 @@ +======= +Contact +======= + +You can get in touch with the developer & user community in any of the +following ways: + +* IRC: ``#paramiko`` on Freenode +* Mailing list: ``paramiko@librelist.com`` (see `the LibreList homepage + `_ for usage details). +* This website's :doc:`blog `. diff --git a/sites/www/contributing.rst b/sites/www/contributing.rst new file mode 100644 index 0000000..b121e64 --- /dev/null +++ b/sites/www/contributing.rst @@ -0,0 +1,19 @@ +============ +Contributing +============ + +How to get the code +=================== + +Our primary Git repository is on Github at `paramiko/paramiko +`; please follow their instruction for +cloning to your local system. (If you intend to submit patches/pull requests, +we recommend forking first, then cloning your fork. Github has excellent +documentation for all this.) + + +How to submit bug reports or new code +===================================== + +Please see `this project-agnostic contribution guide +`_ - we follow it explicitly. diff --git a/sites/www/index.rst b/sites/www/index.rst new file mode 100644 index 0000000..7fefedd --- /dev/null +++ b/sites/www/index.rst @@ -0,0 +1,38 @@ +Welcome to Paramiko! +==================== + +Paramiko is a Python (2.5+) implementation of the SSHv2 protocol [#]_, +providing both client and server functionality. While it leverages a Python C +extension for low level cryptography (`PyCrypto `_), +Paramiko itself is a pure Python interface around SSH networking concepts. + +This website covers project information for Paramiko such as the changelog, +contribution guidelines, development roadmap, news/blog, and so forth. Detailed +usage and API documentation can be found at our code documentation site, +`docs.paramiko.org `_. + +.. toctree:: + changelog + installing + contributing + contact + +.. Hide blog in hidden toctree for now (to avoid warnings.) + +.. toctree:: + :hidden: + + blog + + +.. rubric:: Footnotes + +.. [#] + SSH is defined in RFCs + `4251 `_, + `4252 `_, + `4253 `_, and + `4254 `_; + the primary working implementation of the protocol is the `OpenSSH project + `_. Paramiko implements a large portion of the SSH + feature set, but there are occasional gaps. diff --git a/sites/www/installing.rst b/sites/www/installing.rst new file mode 100644 index 0000000..0d4dc1a --- /dev/null +++ b/sites/www/installing.rst @@ -0,0 +1,105 @@ +========== +Installing +========== + +Paramiko itself +=============== + +The recommended way to get Invoke is to **install the latest stable release** +via `pip `_:: + + $ pip install paramiko + +.. note:: + Users who want the bleeding edge can install the development version via + ``pip install paramiko==dev``. + +We currently support **Python 2.5/2.6/2.7**, with support for Python 3 coming +soon. Users on Python 2.4 or older are urged to upgrade. Paramiko *may* work on +Python 2.4 still, but there is no longer any support guarantee. + +Paramiko has two dependencies: the pure-Python ECDSA module `ecdsa`, and the +PyCrypto C extension. `ecdsa` is easily installable from wherever you +obtained Paramiko's package; PyCrypto may require more work. Read on for +details. + +PyCrypto +======== + +`PyCrypto `_ provides the low-level +(C-based) encryption algorithms we need to implement the SSH protocol. There +are a couple gotchas associated with installing PyCrypto: its compatibility +with Python's package tools, and the fact that it is a C-based extension. + +.. _pycrypto-and-pip: + +Possible gotcha on older Python and/or pip versions +--------------------------------------------------- + +We strongly recommend using ``pip`` to as it is newer and generally better than +``easy_install``. However, a combination of bugs in specific (now rather old) +versions of Python, ``pip`` and PyCrypto can prevent installation of PyCrypto. +Specifically: + +* Python = 2.5.x +* PyCrypto >= 2.1 (required for most modern versions of Paramiko) +* ``pip`` < 0.8.1 + +When all three criteria are met, you may encounter ``No such file or +directory`` IOErrors when trying to ``pip install paramiko`` or ``pip install +PyCrypto``. + +The fix is to make sure at least one of the above criteria is not met, by doing +the following (in order of preference): + +* Upgrade to ``pip`` 0.8.1 or above, e.g. by running ``pip install -U pip``. +* Upgrade to Python 2.6 or above. +* Downgrade to Paramiko 1.7.6 or 1.7.7, which do not require PyCrypto >= 2.1, + and install PyCrypto 2.0.1 (the oldest version on PyPI which works with + Paramiko 1.7.6/1.7.7) + + +C extension +----------- + +Unless you are installing from a precompiled source such as a Debian apt +repository or RedHat RPM, or using :ref:`pypm `, you will also need the +ability to build Python C-based modules from source in order to install +PyCrypto. Users on **Unix-based platforms** such as Ubuntu or Mac OS X will +need the traditional C build toolchain installed (e.g. Developer Tools / XCode +Tools on the Mac, or the ``build-essential`` package on Ubuntu or Debian Linux +-- basically, anything with ``gcc``, ``make`` and so forth) as well as the +Python development libraries, often named ``python-dev`` or similar. + +For **Windows** users we recommend using :ref:`pypm`, installing a C +development environment such as `Cygwin `_ or obtaining a +precompiled Win32 PyCrypto package from `voidspace's Python modules page +`_. + +.. note:: + Some Windows users whose Python is 64-bit have found that the PyCrypto + dependency ``winrandom`` may not install properly, leading to ImportErrors. + In this scenario, you'll probably need to compile ``winrandom`` yourself + via e.g. MS Visual Studio. See `Fabric #194 + `_ for info. + + +.. _pypm: + +ActivePython and PyPM +===================== + +Windows users who already have ActiveState's `ActivePython +`_ distribution installed +may find Paramiko is best installed with `its package manager, PyPM +`_. Below is example output from an +installation of Paramiko via ``pypm``:: + + C:\> pypm install paramiko + The following packages will be installed into "%APPDATA%\Python" (2.7): + paramiko-1.7.8 pycrypto-2.4 + Get: [pypm-free.activestate.com] paramiko 1.7.8 + Get: [pypm-free.activestate.com] pycrypto 2.4 + Installing paramiko-1.7.8 + Installing pycrypto-2.4 + C:\> diff --git a/tasks.py b/tasks.py new file mode 100644 index 0000000..c716415 --- /dev/null +++ b/tasks.py @@ -0,0 +1,23 @@ +from os.path import join + +from invoke import Collection +from invocations import docs as _docs, testing + + +d = 'sites' + +# Usage doc/API site (published as docs.paramiko.org) +path = join(d, 'docs') +docs = Collection.from_module(_docs, name='docs', config={ + 'sphinx.source': path, + 'sphinx.target': join(path, '_build'), +}) + +# Main/about/changelog site ((www.)?paramiko.org) +path = join(d, 'www') +www = Collection.from_module(_docs, name='www', config={ + 'sphinx.source': path, + 'sphinx.target': join(path, '_build'), +}) + +ns = Collection(testing.test, docs=docs, www=www) diff --git a/tox-requirements.txt b/tox-requirements.txt new file mode 100644 index 0000000..26224ce --- /dev/null +++ b/tox-requirements.txt @@ -0,0 +1,2 @@ +# Not sure why tox can't just read setup.py? +pycrypto diff --git a/tox.ini b/tox.ini index e2a8dcf..af4fbf2 100644 --- a/tox.ini +++ b/tox.ini @@ -2,5 +2,5 @@ envlist = py25,py26,py27 [testenv] -commands = pip install --use-mirrors -q -r dev-requirements.txt +commands = pip install --use-mirrors -q -r tox-requirements.txt python test.py