diff --git a/.gitignore b/.gitignore index d322edd..a8bfc2b 100644 --- a/.gitignore +++ b/.gitignore @@ -83,3 +83,6 @@ gpg # setuptools/distribute files: PKG-INFO MANIFEST + +# sphinx default build +docs/_build diff --git a/docs/Makefile b/docs/Makefile index 7159d3e..2d43a93 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -2,7 +2,7 @@ # # You can set these variables from the command line. -SPHINXOPTS = +SPHINXOPTS = -E -n SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build diff --git a/docs/DETAILS.html b/docs/_static/DETAILS.html similarity index 100% rename from docs/DETAILS.html rename to docs/_static/DETAILS.html diff --git a/docs/_static/agogo.css b/docs/_static/agogo.css index 9d6a4af..2bdc26f 100644 --- a/docs/_static/agogo.css +++ b/docs/_static/agogo.css @@ -25,6 +25,16 @@ div.header-wrapper { border-bottom: 3px solid #2e3436; } +div.headertitle a { + font-family: "Georgia", "Times New Roman", serif; + font-size: 2em; + color: rgb(252, 175, 62); + font-weight: normal; +} + +h1 { + color: #204a87; +} /* Default body styles */ a { diff --git a/docs/_static/doctools.js b/docs/_static/doctools.js deleted file mode 100644 index 9447678..0000000 --- a/docs/_static/doctools.js +++ /dev/null @@ -1,232 +0,0 @@ -/// XXX: make it cross browser - -/** - * make the code below compatible with browsers without - * an installed firebug like debugger - */ -if (!window.console || !console.firebug) { - var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml", - "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"]; - window.console = {}; - for (var i = 0; i < names.length; ++i) - window.console[names[i]] = function() {} -} - -/** - * small helper function to urldecode strings - */ -jQuery.urldecode = function(x) { - return decodeURIComponent(x).replace(/\+/g, ' '); -} - -/** - * small helper function to urlencode strings - */ -jQuery.urlencode = encodeURIComponent; - -/** - * This function returns the parsed url parameters of the - * current request. Multiple values per key are supported, - * it will always return arrays of strings for the value parts. - */ -jQuery.getQueryParameters = function(s) { - if (typeof s == 'undefined') - s = document.location.search; - var parts = s.substr(s.indexOf('?') + 1).split('&'); - var result = {}; - for (var i = 0; i < parts.length; i++) { - var tmp = parts[i].split('=', 2); - var key = jQuery.urldecode(tmp[0]); - var value = jQuery.urldecode(tmp[1]); - if (key in result) - result[key].push(value); - else - result[key] = [value]; - } - return result; -} - -/** - * small function to check if an array contains - * a given item. - */ -jQuery.contains = function(arr, item) { - for (var i = 0; i < arr.length; i++) { - if (arr[i] == item) - return true; - } - return false; -} - -/** - * highlight a given string on a jquery object by wrapping it in - * span elements with the given class name. - */ -jQuery.fn.highlightText = function(text, className) { - function highlight(node) { - if (node.nodeType == 3) { - var val = node.nodeValue; - var pos = val.toLowerCase().indexOf(text); - if (pos >= 0 && !jQuery.className.has(node.parentNode, className)) { - var span = document.createElement("span"); - span.className = className; - span.appendChild(document.createTextNode(val.substr(pos, text.length))); - node.parentNode.insertBefore(span, node.parentNode.insertBefore( - document.createTextNode(val.substr(pos + text.length)), - node.nextSibling)); - node.nodeValue = val.substr(0, pos); - } - } - else if (!jQuery(node).is("button, select, textarea")) { - jQuery.each(node.childNodes, function() { - highlight(this) - }); - } - } - return this.each(function() { - highlight(this); - }); -} - -/** - * Small JavaScript module for the documentation. - */ -var Documentation = { - - init : function() { - this.fixFirefoxAnchorBug(); - this.highlightSearchWords(); - this.initModIndex(); - }, - - /** - * i18n support - */ - TRANSLATIONS : {}, - PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, - LOCALE : 'unknown', - - // gettext and ngettext don't access this so that the functions - // can savely bound to a different name (_ = Documentation.gettext) - gettext : function(string) { - var translated = Documentation.TRANSLATIONS[string]; - if (typeof translated == 'undefined') - return string; - return (typeof translated == 'string') ? translated : translated[0]; - }, - - ngettext : function(singular, plural, n) { - var translated = Documentation.TRANSLATIONS[singular]; - if (typeof translated == 'undefined') - return (n == 1) ? singular : plural; - return translated[Documentation.PLURALEXPR(n)]; - }, - - addTranslations : function(catalog) { - for (var key in catalog.messages) - this.TRANSLATIONS[key] = catalog.messages[key]; - this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); - this.LOCALE = catalog.locale; - }, - - /** - * add context elements like header anchor links - */ - addContextElements : function() { - $('div[id] > :header:first').each(function() { - $('\u00B6'). - attr('href', '#' + this.id). - attr('title', _('Permalink to this headline')). - appendTo(this); - }); - $('dt[id]').each(function() { - $('\u00B6'). - attr('href', '#' + this.id). - attr('title', _('Permalink to this definition')). - appendTo(this); - }); - }, - - /** - * workaround a firefox stupidity - */ - fixFirefoxAnchorBug : function() { - if (document.location.hash && $.browser.mozilla) - window.setTimeout(function() { - document.location.href += ''; - }, 10); - }, - - /** - * highlight the search words provided in the url in the text - */ - highlightSearchWords : function() { - var params = $.getQueryParameters(); - var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; - if (terms.length) { - var body = $('div.body'); - window.setTimeout(function() { - $.each(terms, function() { - body.highlightText(this.toLowerCase(), 'highlight'); - }); - }, 10); - $('') - .appendTo($('.sidebar .this-page-menu')); - } - }, - - /** - * init the modindex toggle buttons - */ - initModIndex : function() { - var togglers = $('img.toggler').click(function() { - var src = $(this).attr('src'); - var idnum = $(this).attr('id').substr(7); - console.log($('tr.cg-' + idnum).toggle()); - if (src.substr(-9) == 'minus.png') - $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); - else - $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); - }).css('display', ''); - if (DOCUMENTATION_OPTIONS.COLLAPSE_MODINDEX) { - togglers.click(); - } - }, - - /** - * helper function to hide the search marks again - */ - hideSearchWords : function() { - $('.sidebar .this-page-menu li.highlight-link').fadeOut(300); - $('span.highlight').removeClass('highlight'); - }, - - /** - * make the url absolute - */ - makeURL : function(relativeURL) { - return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; - }, - - /** - * get the current relative url - */ - getCurrentURL : function() { - var path = document.location.pathname; - var parts = path.split(/\//); - $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { - if (this == '..') - parts.pop(); - }); - var url = parts.join('/'); - return path.substring(url.lastIndexOf('/') + 1, path.length - 1); - } -}; - -// quick alias for translations -_ = Documentation.gettext; - -$(document).ready(function() { - Documentation.init(); -}); diff --git a/docs/_static/haiku.css b/docs/_static/haiku.css deleted file mode 100644 index 8e28149..0000000 --- a/docs/_static/haiku.css +++ /dev/null @@ -1,386 +0,0 @@ -/* custom stuff I put in FIXME where is it "supposed" to go? */ - -div.admonition-todo -{ - border: 1px solid red; - background-color: #Fdd; -} - -div.admonition-todo p.admonition-title -{ - margin: 0; - color: red; - text-transform: lowercase; -} - -p.admonition-title -{ - font-size: 120%; - font-weight: bold; -} - -dl.class>dt, dl.interface>dt, dl.function>dt, dl.staticmethod>dt -{ - font-size: 150%; - background-color:#ddd; -} - -dl.method>dt -{ - background-color: #eee; - border-bottom: 2px solid #ddd; -} - -dl.method:hover -{ - background-color:#ffd; -} - -/** end custom */ - -html { - margin: 0px; - padding: 0px; - background: #FFF url(bg-page.png) top left repeat-x; -} - -body { - line-height: 1.5; - margin: auto; - padding: 0px; - font-family: "DejaVu Sans", Arial, Helvetica, sans-serif; - min-width: 59em; - max-width: 70em; - color: #333333; -} - -div.footer { - padding: 8px; - font-size: 11px; - text-align: center; - letter-spacing: 0.5px; -} - -/* link colors and text decoration */ - -a:link { - font-weight: bold; - text-decoration: none; - color: #dc3c01; -} - -a:visited { - font-weight: bold; - text-decoration: none; - color: #892601; -} - -a:hover, a:active { - text-decoration: underline; - color: #ff4500; -} - -/* Some headers act as anchors, don't give them a hover effect */ - -h1 a:hover, a:active { - text-decoration: none; - color: #0c3762; -} - -h2 a:hover, a:active { - text-decoration: none; - color: #0c3762; -} - -h3 a:hover, a:active { - text-decoration: none; - color: #0c3762; -} - -h4 a:hover, a:active { - text-decoration: none; - color: #0c3762; -} - -a.headerlink { - color: #a7ce38; - padding-left: 5px; -} - -a.headerlink:hover { - color: #a7ce38; -} - -/* basic text elements */ - -div.content { - margin-top: 20px; - margin-left: 40px; - margin-right: 40px; - margin-bottom: 50px; - font-size: 0.9em; -} - -/* heading and navigation */ - -div.header { - position: relative; - left: 0px; - top: 0px; - height: 85px; - /* background: #eeeeee; */ - padding: 0 40px; -} -div.header h1 { - font-size: 1.6em; - font-weight: normal; - letter-spacing: 1px; - color: #0c3762; - border: 0; - margin: 0; - padding-top: 15px; -} -div.header h1 a { - font-weight: normal; - color: #0c3762; -} -div.header h2 { - font-size: 1.3em; - font-weight: normal; - letter-spacing: 1px; - text-transform: uppercase; - color: #aaa; - border: 0; - margin-top: -3px; - padding: 0; -} - -div.header img.rightlogo { - float: right; -} - - -div.title { - font-size: 1.3em; - font-weight: bold; - color: #0c3762; - border-bottom: dotted thin #e0e0e0; - margin-bottom: 25px; -} -div.topnav { - /* background: #e0e0e0; */ -} -div.topnav p { - margin-top: 0; - margin-left: 40px; - margin-right: 40px; - margin-bottom: 0px; - text-align: right; - font-size: 0.8em; -} -div.bottomnav { - background: #eeeeee; -} -div.bottomnav p { - margin-right: 40px; - text-align: right; - font-size: 0.8em; -} - -a.uplink { - font-weight: normal; -} - - -/* contents box */ - -table.index { - margin: 0px 0px 30px 30px; - padding: 1px; - border-width: 1px; - border-style: dotted; - border-color: #e0e0e0; -} -table.index tr.heading { - background-color: #e0e0e0; - text-align: center; - font-weight: bold; - font-size: 1.1em; -} -table.index tr.index { - background-color: #eeeeee; -} -table.index td { - padding: 5px 20px; -} - -table.index a:link, table.index a:visited { - font-weight: normal; - text-decoration: none; - color: #dc3c01; -} -table.index a:hover, table.index a:active { - text-decoration: underline; - color: #ff4500; -} - - -/* Haiku User Guide styles and layout */ - -/* Rounded corner boxes */ -/* Common declarations */ -div.admonition { - -webkit-border-radius: 10px; - -khtml-border-radius: 10px; - -moz-border-radius: 10px; - border-radius: 10px; - border-style: dotted; - border-width: thin; - border-color: #dcdcdc; - padding: 10px 15px 10px 15px; - margin-bottom: 15px; - margin-top: 15px; -} -div.note { - padding: 10px 15px 10px 80px; - background: #e4ffde url(alert_info_32.png) 15px 15px no-repeat; - min-height: 42px; -} -div.warning { - padding: 10px 15px 10px 80px; - background: #fffbc6 url(alert_warning_32.png) 15px 15px no-repeat; - min-height: 42px; -} -div.seealso { - background: #e4ffde; -} - -/* More layout and styles */ -h1 { - font-size: 1.3em; - font-weight: bold; - color: #0c3762; - border-bottom: dotted thin #e0e0e0; - margin-top: 30px; -} - -h2 { - font-size: 1.2em; - font-weight: normal; - color: #0c3762; - border-bottom: dotted thin #e0e0e0; - margin-top: 30px; -} - -h3 { - font-size: 1.1em; - font-weight: normal; - color: #0c3762; - margin-top: 30px; -} - -h4 { - font-size: 1.0em; - font-weight: normal; - color: #0c3762; - margin-top: 30px; -} - -p { - text-align: justify; -} - -p.last { - margin-bottom: 0; -} - -ol { - padding-left: 20px; -} - -ul { - padding-left: 5px; - margin-top: 3px; -} - -li { - line-height: 1.3; -} - -div.content ul > li { - -moz-background-clip:border; - -moz-background-inline-policy:continuous; - -moz-background-origin:padding; - background: transparent url(bullet_orange.png) no-repeat scroll left 0.45em; - list-style-image: none; - list-style-type: none; - padding: 0 0 0 1.666em; - margin-bottom: 3px; -} - -td { - vertical-align: top; -} - -tt { - background-color: #e2e2e2; - font-size: 1.0em; - font-family: monospace; -} - -pre { - border-color: #0c3762; - border-style: dotted; - border-width: thin; - margin: 0 0 12px 0; - padding: 0.8em; - background-color: #f0f0f0; -} - -hr { - border-top: 1px solid #ccc; - border-bottom: 0; - border-right: 0; - border-left: 0; - margin-bottom: 10px; - margin-top: 20px; -} - -/* printer only pretty stuff */ -@media print { - .noprint { - display: none; - } - /* for acronyms we want their definitions inlined at print time */ - acronym[title]:after { - font-size: small; - content: " (" attr(title) ")"; - font-style: italic; - } - /* and not have mozilla dotted underline */ - acronym { - border: none; - } - div.topnav, div.bottomnav, div.header, table.index { - display: none; - } - div.content { - margin: 0px; - padding: 0px; - } - html { - background: #FFF; - } -} - -.viewcode-back { - font-family: "DejaVu Sans", Arial, Helvetica, sans-serif; -} - -div.viewcode-block:target { - background-color: #f4debf; - border-top: 1px solid #ac9; - border-bottom: 1px solid #ac9; - margin: -1px -12px; - padding: 0 12px; -} diff --git a/docs/_static/jquery.js b/docs/_static/jquery.js deleted file mode 100644 index 1acc8fd..0000000 --- a/docs/_static/jquery.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * jQuery 1.2.6 - New Wave Javascript - * - * Copyright (c) 2008 John Resig (jquery.com) - * Dual licensed under the MIT (MIT-LICENSE.txt) - * and GPL (GPL-LICENSE.txt) licenses. - * - * $Date: 2008-05-24 14:22:17 -0400 (Sat, 24 May 2008) $ - * $Rev: 5685 $ - */ -(function(){var _jQuery=window.jQuery,_$=window.$;var jQuery=window.jQuery=window.$=function(selector,context){return new jQuery.fn.init(selector,context);};var quickExpr=/^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/,isSimple=/^.[^:#\[\.]*$/,undefined;jQuery.fn=jQuery.prototype={init:function(selector,context){selector=selector||document;if(selector.nodeType){this[0]=selector;this.length=1;return this;}if(typeof selector=="string"){var match=quickExpr.exec(selector);if(match&&(match[1]||!context)){if(match[1])selector=jQuery.clean([match[1]],context);else{var elem=document.getElementById(match[3]);if(elem){if(elem.id!=match[3])return jQuery().find(selector);return jQuery(elem);}selector=[];}}else -return jQuery(context).find(selector);}else if(jQuery.isFunction(selector))return jQuery(document)[jQuery.fn.ready?"ready":"load"](selector);return this.setArray(jQuery.makeArray(selector));},jquery:"1.2.6",size:function(){return this.length;},length:0,get:function(num){return num==undefined?jQuery.makeArray(this):this[num];},pushStack:function(elems){var ret=jQuery(elems);ret.prevObject=this;return ret;},setArray:function(elems){this.length=0;Array.prototype.push.apply(this,elems);return this;},each:function(callback,args){return jQuery.each(this,callback,args);},index:function(elem){var ret=-1;return jQuery.inArray(elem&&elem.jquery?elem[0]:elem,this);},attr:function(name,value,type){var options=name;if(name.constructor==String)if(value===undefined)return this[0]&&jQuery[type||"attr"](this[0],name);else{options={};options[name]=value;}return this.each(function(i){for(name in options)jQuery.attr(type?this.style:this,name,jQuery.prop(this,options[name],type,i,name));});},css:function(key,value){if((key=='width'||key=='height')&&parseFloat(value)<0)value=undefined;return this.attr(key,value,"curCSS");},text:function(text){if(typeof text!="object"&&text!=null)return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(text));var ret="";jQuery.each(text||this,function(){jQuery.each(this.childNodes,function(){if(this.nodeType!=8)ret+=this.nodeType!=1?this.nodeValue:jQuery.fn.text([this]);});});return ret;},wrapAll:function(html){if(this[0])jQuery(html,this[0].ownerDocument).clone().insertBefore(this[0]).map(function(){var elem=this;while(elem.firstChild)elem=elem.firstChild;return elem;}).append(this);return this;},wrapInner:function(html){return this.each(function(){jQuery(this).contents().wrapAll(html);});},wrap:function(html){return this.each(function(){jQuery(this).wrapAll(html);});},append:function(){return this.domManip(arguments,true,false,function(elem){if(this.nodeType==1)this.appendChild(elem);});},prepend:function(){return this.domManip(arguments,true,true,function(elem){if(this.nodeType==1)this.insertBefore(elem,this.firstChild);});},before:function(){return this.domManip(arguments,false,false,function(elem){this.parentNode.insertBefore(elem,this);});},after:function(){return this.domManip(arguments,false,true,function(elem){this.parentNode.insertBefore(elem,this.nextSibling);});},end:function(){return this.prevObject||jQuery([]);},find:function(selector){var elems=jQuery.map(this,function(elem){return jQuery.find(selector,elem);});return this.pushStack(/[^+>] [^+>]/.test(selector)||selector.indexOf("..")>-1?jQuery.unique(elems):elems);},clone:function(events){var ret=this.map(function(){if(jQuery.browser.msie&&!jQuery.isXMLDoc(this)){var clone=this.cloneNode(true),container=document.createElement("div");container.appendChild(clone);return jQuery.clean([container.innerHTML])[0];}else -return this.cloneNode(true);});var clone=ret.find("*").andSelf().each(function(){if(this[expando]!=undefined)this[expando]=null;});if(events===true)this.find("*").andSelf().each(function(i){if(this.nodeType==3)return;var events=jQuery.data(this,"events");for(var type in events)for(var handler in events[type])jQuery.event.add(clone[i],type,events[type][handler],events[type][handler].data);});return ret;},filter:function(selector){return this.pushStack(jQuery.isFunction(selector)&&jQuery.grep(this,function(elem,i){return selector.call(elem,i);})||jQuery.multiFilter(selector,this));},not:function(selector){if(selector.constructor==String)if(isSimple.test(selector))return this.pushStack(jQuery.multiFilter(selector,this,true));else -selector=jQuery.multiFilter(selector,this);var isArrayLike=selector.length&&selector[selector.length-1]!==undefined&&!selector.nodeType;return this.filter(function(){return isArrayLike?jQuery.inArray(this,selector)<0:this!=selector;});},add:function(selector){return this.pushStack(jQuery.unique(jQuery.merge(this.get(),typeof selector=='string'?jQuery(selector):jQuery.makeArray(selector))));},is:function(selector){return!!selector&&jQuery.multiFilter(selector,this).length>0;},hasClass:function(selector){return this.is("."+selector);},val:function(value){if(value==undefined){if(this.length){var elem=this[0];if(jQuery.nodeName(elem,"select")){var index=elem.selectedIndex,values=[],options=elem.options,one=elem.type=="select-one";if(index<0)return null;for(var i=one?index:0,max=one?index+1:options.length;i=0||jQuery.inArray(this.name,value)>=0);else if(jQuery.nodeName(this,"select")){var values=jQuery.makeArray(value);jQuery("option",this).each(function(){this.selected=(jQuery.inArray(this.value,values)>=0||jQuery.inArray(this.text,values)>=0);});if(!values.length)this.selectedIndex=-1;}else -this.value=value;});},html:function(value){return value==undefined?(this[0]?this[0].innerHTML:null):this.empty().append(value);},replaceWith:function(value){return this.after(value).remove();},eq:function(i){return this.slice(i,i+1);},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments));},map:function(callback){return this.pushStack(jQuery.map(this,function(elem,i){return callback.call(elem,i,elem);}));},andSelf:function(){return this.add(this.prevObject);},data:function(key,value){var parts=key.split(".");parts[1]=parts[1]?"."+parts[1]:"";if(value===undefined){var data=this.triggerHandler("getData"+parts[1]+"!",[parts[0]]);if(data===undefined&&this.length)data=jQuery.data(this[0],key);return data===undefined&&parts[1]?this.data(parts[0]):data;}else -return this.trigger("setData"+parts[1]+"!",[parts[0],value]).each(function(){jQuery.data(this,key,value);});},removeData:function(key){return this.each(function(){jQuery.removeData(this,key);});},domManip:function(args,table,reverse,callback){var clone=this.length>1,elems;return this.each(function(){if(!elems){elems=jQuery.clean(args,this.ownerDocument);if(reverse)elems.reverse();}var obj=this;if(table&&jQuery.nodeName(this,"table")&&jQuery.nodeName(elems[0],"tr"))obj=this.getElementsByTagName("tbody")[0]||this.appendChild(this.ownerDocument.createElement("tbody"));var scripts=jQuery([]);jQuery.each(elems,function(){var elem=clone?jQuery(this).clone(true)[0]:this;if(jQuery.nodeName(elem,"script"))scripts=scripts.add(elem);else{if(elem.nodeType==1)scripts=scripts.add(jQuery("script",elem).remove());callback.call(obj,elem);}});scripts.each(evalScript);});}};jQuery.fn.init.prototype=jQuery.fn;function evalScript(i,elem){if(elem.src)jQuery.ajax({url:elem.src,async:false,dataType:"script"});else -jQuery.globalEval(elem.text||elem.textContent||elem.innerHTML||"");if(elem.parentNode)elem.parentNode.removeChild(elem);}function now(){return+new Date;}jQuery.extend=jQuery.fn.extend=function(){var target=arguments[0]||{},i=1,length=arguments.length,deep=false,options;if(target.constructor==Boolean){deep=target;target=arguments[1]||{};i=2;}if(typeof target!="object"&&typeof target!="function")target={};if(length==i){target=this;--i;}for(;i-1;}},swap:function(elem,options,callback){var old={};for(var name in options){old[name]=elem.style[name];elem.style[name]=options[name];}callback.call(elem);for(var name in options)elem.style[name]=old[name];},css:function(elem,name,force){if(name=="width"||name=="height"){var val,props={position:"absolute",visibility:"hidden",display:"block"},which=name=="width"?["Left","Right"]:["Top","Bottom"];function getWH(){val=name=="width"?elem.offsetWidth:elem.offsetHeight;var padding=0,border=0;jQuery.each(which,function(){padding+=parseFloat(jQuery.curCSS(elem,"padding"+this,true))||0;border+=parseFloat(jQuery.curCSS(elem,"border"+this+"Width",true))||0;});val-=Math.round(padding+border);}if(jQuery(elem).is(":visible"))getWH();else -jQuery.swap(elem,props,getWH);return Math.max(0,val);}return jQuery.curCSS(elem,name,force);},curCSS:function(elem,name,force){var ret,style=elem.style;function color(elem){if(!jQuery.browser.safari)return false;var ret=defaultView.getComputedStyle(elem,null);return!ret||ret.getPropertyValue("color")=="";}if(name=="opacity"&&jQuery.browser.msie){ret=jQuery.attr(style,"opacity");return ret==""?"1":ret;}if(jQuery.browser.opera&&name=="display"){var save=style.outline;style.outline="0 solid black";style.outline=save;}if(name.match(/float/i))name=styleFloat;if(!force&&style&&style[name])ret=style[name];else if(defaultView.getComputedStyle){if(name.match(/float/i))name="float";name=name.replace(/([A-Z])/g,"-$1").toLowerCase();var computedStyle=defaultView.getComputedStyle(elem,null);if(computedStyle&&!color(elem))ret=computedStyle.getPropertyValue(name);else{var swap=[],stack=[],a=elem,i=0;for(;a&&color(a);a=a.parentNode)stack.unshift(a);for(;i]*?)\/>/g,function(all,front,tag){return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?all:front+">";});var tags=jQuery.trim(elem).toLowerCase(),div=context.createElement("div");var wrap=!tags.indexOf("",""]||!tags.indexOf("",""]||tags.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"","
"]||!tags.indexOf("",""]||(!tags.indexOf("",""]||!tags.indexOf("",""]||jQuery.browser.msie&&[1,"div
","
"]||[0,"",""];div.innerHTML=wrap[1]+elem+wrap[2];while(wrap[0]--)div=div.lastChild;if(jQuery.browser.msie){var tbody=!tags.indexOf(""&&tags.indexOf("=0;--j)if(jQuery.nodeName(tbody[j],"tbody")&&!tbody[j].childNodes.length)tbody[j].parentNode.removeChild(tbody[j]);if(/^\s/.test(elem))div.insertBefore(context.createTextNode(elem.match(/^\s*/)[0]),div.firstChild);}elem=jQuery.makeArray(div.childNodes);}if(elem.length===0&&(!jQuery.nodeName(elem,"form")&&!jQuery.nodeName(elem,"select")))return;if(elem[0]==undefined||jQuery.nodeName(elem,"form")||elem.options)ret.push(elem);else -ret=jQuery.merge(ret,elem);});return ret;},attr:function(elem,name,value){if(!elem||elem.nodeType==3||elem.nodeType==8)return undefined;var notxml=!jQuery.isXMLDoc(elem),set=value!==undefined,msie=jQuery.browser.msie;name=notxml&&jQuery.props[name]||name;if(elem.tagName){var special=/href|src|style/.test(name);if(name=="selected"&&jQuery.browser.safari)elem.parentNode.selectedIndex;if(name in elem&¬xml&&!special){if(set){if(name=="type"&&jQuery.nodeName(elem,"input")&&elem.parentNode)throw"type property can't be changed";elem[name]=value;}if(jQuery.nodeName(elem,"form")&&elem.getAttributeNode(name))return elem.getAttributeNode(name).nodeValue;return elem[name];}if(msie&¬xml&&name=="style")return jQuery.attr(elem.style,"cssText",value);if(set)elem.setAttribute(name,""+value);var attr=msie&¬xml&&special?elem.getAttribute(name,2):elem.getAttribute(name);return attr===null?undefined:attr;}if(msie&&name=="opacity"){if(set){elem.zoom=1;elem.filter=(elem.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(value)+''=="NaN"?"":"alpha(opacity="+value*100+")");}return elem.filter&&elem.filter.indexOf("opacity=")>=0?(parseFloat(elem.filter.match(/opacity=([^)]*)/)[1])/100)+'':"";}name=name.replace(/-([a-z])/ig,function(all,letter){return letter.toUpperCase();});if(set)elem[name]=value;return elem[name];},trim:function(text){return(text||"").replace(/^\s+|\s+$/g,"");},makeArray:function(array){var ret=[];if(array!=null){var i=array.length;if(i==null||array.split||array.setInterval||array.call)ret[0]=array;else -while(i)ret[--i]=array[i];}return ret;},inArray:function(elem,array){for(var i=0,length=array.length;i*",this).remove();while(this.firstChild)this.removeChild(this.firstChild);}},function(name,fn){jQuery.fn[name]=function(){return this.each(fn,arguments);};});jQuery.each(["Height","Width"],function(i,name){var type=name.toLowerCase();jQuery.fn[type]=function(size){return this[0]==window?jQuery.browser.opera&&document.body["client"+name]||jQuery.browser.safari&&window["inner"+name]||document.compatMode=="CSS1Compat"&&document.documentElement["client"+name]||document.body["client"+name]:this[0]==document?Math.max(Math.max(document.body["scroll"+name],document.documentElement["scroll"+name]),Math.max(document.body["offset"+name],document.documentElement["offset"+name])):size==undefined?(this.length?jQuery.css(this[0],type):null):this.css(type,size.constructor==String?size:size+"px");};});function num(elem,prop){return elem[0]&&parseInt(jQuery.curCSS(elem[0],prop,true),10)||0;}var chars=jQuery.browser.safari&&parseInt(jQuery.browser.version)<417?"(?:[\\w*_-]|\\\\.)":"(?:[\\w\u0128-\uFFFF*_-]|\\\\.)",quickChild=new RegExp("^>\\s*("+chars+"+)"),quickID=new RegExp("^("+chars+"+)(#)("+chars+"+)"),quickClass=new RegExp("^([#.]?)("+chars+"*)");jQuery.extend({expr:{"":function(a,i,m){return m[2]=="*"||jQuery.nodeName(a,m[2]);},"#":function(a,i,m){return a.getAttribute("id")==m[2];},":":{lt:function(a,i,m){return im[3]-0;},nth:function(a,i,m){return m[3]-0==i;},eq:function(a,i,m){return m[3]-0==i;},first:function(a,i){return i==0;},last:function(a,i,m,r){return i==r.length-1;},even:function(a,i){return i%2==0;},odd:function(a,i){return i%2;},"first-child":function(a){return a.parentNode.getElementsByTagName("*")[0]==a;},"last-child":function(a){return jQuery.nth(a.parentNode.lastChild,1,"previousSibling")==a;},"only-child":function(a){return!jQuery.nth(a.parentNode.lastChild,2,"previousSibling");},parent:function(a){return a.firstChild;},empty:function(a){return!a.firstChild;},contains:function(a,i,m){return(a.textContent||a.innerText||jQuery(a).text()||"").indexOf(m[3])>=0;},visible:function(a){return"hidden"!=a.type&&jQuery.css(a,"display")!="none"&&jQuery.css(a,"visibility")!="hidden";},hidden:function(a){return"hidden"==a.type||jQuery.css(a,"display")=="none"||jQuery.css(a,"visibility")=="hidden";},enabled:function(a){return!a.disabled;},disabled:function(a){return a.disabled;},checked:function(a){return a.checked;},selected:function(a){return a.selected||jQuery.attr(a,"selected");},text:function(a){return"text"==a.type;},radio:function(a){return"radio"==a.type;},checkbox:function(a){return"checkbox"==a.type;},file:function(a){return"file"==a.type;},password:function(a){return"password"==a.type;},submit:function(a){return"submit"==a.type;},image:function(a){return"image"==a.type;},reset:function(a){return"reset"==a.type;},button:function(a){return"button"==a.type||jQuery.nodeName(a,"button");},input:function(a){return/input|select|textarea|button/i.test(a.nodeName);},has:function(a,i,m){return jQuery.find(m[3],a).length;},header:function(a){return/h\d/i.test(a.nodeName);},animated:function(a){return jQuery.grep(jQuery.timers,function(fn){return a==fn.elem;}).length;}}},parse:[/^(\[) *@?([\w-]+) *([!*$^~=]*) *('?"?)(.*?)\4 *\]/,/^(:)([\w-]+)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/,new RegExp("^([:.#]*)("+chars+"+)")],multiFilter:function(expr,elems,not){var old,cur=[];while(expr&&expr!=old){old=expr;var f=jQuery.filter(expr,elems,not);expr=f.t.replace(/^\s*,\s*/,"");cur=not?elems=f.r:jQuery.merge(cur,f.r);}return cur;},find:function(t,context){if(typeof t!="string")return[t];if(context&&context.nodeType!=1&&context.nodeType!=9)return[];context=context||document;var ret=[context],done=[],last,nodeName;while(t&&last!=t){var r=[];last=t;t=jQuery.trim(t);var foundToken=false,re=quickChild,m=re.exec(t);if(m){nodeName=m[1].toUpperCase();for(var i=0;ret[i];i++)for(var c=ret[i].firstChild;c;c=c.nextSibling)if(c.nodeType==1&&(nodeName=="*"||c.nodeName.toUpperCase()==nodeName))r.push(c);ret=r;t=t.replace(re,"");if(t.indexOf(" ")==0)continue;foundToken=true;}else{re=/^([>+~])\s*(\w*)/i;if((m=re.exec(t))!=null){r=[];var merge={};nodeName=m[2].toUpperCase();m=m[1];for(var j=0,rl=ret.length;j=0;if(!not&&pass||not&&!pass)tmp.push(r[i]);}return tmp;},filter:function(t,r,not){var last;while(t&&t!=last){last=t;var p=jQuery.parse,m;for(var i=0;p[i];i++){m=p[i].exec(t);if(m){t=t.substring(m[0].length);m[2]=m[2].replace(/\\/g,"");break;}}if(!m)break;if(m[1]==":"&&m[2]=="not")r=isSimple.test(m[3])?jQuery.filter(m[3],r,true).r:jQuery(r).not(m[3]);else if(m[1]==".")r=jQuery.classFilter(r,m[2],not);else if(m[1]=="["){var tmp=[],type=m[3];for(var i=0,rl=r.length;i=0)^not)tmp.push(a);}r=tmp;}else if(m[1]==":"&&m[2]=="nth-child"){var merge={},tmp=[],test=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(m[3]=="even"&&"2n"||m[3]=="odd"&&"2n+1"||!/\D/.test(m[3])&&"0n+"+m[3]||m[3]),first=(test[1]+(test[2]||1))-0,last=test[3]-0;for(var i=0,rl=r.length;i=0)add=true;if(add^not)tmp.push(node);}r=tmp;}else{var fn=jQuery.expr[m[1]];if(typeof fn=="object")fn=fn[m[2]];if(typeof fn=="string")fn=eval("false||function(a,i){return "+fn+";}");r=jQuery.grep(r,function(elem,i){return fn(elem,i,m,r);},not);}}return{r:r,t:t};},dir:function(elem,dir){var matched=[],cur=elem[dir];while(cur&&cur!=document){if(cur.nodeType==1)matched.push(cur);cur=cur[dir];}return matched;},nth:function(cur,result,dir,elem){result=result||1;var num=0;for(;cur;cur=cur[dir])if(cur.nodeType==1&&++num==result)break;return cur;},sibling:function(n,elem){var r=[];for(;n;n=n.nextSibling){if(n.nodeType==1&&n!=elem)r.push(n);}return r;}});jQuery.event={add:function(elem,types,handler,data){if(elem.nodeType==3||elem.nodeType==8)return;if(jQuery.browser.msie&&elem.setInterval)elem=window;if(!handler.guid)handler.guid=this.guid++;if(data!=undefined){var fn=handler;handler=this.proxy(fn,function(){return fn.apply(this,arguments);});handler.data=data;}var events=jQuery.data(elem,"events")||jQuery.data(elem,"events",{}),handle=jQuery.data(elem,"handle")||jQuery.data(elem,"handle",function(){if(typeof jQuery!="undefined"&&!jQuery.event.triggered)return jQuery.event.handle.apply(arguments.callee.elem,arguments);});handle.elem=elem;jQuery.each(types.split(/\s+/),function(index,type){var parts=type.split(".");type=parts[0];handler.type=parts[1];var handlers=events[type];if(!handlers){handlers=events[type]={};if(!jQuery.event.special[type]||jQuery.event.special[type].setup.call(elem)===false){if(elem.addEventListener)elem.addEventListener(type,handle,false);else if(elem.attachEvent)elem.attachEvent("on"+type,handle);}}handlers[handler.guid]=handler;jQuery.event.global[type]=true;});elem=null;},guid:1,global:{},remove:function(elem,types,handler){if(elem.nodeType==3||elem.nodeType==8)return;var events=jQuery.data(elem,"events"),ret,index;if(events){if(types==undefined||(typeof types=="string"&&types.charAt(0)=="."))for(var type in events)this.remove(elem,type+(types||""));else{if(types.type){handler=types.handler;types=types.type;}jQuery.each(types.split(/\s+/),function(index,type){var parts=type.split(".");type=parts[0];if(events[type]){if(handler)delete events[type][handler.guid];else -for(handler in events[type])if(!parts[1]||events[type][handler].type==parts[1])delete events[type][handler];for(ret in events[type])break;if(!ret){if(!jQuery.event.special[type]||jQuery.event.special[type].teardown.call(elem)===false){if(elem.removeEventListener)elem.removeEventListener(type,jQuery.data(elem,"handle"),false);else if(elem.detachEvent)elem.detachEvent("on"+type,jQuery.data(elem,"handle"));}ret=null;delete events[type];}}});}for(ret in events)break;if(!ret){var handle=jQuery.data(elem,"handle");if(handle)handle.elem=null;jQuery.removeData(elem,"events");jQuery.removeData(elem,"handle");}}},trigger:function(type,data,elem,donative,extra){data=jQuery.makeArray(data);if(type.indexOf("!")>=0){type=type.slice(0,-1);var exclusive=true;}if(!elem){if(this.global[type])jQuery("*").add([window,document]).trigger(type,data);}else{if(elem.nodeType==3||elem.nodeType==8)return undefined;var val,ret,fn=jQuery.isFunction(elem[type]||null),event=!data[0]||!data[0].preventDefault;if(event){data.unshift({type:type,target:elem,preventDefault:function(){},stopPropagation:function(){},timeStamp:now()});data[0][expando]=true;}data[0].type=type;if(exclusive)data[0].exclusive=true;var handle=jQuery.data(elem,"handle");if(handle)val=handle.apply(elem,data);if((!fn||(jQuery.nodeName(elem,'a')&&type=="click"))&&elem["on"+type]&&elem["on"+type].apply(elem,data)===false)val=false;if(event)data.shift();if(extra&&jQuery.isFunction(extra)){ret=extra.apply(elem,val==null?data:data.concat(val));if(ret!==undefined)val=ret;}if(fn&&donative!==false&&val!==false&&!(jQuery.nodeName(elem,'a')&&type=="click")){this.triggered=true;try{elem[type]();}catch(e){}}this.triggered=false;}return val;},handle:function(event){var val,ret,namespace,all,handlers;event=arguments[0]=jQuery.event.fix(event||window.event);namespace=event.type.split(".");event.type=namespace[0];namespace=namespace[1];all=!namespace&&!event.exclusive;handlers=(jQuery.data(this,"events")||{})[event.type];for(var j in handlers){var handler=handlers[j];if(all||handler.type==namespace){event.handler=handler;event.data=handler.data;ret=handler.apply(this,arguments);if(val!==false)val=ret;if(ret===false){event.preventDefault();event.stopPropagation();}}}return val;},fix:function(event){if(event[expando]==true)return event;var originalEvent=event;event={originalEvent:originalEvent};var props="altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target timeStamp toElement type view wheelDelta which".split(" ");for(var i=props.length;i;i--)event[props[i]]=originalEvent[props[i]];event[expando]=true;event.preventDefault=function(){if(originalEvent.preventDefault)originalEvent.preventDefault();originalEvent.returnValue=false;};event.stopPropagation=function(){if(originalEvent.stopPropagation)originalEvent.stopPropagation();originalEvent.cancelBubble=true;};event.timeStamp=event.timeStamp||now();if(!event.target)event.target=event.srcElement||document;if(event.target.nodeType==3)event.target=event.target.parentNode;if(!event.relatedTarget&&event.fromElement)event.relatedTarget=event.fromElement==event.target?event.toElement:event.fromElement;if(event.pageX==null&&event.clientX!=null){var doc=document.documentElement,body=document.body;event.pageX=event.clientX+(doc&&doc.scrollLeft||body&&body.scrollLeft||0)-(doc.clientLeft||0);event.pageY=event.clientY+(doc&&doc.scrollTop||body&&body.scrollTop||0)-(doc.clientTop||0);}if(!event.which&&((event.charCode||event.charCode===0)?event.charCode:event.keyCode))event.which=event.charCode||event.keyCode;if(!event.metaKey&&event.ctrlKey)event.metaKey=event.ctrlKey;if(!event.which&&event.button)event.which=(event.button&1?1:(event.button&2?3:(event.button&4?2:0)));return event;},proxy:function(fn,proxy){proxy.guid=fn.guid=fn.guid||proxy.guid||this.guid++;return proxy;},special:{ready:{setup:function(){bindReady();return;},teardown:function(){return;}},mouseenter:{setup:function(){if(jQuery.browser.msie)return false;jQuery(this).bind("mouseover",jQuery.event.special.mouseenter.handler);return true;},teardown:function(){if(jQuery.browser.msie)return false;jQuery(this).unbind("mouseover",jQuery.event.special.mouseenter.handler);return true;},handler:function(event){if(withinElement(event,this))return true;event.type="mouseenter";return jQuery.event.handle.apply(this,arguments);}},mouseleave:{setup:function(){if(jQuery.browser.msie)return false;jQuery(this).bind("mouseout",jQuery.event.special.mouseleave.handler);return true;},teardown:function(){if(jQuery.browser.msie)return false;jQuery(this).unbind("mouseout",jQuery.event.special.mouseleave.handler);return true;},handler:function(event){if(withinElement(event,this))return true;event.type="mouseleave";return jQuery.event.handle.apply(this,arguments);}}}};jQuery.fn.extend({bind:function(type,data,fn){return type=="unload"?this.one(type,data,fn):this.each(function(){jQuery.event.add(this,type,fn||data,fn&&data);});},one:function(type,data,fn){var one=jQuery.event.proxy(fn||data,function(event){jQuery(this).unbind(event,one);return(fn||data).apply(this,arguments);});return this.each(function(){jQuery.event.add(this,type,one,fn&&data);});},unbind:function(type,fn){return this.each(function(){jQuery.event.remove(this,type,fn);});},trigger:function(type,data,fn){return this.each(function(){jQuery.event.trigger(type,data,this,true,fn);});},triggerHandler:function(type,data,fn){return this[0]&&jQuery.event.trigger(type,data,this[0],false,fn);},toggle:function(fn){var args=arguments,i=1;while(i=0){var selector=url.slice(off,url.length);url=url.slice(0,off);}callback=callback||function(){};var type="GET";if(params)if(jQuery.isFunction(params)){callback=params;params=null;}else{params=jQuery.param(params);type="POST";}var self=this;jQuery.ajax({url:url,type:type,dataType:"html",data:params,complete:function(res,status){if(status=="success"||status=="notmodified")self.html(selector?jQuery("
").append(res.responseText.replace(//g,"")).find(selector):res.responseText);self.each(callback,[res.responseText,status,res]);}});return this;},serialize:function(){return jQuery.param(this.serializeArray());},serializeArray:function(){return this.map(function(){return jQuery.nodeName(this,"form")?jQuery.makeArray(this.elements):this;}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password/i.test(this.type));}).map(function(i,elem){var val=jQuery(this).val();return val==null?null:val.constructor==Array?jQuery.map(val,function(val,i){return{name:elem.name,value:val};}):{name:elem.name,value:val};}).get();}});jQuery.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(i,o){jQuery.fn[o]=function(f){return this.bind(o,f);};});var jsc=now();jQuery.extend({get:function(url,data,callback,type){if(jQuery.isFunction(data)){callback=data;data=null;}return jQuery.ajax({type:"GET",url:url,data:data,success:callback,dataType:type});},getScript:function(url,callback){return jQuery.get(url,null,callback,"script");},getJSON:function(url,data,callback){return jQuery.get(url,data,callback,"json");},post:function(url,data,callback,type){if(jQuery.isFunction(data)){callback=data;data={};}return jQuery.ajax({type:"POST",url:url,data:data,success:callback,dataType:type});},ajaxSetup:function(settings){jQuery.extend(jQuery.ajaxSettings,settings);},ajaxSettings:{url:location.href,global:true,type:"GET",timeout:0,contentType:"application/x-www-form-urlencoded",processData:true,async:true,data:null,username:null,password:null,accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(s){s=jQuery.extend(true,s,jQuery.extend(true,{},jQuery.ajaxSettings,s));var jsonp,jsre=/=\?(&|$)/g,status,data,type=s.type.toUpperCase();if(s.data&&s.processData&&typeof s.data!="string")s.data=jQuery.param(s.data);if(s.dataType=="jsonp"){if(type=="GET"){if(!s.url.match(jsre))s.url+=(s.url.match(/\?/)?"&":"?")+(s.jsonp||"callback")+"=?";}else if(!s.data||!s.data.match(jsre))s.data=(s.data?s.data+"&":"")+(s.jsonp||"callback")+"=?";s.dataType="json";}if(s.dataType=="json"&&(s.data&&s.data.match(jsre)||s.url.match(jsre))){jsonp="jsonp"+jsc++;if(s.data)s.data=(s.data+"").replace(jsre,"="+jsonp+"$1");s.url=s.url.replace(jsre,"="+jsonp+"$1");s.dataType="script";window[jsonp]=function(tmp){data=tmp;success();complete();window[jsonp]=undefined;try{delete window[jsonp];}catch(e){}if(head)head.removeChild(script);};}if(s.dataType=="script"&&s.cache==null)s.cache=false;if(s.cache===false&&type=="GET"){var ts=now();var ret=s.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+ts+"$2");s.url=ret+((ret==s.url)?(s.url.match(/\?/)?"&":"?")+"_="+ts:"");}if(s.data&&type=="GET"){s.url+=(s.url.match(/\?/)?"&":"?")+s.data;s.data=null;}if(s.global&&!jQuery.active++)jQuery.event.trigger("ajaxStart");var remote=/^(?:\w+:)?\/\/([^\/?#]+)/;if(s.dataType=="script"&&type=="GET"&&remote.test(s.url)&&remote.exec(s.url)[1]!=location.host){var head=document.getElementsByTagName("head")[0];var script=document.createElement("script");script.src=s.url;if(s.scriptCharset)script.charset=s.scriptCharset;if(!jsonp){var done=false;script.onload=script.onreadystatechange=function(){if(!done&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){done=true;success();complete();head.removeChild(script);}};}head.appendChild(script);return undefined;}var requestDone=false;var xhr=window.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest();if(s.username)xhr.open(type,s.url,s.async,s.username,s.password);else -xhr.open(type,s.url,s.async);try{if(s.data)xhr.setRequestHeader("Content-Type",s.contentType);if(s.ifModified)xhr.setRequestHeader("If-Modified-Since",jQuery.lastModified[s.url]||"Thu, 01 Jan 1970 00:00:00 GMT");xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Accept",s.dataType&&s.accepts[s.dataType]?s.accepts[s.dataType]+", */*":s.accepts._default);}catch(e){}if(s.beforeSend&&s.beforeSend(xhr,s)===false){s.global&&jQuery.active--;xhr.abort();return false;}if(s.global)jQuery.event.trigger("ajaxSend",[xhr,s]);var onreadystatechange=function(isTimeout){if(!requestDone&&xhr&&(xhr.readyState==4||isTimeout=="timeout")){requestDone=true;if(ival){clearInterval(ival);ival=null;}status=isTimeout=="timeout"&&"timeout"||!jQuery.httpSuccess(xhr)&&"error"||s.ifModified&&jQuery.httpNotModified(xhr,s.url)&&"notmodified"||"success";if(status=="success"){try{data=jQuery.httpData(xhr,s.dataType,s.dataFilter);}catch(e){status="parsererror";}}if(status=="success"){var modRes;try{modRes=xhr.getResponseHeader("Last-Modified");}catch(e){}if(s.ifModified&&modRes)jQuery.lastModified[s.url]=modRes;if(!jsonp)success();}else -jQuery.handleError(s,xhr,status);complete();if(s.async)xhr=null;}};if(s.async){var ival=setInterval(onreadystatechange,13);if(s.timeout>0)setTimeout(function(){if(xhr){xhr.abort();if(!requestDone)onreadystatechange("timeout");}},s.timeout);}try{xhr.send(s.data);}catch(e){jQuery.handleError(s,xhr,null,e);}if(!s.async)onreadystatechange();function success(){if(s.success)s.success(data,status);if(s.global)jQuery.event.trigger("ajaxSuccess",[xhr,s]);}function complete(){if(s.complete)s.complete(xhr,status);if(s.global)jQuery.event.trigger("ajaxComplete",[xhr,s]);if(s.global&&!--jQuery.active)jQuery.event.trigger("ajaxStop");}return xhr;},handleError:function(s,xhr,status,e){if(s.error)s.error(xhr,status,e);if(s.global)jQuery.event.trigger("ajaxError",[xhr,s,e]);},active:0,httpSuccess:function(xhr){try{return!xhr.status&&location.protocol=="file:"||(xhr.status>=200&&xhr.status<300)||xhr.status==304||xhr.status==1223||jQuery.browser.safari&&xhr.status==undefined;}catch(e){}return false;},httpNotModified:function(xhr,url){try{var xhrRes=xhr.getResponseHeader("Last-Modified");return xhr.status==304||xhrRes==jQuery.lastModified[url]||jQuery.browser.safari&&xhr.status==undefined;}catch(e){}return false;},httpData:function(xhr,type,filter){var ct=xhr.getResponseHeader("content-type"),xml=type=="xml"||!type&&ct&&ct.indexOf("xml")>=0,data=xml?xhr.responseXML:xhr.responseText;if(xml&&data.documentElement.tagName=="parsererror")throw"parsererror";if(filter)data=filter(data,type);if(type=="script")jQuery.globalEval(data);if(type=="json")data=eval("("+data+")");return data;},param:function(a){var s=[];if(a.constructor==Array||a.jquery)jQuery.each(a,function(){s.push(encodeURIComponent(this.name)+"="+encodeURIComponent(this.value));});else -for(var j in a)if(a[j]&&a[j].constructor==Array)jQuery.each(a[j],function(){s.push(encodeURIComponent(j)+"="+encodeURIComponent(this));});else -s.push(encodeURIComponent(j)+"="+encodeURIComponent(jQuery.isFunction(a[j])?a[j]():a[j]));return s.join("&").replace(/%20/g,"+");}});jQuery.fn.extend({show:function(speed,callback){return speed?this.animate({height:"show",width:"show",opacity:"show"},speed,callback):this.filter(":hidden").each(function(){this.style.display=this.oldblock||"";if(jQuery.css(this,"display")=="none"){var elem=jQuery("<"+this.tagName+" />").appendTo("body");this.style.display=elem.css("display");if(this.style.display=="none")this.style.display="block";elem.remove();}}).end();},hide:function(speed,callback){return speed?this.animate({height:"hide",width:"hide",opacity:"hide"},speed,callback):this.filter(":visible").each(function(){this.oldblock=this.oldblock||jQuery.css(this,"display");this.style.display="none";}).end();},_toggle:jQuery.fn.toggle,toggle:function(fn,fn2){return jQuery.isFunction(fn)&&jQuery.isFunction(fn2)?this._toggle.apply(this,arguments):fn?this.animate({height:"toggle",width:"toggle",opacity:"toggle"},fn,fn2):this.each(function(){jQuery(this)[jQuery(this).is(":hidden")?"show":"hide"]();});},slideDown:function(speed,callback){return this.animate({height:"show"},speed,callback);},slideUp:function(speed,callback){return this.animate({height:"hide"},speed,callback);},slideToggle:function(speed,callback){return this.animate({height:"toggle"},speed,callback);},fadeIn:function(speed,callback){return this.animate({opacity:"show"},speed,callback);},fadeOut:function(speed,callback){return this.animate({opacity:"hide"},speed,callback);},fadeTo:function(speed,to,callback){return this.animate({opacity:to},speed,callback);},animate:function(prop,speed,easing,callback){var optall=jQuery.speed(speed,easing,callback);return this[optall.queue===false?"each":"queue"](function(){if(this.nodeType!=1)return false;var opt=jQuery.extend({},optall),p,hidden=jQuery(this).is(":hidden"),self=this;for(p in prop){if(prop[p]=="hide"&&hidden||prop[p]=="show"&&!hidden)return opt.complete.call(this);if(p=="height"||p=="width"){opt.display=jQuery.css(this,"display");opt.overflow=this.style.overflow;}}if(opt.overflow!=null)this.style.overflow="hidden";opt.curAnim=jQuery.extend({},prop);jQuery.each(prop,function(name,val){var e=new jQuery.fx(self,opt,name);if(/toggle|show|hide/.test(val))e[val=="toggle"?hidden?"show":"hide":val](prop);else{var parts=val.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),start=e.cur(true)||0;if(parts){var end=parseFloat(parts[2]),unit=parts[3]||"px";if(unit!="px"){self.style[name]=(end||1)+unit;start=((end||1)/e.cur(true))*start;self.style[name]=start+unit;}if(parts[1])end=((parts[1]=="-="?-1:1)*end)+start;e.custom(start,end,unit);}else -e.custom(start,val,"");}});return true;});},queue:function(type,fn){if(jQuery.isFunction(type)||(type&&type.constructor==Array)){fn=type;type="fx";}if(!type||(typeof type=="string"&&!fn))return queue(this[0],type);return this.each(function(){if(fn.constructor==Array)queue(this,type,fn);else{queue(this,type).push(fn);if(queue(this,type).length==1)fn.call(this);}});},stop:function(clearQueue,gotoEnd){var timers=jQuery.timers;if(clearQueue)this.queue([]);this.each(function(){for(var i=timers.length-1;i>=0;i--)if(timers[i].elem==this){if(gotoEnd)timers[i](true);timers.splice(i,1);}});if(!gotoEnd)this.dequeue();return this;}});var queue=function(elem,type,array){if(elem){type=type||"fx";var q=jQuery.data(elem,type+"queue");if(!q||array)q=jQuery.data(elem,type+"queue",jQuery.makeArray(array));}return q;};jQuery.fn.dequeue=function(type){type=type||"fx";return this.each(function(){var q=queue(this,type);q.shift();if(q.length)q[0].call(this);});};jQuery.extend({speed:function(speed,easing,fn){var opt=speed&&speed.constructor==Object?speed:{complete:fn||!fn&&easing||jQuery.isFunction(speed)&&speed,duration:speed,easing:fn&&easing||easing&&easing.constructor!=Function&&easing};opt.duration=(opt.duration&&opt.duration.constructor==Number?opt.duration:jQuery.fx.speeds[opt.duration])||jQuery.fx.speeds.def;opt.old=opt.complete;opt.complete=function(){if(opt.queue!==false)jQuery(this).dequeue();if(jQuery.isFunction(opt.old))opt.old.call(this);};return opt;},easing:{linear:function(p,n,firstNum,diff){return firstNum+diff*p;},swing:function(p,n,firstNum,diff){return((-Math.cos(p*Math.PI)/2)+0.5)*diff+firstNum;}},timers:[],timerId:null,fx:function(elem,options,prop){this.options=options;this.elem=elem;this.prop=prop;if(!options.orig)options.orig={};}});jQuery.fx.prototype={update:function(){if(this.options.step)this.options.step.call(this.elem,this.now,this);(jQuery.fx.step[this.prop]||jQuery.fx.step._default)(this);if(this.prop=="height"||this.prop=="width")this.elem.style.display="block";},cur:function(force){if(this.elem[this.prop]!=null&&this.elem.style[this.prop]==null)return this.elem[this.prop];var r=parseFloat(jQuery.css(this.elem,this.prop,force));return r&&r>-10000?r:parseFloat(jQuery.curCSS(this.elem,this.prop))||0;},custom:function(from,to,unit){this.startTime=now();this.start=from;this.end=to;this.unit=unit||this.unit||"px";this.now=this.start;this.pos=this.state=0;this.update();var self=this;function t(gotoEnd){return self.step(gotoEnd);}t.elem=this.elem;jQuery.timers.push(t);if(jQuery.timerId==null){jQuery.timerId=setInterval(function(){var timers=jQuery.timers;for(var i=0;ithis.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var done=true;for(var i in this.options.curAnim)if(this.options.curAnim[i]!==true)done=false;if(done){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(jQuery.css(this.elem,"display")=="none")this.elem.style.display="block";}if(this.options.hide)this.elem.style.display="none";if(this.options.hide||this.options.show)for(var p in this.options.curAnim)jQuery.attr(this.elem.style,p,this.options.orig[p]);}if(done)this.options.complete.call(this.elem);return false;}else{var n=t-this.startTime;this.state=n/this.options.duration;this.pos=jQuery.easing[this.options.easing||(jQuery.easing.swing?"swing":"linear")](this.state,n,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update();}return true;}};jQuery.extend(jQuery.fx,{speeds:{slow:600,fast:200,def:400},step:{scrollLeft:function(fx){fx.elem.scrollLeft=fx.now;},scrollTop:function(fx){fx.elem.scrollTop=fx.now;},opacity:function(fx){jQuery.attr(fx.elem.style,"opacity",fx.now);},_default:function(fx){fx.elem.style[fx.prop]=fx.now+fx.unit;}}});jQuery.fn.offset=function(){var left=0,top=0,elem=this[0],results;if(elem)with(jQuery.browser){var parent=elem.parentNode,offsetChild=elem,offsetParent=elem.offsetParent,doc=elem.ownerDocument,safari2=safari&&parseInt(version)<522&&!/adobeair/i.test(userAgent),css=jQuery.curCSS,fixed=css(elem,"position")=="fixed";if(elem.getBoundingClientRect){var box=elem.getBoundingClientRect();add(box.left+Math.max(doc.documentElement.scrollLeft,doc.body.scrollLeft),box.top+Math.max(doc.documentElement.scrollTop,doc.body.scrollTop));add(-doc.documentElement.clientLeft,-doc.documentElement.clientTop);}else{add(elem.offsetLeft,elem.offsetTop);while(offsetParent){add(offsetParent.offsetLeft,offsetParent.offsetTop);if(mozilla&&!/^t(able|d|h)$/i.test(offsetParent.tagName)||safari&&!safari2)border(offsetParent);if(!fixed&&css(offsetParent,"position")=="fixed")fixed=true;offsetChild=/^body$/i.test(offsetParent.tagName)?offsetChild:offsetParent;offsetParent=offsetParent.offsetParent;}while(parent&&parent.tagName&&!/^body|html$/i.test(parent.tagName)){if(!/^inline|table.*$/i.test(css(parent,"display")))add(-parent.scrollLeft,-parent.scrollTop);if(mozilla&&css(parent,"overflow")!="visible")border(parent);parent=parent.parentNode;}if((safari2&&(fixed||css(offsetChild,"position")=="absolute"))||(mozilla&&css(offsetChild,"position")!="absolute"))add(-doc.body.offsetLeft,-doc.body.offsetTop);if(fixed)add(Math.max(doc.documentElement.scrollLeft,doc.body.scrollLeft),Math.max(doc.documentElement.scrollTop,doc.body.scrollTop));}results={top:top,left:left};}function border(elem){add(jQuery.curCSS(elem,"borderLeftWidth",true),jQuery.curCSS(elem,"borderTopWidth",true));}function add(l,t){left+=parseInt(l,10)||0;top+=parseInt(t,10)||0;}return results;};jQuery.fn.extend({position:function(){var left=0,top=0,results;if(this[0]){var offsetParent=this.offsetParent(),offset=this.offset(),parentOffset=/^body|html$/i.test(offsetParent[0].tagName)?{top:0,left:0}:offsetParent.offset();offset.top-=num(this,'marginTop');offset.left-=num(this,'marginLeft');parentOffset.top+=num(offsetParent,'borderTopWidth');parentOffset.left+=num(offsetParent,'borderLeftWidth');results={top:offset.top-parentOffset.top,left:offset.left-parentOffset.left};}return results;},offsetParent:function(){var offsetParent=this[0].offsetParent;while(offsetParent&&(!/^body|html$/i.test(offsetParent.tagName)&&jQuery.css(offsetParent,'position')=='static'))offsetParent=offsetParent.offsetParent;return jQuery(offsetParent);}});jQuery.each(['Left','Top'],function(i,name){var method='scroll'+name;jQuery.fn[method]=function(val){if(!this[0])return;return val!=undefined?this.each(function(){this==window||this==document?window.scrollTo(!i?val:jQuery(window).scrollLeft(),i?val:jQuery(window).scrollTop()):this[method]=val;}):this[0]==window||this[0]==document?self[i?'pageYOffset':'pageXOffset']||jQuery.boxModel&&document.documentElement[method]||document.body[method]:this[0][method];};});jQuery.each(["Height","Width"],function(i,name){var tl=i?"Left":"Top",br=i?"Right":"Bottom";jQuery.fn["inner"+name]=function(){return this[name.toLowerCase()]()+num(this,"padding"+tl)+num(this,"padding"+br);};jQuery.fn["outer"+name]=function(margin){return this["inner"+name]()+num(this,"border"+tl+"Width")+num(this,"border"+br+"Width")+(margin?num(this,"margin"+tl)+num(this,"margin"+br):0);};});})(); diff --git a/docs/conf.py b/docs/conf.py index 248f450..4986e0b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -29,11 +29,16 @@ autoclass_content = 'both' # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. -needs_sphinx = '1.0' +needs_sphinx = '1.1' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] +extensions = ['sphinx.ext.autodoc', + 'sphinx.ext.viewcode', + 'sphinx.ext.intersphinx', + 'sphinx.ext.doctest', + 'sphinxcontrib.fulltoc', + ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_static'] @@ -42,7 +47,7 @@ templates_path = ['_static'] source_suffix = '.rst' # The encoding of source files. -#source_encoding = 'utf-8-sig' +source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' @@ -83,7 +88,7 @@ add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +add_module_names = False # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. @@ -95,6 +100,8 @@ pygments_style = 'monokai' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'http://docs.python.org/': None} # -- Options for HTML output --------------------------------------------------- diff --git a/docs/gnupg.rst b/docs/gnupg.rst index c216846..fc8a479 100644 --- a/docs/gnupg.rst +++ b/docs/gnupg.rst @@ -17,6 +17,7 @@ do: :private-members: :show-inheritance: +.. _meta: meta module ----------- @@ -28,10 +29,13 @@ doing some serious hacking. .. automodule:: gnupg._meta :members: - :undoc-members: :private-members: + :special-members: + :exclude-members: _agent_proc, __module__, __dict__, _decode_errors, init, + __weakref__, _result_map, __metaclass__ :show-inheritance: +.. _parsers: parsers module -------------- @@ -39,10 +43,11 @@ parsers module These are classes for parsing both user inputs and status file descriptor flags from GnuPG's output. The latter are used in order to determine what our GnuPG process is doing and retrieve information about its operations, which -are stored in corresponding classes in :attr:`gnupg.GPG._result_dict`. Some -status flags aren't handled yet -- infomation on *all* of the flags (well, at -least the documented ones…) can be found in the docs/DETAILS file in GnuPG's -source_, which has been included here_ as well. +are stored in corresponding classes in +:attr:`~gnupg._meta.GPGBase._result_map`. Some status flags aren't handled yet +-- information on *all* of the flags (well, at least the documented ones…) can +be found in the :file:`docs/DETAILS` file in GnuPG's source_, which has been +included here_ as well. .. automodule:: gnupg._parsers @@ -52,6 +57,8 @@ source_, which has been included here_ as well. :show-inheritance: +.. _util: + util module ----------- @@ -75,51 +82,50 @@ by Steve Traugott, which in turn is a modification of the pycrypto GnuPG interface written by A.M. Kuchling. This version is patched to sanitize untrusted inputs, due to the necessity of -executing :class:`subprocess.Popen([...], shell=True)` in order to communicate -with GnuPG. Several speed improvements were also made based on code profiling, -and the API has been cleaned up to support an easier, more Pythonic, -interaction. +executing ``subprocess.Popen([...], shell=True)`` in order to communicate with +GnuPG. Several speed improvements were also made based on code profiling, and +the API has been cleaned up to support an easier, more Pythonic, interaction. Previous Authors' Documentation ------------------------------- Steve Traugott's documentation: - - | Portions of this module are derived from A.M. Kuchling's well-designed - | GPG.py, using Richard Jones' updated version 1.3, which can be found in - | the pycrypto CVS repository on Sourceforge: - | - | http://pycrypto.cvs.sourceforge.net/viewvc/pycrypto/gpg/GPG.py - | - | This module is *not* forward-compatible with amk's; some of the old - | interface has changed. For instance, since I've added decrypt - | functionality, I elected to initialize with a 'gpghome' argument instead - | of 'keyring', so that gpg can find both the public and secret keyrings. - | I've also altered some of the returned objects in order for the caller to - | not have to know as much about the internals of the result classes. - | - | While the rest of ISconf is released under the GPL, I am releasing this - | single file under the same terms that A.M. Kuchling used for pycrypto. - | - | Steve Traugott, stevegt@terraluna.org - | Thu Jun 23 21:27:20 PDT 2005 + | + | Portions of this module are derived from A.M. Kuchling's well-designed + | GPG.py, using Richard Jones' updated version 1.3, which can be found in + | the pycrypto CVS repository on Sourceforge: + | + | http://pycrypto.cvs.sourceforge.net/viewvc/pycrypto/gpg/GPG.py + | + | This module is *not* forward-compatible with amk's; some of the old + | interface has changed. For instance, since I've added decrypt + | functionality, I elected to initialize with a 'gpghome' argument instead + | of 'keyring', so that gpg can find both the public and secret keyrings. + | I've also altered some of the returned objects in order for the caller to + | not have to know as much about the internals of the result classes. + | + | While the rest of ISconf is released under the GPL, I am releasing this + | single file under the same terms that A.M. Kuchling used for pycrypto. + | + | Steve Traugott, stevegt@terraluna.org + | Thu Jun 23 21:27:20 PDT 2005 Vinay Sajip's documentation: - - | This version of the module has been modified from Steve Traugott's version - | (see http://trac.t7a.org/isconf/browser/trunk/lib/python/isconf/GPG.py) by - | Vinay Sajip to make use of the subprocess module (Steve's version uses - | os.fork() and so does not work on Windows). Renamed to gnupg.py to avoid - | confusion with the previous versions. - | - | A unittest harness (test_gnupg.py) has also been added. - | - | Modifications Copyright (C) 2008-2012 Vinay Sajip. All rights reserved. + | + | This version of the module has been modified from Steve Traugott's version + | (see http://trac.t7a.org/isconf/browser/trunk/lib/python/isconf/GPG.py) by + | Vinay Sajip to make use of the subprocess module (Steve's version uses + | os.fork() and so does not work on Windows). Renamed to gnupg.py to avoid + | confusion with the previous versions. + | + | A unittest harness (test_gnupg.py) has also been added. + | + | Modifications Copyright (C) 2008-2012 Vinay Sajip. All rights reserved. .. _GnuPG: http://gnupg.org .. _python-gnupg: https://code.google.com/p/python-gnupg/ .. _source: http://http://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=shortlog;h=refs/heads/master -.. _here: ./DETAILS.html +.. _here: ./_static/DETAILS.html diff --git a/docs/index.rst b/docs/index.rst index bafe40e..c266353 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -21,7 +21,7 @@ Contents: Source, license, & bug reports ============================== The source code which was used to generate this documentation is accessible by -clicking the little [source]_ links next to the docs. Current source code can +clicking the little `source` links next to the docs. Current source code can be found in this github repository_. The **master** branch always reflects the latest release, all releases are tagged with signed, annotated git tags, and the **develop** branch represents the state of the next release. diff --git a/gnupg/__init__.py b/gnupg/__init__.py index 982ffbf..5c1430c 100644 --- a/gnupg/__init__.py +++ b/gnupg/__init__.py @@ -8,31 +8,31 @@ # © 2008-2012 Vinay Sajip # © 2005 Steve Traugott # © 2004 A.M. Kuchling -# +# # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) # any later version. -# +# # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details. from __future__ import absolute_import -from . import gnupg -from . import copyleft -from . import _ansistrm -from . import _logger -from . import _meta -from . import _parsers -from . import _util -from .gnupg import GPG +from . import gnupg +from . import copyleft +from . import _ansistrm +from . import _logger +from . import _meta +from . import _parsers +from . import _util +from .gnupg import GPG from ._version import get_versions -__version__ = get_versions()['version'] -__authors__ = copyleft.authors -__license__ = copyleft.full_text +__version__ = get_versions()['version'] +__authors__ = copyleft.authors +__license__ = copyleft.full_text __copyleft__ = copyleft.copyright ## do not set __package__ = "gnupg", else we will end up with diff --git a/gnupg/_logger.py b/gnupg/_logger.py index df56b85..6dd93c7 100644 --- a/gnupg/_logger.py +++ b/gnupg/_logger.py @@ -17,10 +17,7 @@ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details. -'''log.py ----------- -Logging module for python-gnupg. -''' +'''Logging module for python-gnupg.''' from __future__ import absolute_import from __future__ import print_function @@ -51,17 +48,23 @@ def status(self, message, *args, **kwargs): def create_logger(level=logging.NOTSET): """Create a logger for python-gnupg at a specific message level. - :type level: int or str - :param level: A string or an integer for the lowest level to log. - Available levels: - int str description - 0 NOTSET Disable all logging. - 9 GNUPG Log GnuPG's internal status messages. - 10 DEBUG Log module level debuging messages. - 20 INFO Normal user-level messages. - 30 WARN Warning messages. - 40 ERROR Error messages and tracebacks. - 50 CRITICAL Unhandled exceptions and tracebacks. + :type level: :obj:`int` or :obj:`str` + :param level: A string or an integer for the lowest level to include in + logs. + + **Available levels:** + + ==== ======== ======================================== + int str description + ==== ======== ======================================== + 0 NOTSET Disable all logging. + 9 GNUPG Log GnuPG's internal status messages. + 10 DEBUG Log module level debuging messages. + 20 INFO Normal user-level messages. + 30 WARN Warning messages. + 40 ERROR Error messages and tracebacks. + 50 CRITICAL Unhandled exceptions and tracebacks. + ==== ======== ======================================== """ _test = os.path.join(os.path.join(os.getcwd(), 'gnupg'), 'test') _now = datetime.now().strftime("%Y-%m-%d_%H%M%S") @@ -86,10 +89,8 @@ def create_logger(level=logging.NOTSET): formatr = logging.Formatter(_fmt) handler.setFormatter(formatr) - print("Starting the logger...") else: handler = NullHandler() - print("GnuPG logging disabled...") log = logging.getLogger('gnupg') log.addHandler(handler) diff --git a/gnupg/_meta.py b/gnupg/_meta.py index 5ab7a28..6654ac6 100644 --- a/gnupg/_meta.py +++ b/gnupg/_meta.py @@ -7,20 +7,18 @@ # © 2008-2012 Vinay Sajip # © 2005 Steve Traugott # © 2004 A.M. Kuchling -# +# # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) # any later version. -# +# # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details. -'''meta.py ----------- -Meta and base classes for hiding internal functions, and controlling attribute -creation and handling. +'''Meta and base classes for hiding internal functions, and controlling +attribute creation and handling. ''' from __future__ import absolute_import @@ -28,6 +26,7 @@ from __future__ import absolute_import import atexit import codecs import encodings +import exceptions ## For AOS, the locale module will need to point to a wrapper around the ## java.util.Locale class. ## See https://code.patternsinthevoid.net/?p=android-locale-hack.git @@ -52,6 +51,10 @@ class GPGMeta(type): Detects running gpg-agent processes and the presence of a pinentry program, and disables pinentry so that python-gnupg can write the passphrase to the controlled GnuPG process without killing the agent. + + :attr _agent_proc: If a :program:`gpg-agent` process is currently running + for the effective userid, then **_agent_proc** will be + set to a ``psutil.Process`` for that process. """ def __new__(cls, name, bases, attrs): @@ -69,7 +72,7 @@ class GPGMeta(type): If there is a matching gpg-agent process, set a :class:`psutil.Process` instance containing the gpg-agent process' information to - :attr:`GPG._agent_proc`. + ``cls._agent_proc``. :returns: True if there exists a gpg-agent process running under the same effective user ID as that of this program. Otherwise, @@ -87,8 +90,13 @@ class GPGMeta(type): class GPGBase(object): - """Base class for property storage and to control process initialisation.""" + """Base class for storing properties and controlling process initialisation. + :const _result_map: A *dict* containing classes from + :mod:`~gnupg._parsers`, used for parsing results + obtained from GnuPG commands. + :const _decode_errors: How to handle encoding errors. + """ __metaclass__ = GPGMeta _decode_errors = 'strict' _result_map = { 'crypt': _parsers.Crypt, @@ -103,7 +111,28 @@ class GPGBase(object): def __init__(self, binary=None, home=None, keyring=None, secring=None, use_agent=False, default_preference_list=None, verbose=False, options=None): + """Create a ``GPGBase``. + This class is used to set up properties for controlling the behaviour + of configuring various options for GnuPG, such as setting GnuPG's + **homedir** , and the paths to its **binary** and **keyring** . + + :const binary: (:obj:`str`) The full path to the GnuPG binary. + + :ivar homedir: (:class:`~gnupg._util.InheritableProperty`) The full + path to the current setting for the GnuPG + ``--homedir``. + + :ivar _generated_keys: (:class:`~gnupg._util.InheritableProperty`) + Controls setting the directory for storing any + keys which are generated with + :meth:`~gnupg.GPG.gen_key`. + + :ivar str keyring: The filename in **homedir** to use as the keyring + file for public keys. + :ivar str secring: The filename in **homedir** to use as the keyring + file for secret keys. + """ self.binary = _util._find_binary(binary) self.homedir = home if home else _util._conf pub = _parsers._fix_unsafe(keyring) if keyring else 'pubring.gpg' @@ -154,15 +183,14 @@ class GPGBase(object): self.__remove_path__('pinentry') def __remove_path__(self, prog=None, at_exit=True): - """Remove a the directories containing a program from the system's - ``$PATH``. If :attr:`GPG.binary` is in a directory being removed, it - is symlinked to './gpg' + """Remove the directories containing a program from the system's + ``$PATH``. If ``GPGBase.binary`` is in a directory being removed, it + is linked to :file:'./gpg' in the current directory. :param str prog: The program to remove from ``$PATH``. - :param bool at_exit: Add the program back into the ``$PATH`` when the Python interpreter exits, and delete any symlinks - to :attr:`GPG.binary` which were created. + to ``GPGBase.binary`` which were created. """ #: A list of ``$PATH`` entries which were removed to disable pinentry. self._removed_path_entries = [] @@ -222,7 +250,7 @@ class GPGBase(object): @staticmethod def update_path(environment, path): - """Add paths to the string at os.environ['PATH']. + """Add paths to the string at ``os.environ['PATH']``. :param str environment: The environment mapping to update. :param list path: A list of strings to update the PATH with. @@ -270,7 +298,7 @@ class GPGBase(object): Note that "original state" does not mean the default preference list for whichever version of GnuPG is being used. It means the - default preference list defined by :attr:`GPGBase._preferences`. + default preference list defined by :attr:`GPGBase._prefs`. Using BZIP2 is avoided due to not interacting well with some versions of GnuPG>=2.0.0. @@ -324,8 +352,8 @@ class GPGBase(object): :param str directory: A relative or absolute path to the directory to use for storing/accessing GnuPG's files, including keyrings and the trustdb. - :raises: :exc:`RuntimeError` if unable to find a suitable directory to - use. + :raises: :exc:`~exceptions.RuntimeError` if unable to find a suitable + directory to use. """ if not directory: log.debug("GPGBase._homedir_setter(): Using default homedir: '%s'" @@ -365,17 +393,18 @@ class GPGBase(object): def _generated_keys_setter(self, directory): """Set the directory for storing generated keys. - If unspecified, use $GNUPGHOME/generated-keys. If specified, ensure - that the ``directory`` does not contain various shell escape - characters. If ``directory`` is not found, it will be automatically - created. Lastly, the ``direcory`` will be checked that the EUID has - read and write permissions for it. + If unspecified, use + :meth:`~gnupg._meta.GPGBase.homedir`/generated-keys. If specified, + ensure that the ``directory`` does not contain various shell escape + characters. If ``directory`` isn't found, it will be automatically + created. Lastly, the ``directory`` will be checked to ensure that the + current EUID has read and write permissions for it. :param str directory: A relative or absolute path to the directory to use for storing/accessing GnuPG's files, including keyrings and the trustdb. - :raises: :exc:`RuntimeError` if unable to find a suitable directory to - use. + :raises: :exc:`~exceptions.RuntimeError` if unable to find a suitable + directory to use. """ if not directory: directory = os.path.join(self.homedir, 'generated-keys') @@ -407,10 +436,11 @@ class GPGBase(object): _generated_keys_setter) def _make_args(self, args, passphrase=False): - """Make a list of command line elements for GPG. The value of ``args`` - will be appended only if it passes the checks in - :func:`parsers._sanitise`. The ``passphrase`` argument needs to be True - if a passphrase will be sent to GPG, else False. + """Make a list of command line elements for GPG. + + The value of ``args`` will be appended only if it passes the checks in + :func:`gnupg._parsers._sanitise`. The ``passphrase`` argument needs to + be True if a passphrase will be sent to GnuPG, else False. :param list args: A list of strings of options and flags to pass to ``GPG.binary``. This is input safe, meaning that @@ -489,12 +519,13 @@ class GPGBase(object): Calls methods on the response object for each valid token found, with the arg being the remainder of the status line. - :param stream: A byte-stream, file handle, or :class:`subprocess.PIPE` - to parse the for status codes from the GnuPG process. + :param stream: A byte-stream, file handle, or a + :data:`subprocess.PIPE` for parsing the status codes + from the GnuPG process. - :param result: The result parser class from :mod:`_parsers` with which - to call ``handle_status`` and parse the output of - ``stream``. + :param result: The result parser class from :mod:`~gnupg._parsers` ― + the ``handle_status()`` method of that class will be + called in order to parse the output of ``stream``. """ lines = [] while True: @@ -535,8 +566,8 @@ class GPGBase(object): and stored as ``result.data``. :param stream: An open file-like object to read() from. - :param result: An instance of one of the result parsing classes from - :attr:`GPGBase._result_mapping`. + :param result: An instance of one of the :ref:`result parsing classes + ` from :const:`~gnupg._meta.GPGBase._result_map`. """ chunks = [] log.debug("Reading data from stream %r..." % stream.__repr__()) @@ -604,7 +635,7 @@ class GPGBase(object): :param str keyids: A space-delimited string containing the keyids to request. :param str keyserver: The keyserver to request the ``keyids`` from; - defaults to :property:`gnupg.GPG.keyserver`. + defaults to `gnupg.GPG.keyserver`. """ if not keyserver: keyserver = self.keyserver @@ -631,9 +662,10 @@ class GPGBase(object): :param bool detach: If True, create a detached signature. :param bool binary: If True, do not ascii armour the output. :param str digest_algo: The hash digest to use. Again, to see which - hashes your GnuPG is capable of using, do: - ``$ gpg --with-colons --list-config digestname``. - The default, if unspecified, is ``'SHA512'``. + hashes your GnuPG is capable of using, do: + ``$ gpg --with-colons --list-config + digestname``. The default, if unspecified, is + ``'SHA512'``. """ log.debug("_sign_file():") if binary: @@ -681,46 +713,55 @@ class GPGBase(object): cipher_algo='AES256', digest_algo='SHA512', compress_algo='ZLIB'): - """Encrypt the message read from the file-like object ``data``. + """Encrypt the message read from the file-like object **data**. :param str data: The file or bytestream to encrypt. :param str recipients: The recipients to encrypt to. Recipients must - be specified keyID/fingerprint. Care should be taken in Python2.x - to make sure that the given fingerprint is in fact a string and - not a unicode object. + be specified keyID/fingerprint. + + .. warning:: Care should be taken in Python2 to make sure that the + given fingerprints for **recipients** are in fact strings + and not unicode objects. :param str default_key: The keyID/fingerprint of the key to use for - signing. If given, ``data`` will be encrypted and signed. + signing. If given, **data** will be encrypted + *and* signed. - :param str passphrase: If given, and ``default_key`` is also given, - use this passphrase to unlock the secret portion of the - ``default_key`` to sign the encrypted ``data``. Otherwise, if - ``default_key`` is not given, but ``symmetric=True``, then use - this passphrase as the passphrase for symmetric - encryption. Signing and symmetric encryption should *not* be - combined when sending the ``data`` to other recipients, else the - passphrase to the secret key would be shared with them. + :param str passphrase: If given, and **default_key** is also given, + use this passphrase to unlock the secret + portion of the **default_key** to sign the + encrypted **data**. Otherwise, if + **default_key** is not given, but **symmetric** + is ``True``, then use this passphrase as the + passphrase for symmetric encryption. Signing + and symmetric encryption should *not* be + combined when sending the **data** to other + recipients, else the passphrase to the secret + key would be shared with them. :param bool armor: If True, ascii armor the output; otherwise, the - output will be in binary format. (Default: True) + output will be in binary format. (Default: True) - :param bool encrypt: If True, encrypt the ``data`` using the - ``recipients`` public keys. (Default: True) + :param bool encrypt: If True, encrypt the **data** using the + **recipients** public keys. (Default: True) - :param bool symmetric: If True, encrypt the ``data`` to ``recipients`` - using a symmetric key. See the ``passphrase`` parameter. Symmetric - encryption and public key encryption can be used simultaneously, - and will result in a ciphertext which is decryptable with either - the symmetric ``passphrase`` or one of the corresponding private - keys. + :param bool symmetric: If True, encrypt the **data** to **recipients** + using a symmetric key. See the **passphrase** + parameter. Symmetric encryption and public key + encryption can be used simultaneously, and will + result in a ciphertext which is decryptable + with either the symmetric **passphrase** or one + of the corresponding private keys. - :param bool always_trust: If True, ignore trust warnings on recipient - keys. If False, display trust warnings. (default: True) + :param bool always_trust: If True, ignore trust warnings on + **recipients** keys. If False, display trust + warnings. (default: True) :param str output: The output file to write to. If not specified, the - encrypted output is returned, and thus should be stored as an - object in Python. For example: + encrypted output is returned, and thus should be + stored as an object in Python. For example: + >>> import shutil >>> import gnupg @@ -742,17 +783,20 @@ class GPGBase(object): 'The crow flies at midnight.' :param str cipher_algo: The cipher algorithm to use. To see available - algorithms with your version of GnuPG, do: - ``$ gpg --with-colons --list-config ciphername``. - The default ``cipher_algo``, if unspecified, is ``'AES256'``. + algorithms with your version of GnuPG, do: + :command:`$ gpg --with-colons --list-config + ciphername`. The default **cipher_algo**, if + unspecified, is ``'AES256'``. :param str digest_algo: The hash digest to use. Again, to see which - hashes your GnuPG is capable of using, do: - ``$ gpg --with-colons --list-config digestname``. - The default, if unspecified, is ``'SHA512'``. + hashes your GnuPG is capable of using, do: + :command:`$ gpg --with-colons --list-config + digestname`. The default, if unspecified, is + ``'SHA512'``. :param str compress_algo: The compression algorithm to use. Can be one - of ``'ZLIB'``, ``'BZIP2'``, ``'ZIP'``, or ``'Uncompressed'``. + of ``'ZLIB'``, ``'BZIP2'``, ``'ZIP'``, or + ``'Uncompressed'``. """ args = [] diff --git a/gnupg/_parsers.py b/gnupg/_parsers.py index 1c99c0d..58a681c 100644 --- a/gnupg/_parsers.py +++ b/gnupg/_parsers.py @@ -7,25 +7,28 @@ # © 2008-2012 Vinay Sajip # © 2005 Steve Traugott # © 2004 A.M. Kuchling -# +# # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) # any later version. -# +# # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details. -'''parsers.py -------------- -Classes for parsing GnuPG status messages and sanitising commandline options. +'''Classes for parsing GnuPG status messages and sanitising commandline +options. ''' from __future__ import absolute_import from __future__ import print_function -import collections +try: + import collections +except ImportError: + import ordereddict as collections + import re from . import _util @@ -51,7 +54,7 @@ def _check_keyserver(location): should contain the desired keyserver protocol which is supported by the keyserver, for example, the default is ``'hkp://subkeys.pgp.net'``. - :rtype: str or None + :rtype: :obj:`str` or :obj:`None` :returns: A string specifying the protocol and keyserver hostname, if the checks passed. If not, returns None. """ @@ -74,10 +77,11 @@ def _check_keyserver(location): def _check_preferences(prefs, pref_type=None): """Check cipher, digest, and compression preference settings. - MD5 is not allowed. This is not 1994.[0] SHA1 is allowed grudgingly.[1] + MD5 is not allowed. This is `not 1994`__. SHA1 is allowed_ grudgingly_. - [0]: http://www.cs.colorado.edu/~jrblack/papers/md5e-full.pdf - [1]: http://eprint.iacr.org/2008/469.pdf + __ http://www.cs.colorado.edu/~jrblack/papers/md5e-full.pdf + .. _allowed: http://eprint.iacr.org/2008/469.pdf + .. _grudgingly: https://www.schneier.com/blog/archives/2012/10/when_will_we_se.html """ if prefs is None: return @@ -115,8 +119,8 @@ def _check_preferences(prefs, pref_type=None): return allowed def _fix_unsafe(shell_input): - """Find characters used to escape from a string into a shell, and wrap them - in quotes if they exist. Regex pilfered from python-3.x shlex module. + """Find characters used to escape from a string into a shell, and wrap them in + quotes if they exist. Regex pilfered from Python3 :mod:`shlex` module. :param str shell_input: The input intended for the GnuPG process. """ @@ -161,11 +165,15 @@ def _is_allowed(input): class and its name will need to be added to this set. - :rtype: Exception or str - :raise: :exc:UsageError if ``_allowed`` is not a subset of ``_possible``. - ProtectedOption if ``input`` is not in the set ``_allowed``. - :return: The original parameter ``input``, unmodified and unsanitized, - if no errors occur. + :raises: :exc:`UsageError` if **input** is not a subset of the hard-coded + set of all GnuPG options in :func:`_get_all_gnupg_options`. + + :exc:`ProtectedOption` if **input** is not in the set of allowed + options. + + :rtype: str + :return: The original **input** parameter, unmodified and unsanitized, if + no errors occur. """ gnupg_options = _get_all_gnupg_options() allowed = _get_options_group("allowed") @@ -220,12 +228,12 @@ def _is_hex(string): def _is_string(thing): """Python character arrays are a mess. - If Python2, check if ``thing`` is a ``unicode()`` or ``str()``. - If Python3, check if ``thing`` is a ``str()``. + If Python2, check if **thing** is an :obj:`unicode` or a :obj:`str`. + If Python3, check if **thing** is a :obj:`str`. :param thing: The thing to check. - :returns: ``True`` if ``thing`` is a "string" according to whichever - version of Python we're running in. + :returns: ``True`` if **thing** is a string according to whichever version + of Python we're running in. """ if _util._py3k: return isinstance(thing, str) else: return isinstance(thing, basestring) @@ -238,18 +246,20 @@ def _sanitise(*args): sanitised, allowed options. Each new option that we support that is not a boolean, but instead has - some extra inputs, i.e. "--encrypt-file foo.txt", will need some basic - safety checks added here. + some additional inputs following it, i.e. "--encrypt-file foo.txt", will + need some basic safety checks added here. GnuPG has three-hundred and eighteen commandline flags. Also, not all implementations of OpenPGP parse PGP packets and headers in the same way, so there is added potential there for messing with calls to GPG. - For information on the PGP message format specification, see: - https://www.ietf.org/rfc/rfc1991.txt + For information on the PGP message format specification, see + :rfc:`1991`. If you're asking, "Is this *really* necessary?": No, not really -- we could - just do a check as described here: https://xkcd.com/1181/ + just follow the security precautions recommended by `this xkcd`__. + + __ https://xkcd.com/1181/ :param str args: (optional) The boolean arguments which will be passed to the GnuPG process. @@ -260,22 +270,23 @@ def _sanitise(*args): ## see TODO file, tag :cleanup:sanitise: def _check_option(arg, value): - """ - Check that a single :param:arg is an allowed option. If it is allowed, - quote out any escape characters in :param:values, and add the pair to - :ivar:sanitised. + """Check that a single ``arg`` is an allowed option. + + If it is allowed, quote out any escape characters in ``value``, and + add the pair to :ivar:`sanitised`. Otherwise, drop them. :param str arg: The arguments which will be passed to the GnuPG process, and, optionally their corresponding values. The values are any additional arguments following the - GnuPG option or flag. For example, if we wanted to pass - "--encrypt --recipient isis@leap.se" to gpg, then - "--encrypt" would be an arg without a value, and - "--recipient" would also be an arg, with a value of - "isis@leap.se". + GnuPG option or flag. For example, if we wanted to + pass ``"--encrypt --recipient isis@leap.se"`` to + GnuPG, then ``"--encrypt"`` would be an arg without a + value, and ``"--recipient"`` would also be an arg, + with a value of ``"isis@leap.se"``. + :ivar list checked: The sanitised, allowed options and values. :rtype: str - :returns: A string of the items in ``checked`` delimited by spaces. + :returns: A string of the items in ``checked``, delimited by spaces. """ checked = str() none_options = _get_options_group("none_options") @@ -332,8 +343,9 @@ def _sanitise(*args): if flag in ['--encrypt', '--encrypt-files', '--decrypt', '--decrypt-files', '--import', '--verify']: - if _util._is_file(val) or \ - (flag == '--verify' and val == '-'): + if ( (_util._is_file(val)) + or + ((flag == '--verify') and (val == '-')) ): checked += (val + " ") else: log.debug("%s not file: %s" % (flag, val)) @@ -574,21 +586,22 @@ def _get_all_gnupg_options(): This is hardcoded within a local scope to reduce the chance of a tampered GnuPG binary reporting falsified option sets, i.e. because certain options - (namedly the '--no-options' option, which prevents the usage of gpg.conf + (namedly the ``--no-options`` option, which prevents the usage of gpg.conf files) are necessary and statically specified in - :meth:`gnupg.GPG._makeargs`, if the inputs into Python are already - controlled, and we were to summon the GnuPG binary to ask it for its - options, it would be possible to receive a falsified options set missing - the '--no-options' option in response. This seems unlikely, and the method - is stupid and ugly, but at least we'll never have to debug whether or not - an option *actually* disappeared in a different GnuPG version, or some - funny business is happening. + :meth:`gnupg._meta.GPGBase._make_args`, if the inputs into Python are + already controlled, and we were to summon the GnuPG binary to ask it for + its options, it would be possible to receive a falsified options set + missing the ``--no-options`` option in response. This seems unlikely, and + the method is stupid and ugly, but at least we'll never have to debug + whether or not an option *actually* disappeared in a different GnuPG + version, or some funny business is happening. These are the options as of GnuPG 1.4.12; the current stable branch of the 2.1.x tree contains a few more -- if you need them you'll have to add them in here. - :ivar frozenset gnupg_options: All known GPG options and flags. + :type gnupg_options: frozenset + :ivar gnupg_options: All known GPG options and flags. :rtype: frozenset :returns: ``gnupg_options`` """ @@ -791,8 +804,8 @@ def progress(status_code): class GenKey(object): """Handle status messages for key generation. - Calling the GenKey.__str__() method of this class will return the - generated key's fingerprint, or a status string explaining the results. + Calling the ``__str__()`` method of this class will return the generated + key's fingerprint, or a status string explaining the results. """ def __init__(self, gpg): self._gpg = gpg @@ -803,11 +816,13 @@ class GenKey(object): self.status = None self.subkey_created = False self.primary_created = False - #: This will store the filename of the key's public keyring if - #: :meth:`GPG.gen_key_input` was called with ``separate_keyring=True`` + #: This will store the key's public keyring filename, if + #: :meth:`~gnupg.GPG.gen_key_input` was called with + #: ``separate_keyring=True``. self.keyring = None - #: This will store the filename of the key's secret keyring if - #: :meth:`GPG.gen_key_input` was called with ``separate_keyring=True`` + #: This will store the key's secret keyring filename, if : + #: :meth:`~gnupg.GPG.gen_key_input` was called with + #: ``separate_keyring=True``. self.secring = None def __nonzero__(self): @@ -827,7 +842,7 @@ class GenKey(object): def _handle_status(self, key, value): """Parse a status code from the attached GnuPG process. - :raises: :exc:`ValueError` if the status message is unknown. + :raises: :exc:`~exceptions.ValueError` if the status message is unknown. """ if key in ("GOOD_PASSPHRASE"): pass @@ -864,7 +879,7 @@ class DeleteResult(object): def _handle_status(self, key, value): """Parse a status code from the attached GnuPG process. - :raises: :exc:`ValueError` if the status message is unknown. + :raises: :exc:`~exceptions.ValueError` if the status message is unknown. """ if key == "DELETE_PROBLEM": self.status = self.problem_reason.get(value, "Unknown error: %r" @@ -909,7 +924,7 @@ class Sign(object): def _handle_status(self, key, value): """Parse a status code from the attached GnuPG process. - :raises: :exc:`ValueError` if the status message is unknown. + :raises: :exc:`~exceptions.ValueError` if the status message is unknown. """ if key in ("USERID_HINT", "NEED_PASSPHRASE", "BAD_PASSPHRASE", "GOOD_PASSPHRASE", "BEGIN_SIGNING", "CARDCTRL", @@ -1041,7 +1056,7 @@ class ImportResult(object): def _handle_status(self, key, value): """Parse a status code from the attached GnuPG process. - :raises: :exc:`ValueError` if the status message is unknown. + :raises: :exc:`~exceptions.ValueError` if the status message is unknown. """ if key == "IMPORTED": # this duplicates info we already see in import_ok & import_problem @@ -1187,7 +1202,7 @@ class Verify(object): def _handle_status(self, key, value): """Parse a status code from the attached GnuPG process. - :raises: :exc:`ValueError` if the status message is unknown. + :raises: :exc:`~exceptions.ValueError` if the status message is unknown. """ if key in self.TRUST_LEVELS: self.trust_text = key @@ -1282,7 +1297,7 @@ class Crypt(Verify): def _handle_status(self, key, value): """Parse a status code from the attached GnuPG process. - :raises: :exc:`ValueError` if the status message is unknown. + :raises: :exc:`~exceptions.ValueError` if the status message is unknown. """ if key in ("ENC_TO", "USERID_HINT", "GOODMDC", "END_DECRYPTION", "BEGIN_SIGNING", "NO_SECKEY", "ERROR", "NODATA", @@ -1349,7 +1364,7 @@ class ListPackets(object): def _handle_status(self, key, value): """Parse a status code from the attached GnuPG process. - :raises: :exc:`ValueError` if the status message is unknown. + :raises: :exc:`~exceptions.ValueError` if the status message is unknown. """ if key == 'NODATA': self.status = nodata(value) diff --git a/gnupg/_trust.py b/gnupg/_trust.py index 30f7439..62d8fc6 100644 --- a/gnupg/_trust.py +++ b/gnupg/_trust.py @@ -7,19 +7,17 @@ # © 2008-2012 Vinay Sajip # © 2005 Steve Traugott # © 2004 A.M. Kuchling -# +# # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) # any later version. -# +# # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details. -'''trust.py ------------ -Functions for handling trustdb and trust calculations. +'''Functions for handling trustdb and trust calculations. The functions within this module take an instance of :class:`gnupg.GPGBase` or a suitable subclass as their first argument. @@ -44,11 +42,12 @@ def _create_trustdb(cls): def export_ownertrust(cls, trustdb=None): """Export ownertrust to a trustdb file. - If there is already a file named 'trustdb.gpg' in the current GnuPG - homedir, it will be renamed to 'trustdb.gpg.bak'. + If there is already a file named :file:`trustdb.gpg` in the current GnuPG + homedir, it will be renamed to :file:`trustdb.gpg.bak`. :param string trustdb: The path to the trustdb.gpg file. If not given, - defaults to 'trustdb.gpg' in the current GnuPG homedir. + defaults to ``'trustdb.gpg'`` in the current GnuPG + homedir. """ if trustdb is None: trustdb = os.path.join(cls.homedir, 'trustdb.gpg') @@ -65,8 +64,9 @@ def export_ownertrust(cls, trustdb=None): def import_ownertrust(self, trustdb=None): """Import ownertrust from a trustdb file. - :param string trustdb: The path to the trustdb.gpg file. If not given, - defaults to 'trustdb.gpg' in the current GnuPG homedir. + :param str trustdb: The path to the trustdb.gpg file. If not given, + defaults to :file:`trustdb.gpg` in the current GnuPG + homedir. """ if trustdb is None: trustdb = os.path.join(cls.homedir, 'trustdb.gpg') @@ -78,22 +78,23 @@ def import_ownertrust(self, trustdb=None): def fix_trustdb(cls, trustdb=None): """Attempt to repair a broken trustdb.gpg file. - GnuPG>=2.0.x has this magical-seeming flag: '--fix-trustdb'. You'd think + GnuPG>=2.0.x has this magical-seeming flag: `--fix-trustdb`. You'd think it would fix the the trustdb. Hah! It doesn't. Here's what it does - instead: + instead:: - (python-gnupg)∃!isisⒶwintermute:(testing/digest-algo *$=)~/code/python-gnupg ∴ gpg2 --fix-trustdb - gpg: You may try to re-create the trustdb using the commands: - gpg: cd ~/.gnupg - gpg: gpg2 --export-ownertrust > otrust.tmp - gpg: rm trustdb.gpg - gpg: gpg2 --import-ownertrust < otrust.tmp - gpg: If that does not work, please consult the manual + (gpg)~/code/python-gnupg $ gpg2 --fix-trustdb + gpg: You may try to re-create the trustdb using the commands: + gpg: cd ~/.gnupg + gpg: gpg2 --export-ownertrust > otrust.tmp + gpg: rm trustdb.gpg + gpg: gpg2 --import-ownertrust < otrust.tmp + gpg: If that does not work, please consult the manual Brilliant piece of software engineering right there. - :param string trustdb: The path to the trustdb.gpg file. If not given, - defaults to 'trustdb.gpg' in the current GnuPG homedir. + :param str trustdb: The path to the trustdb.gpg file. If not given, + defaults to :file:`trustdb.gpg` in the current GnuPG + homedir. """ if trustdb is None: trustdb = os.path.join(cls.homedir, 'trustdb.gpg') diff --git a/gnupg/_util.py b/gnupg/_util.py index f5b7ff3..179ecbf 100644 --- a/gnupg/_util.py +++ b/gnupg/_util.py @@ -17,10 +17,7 @@ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details. -'''util.py ----------- -Extra utilities for python-gnupg. -''' +'''Extra utilities for python-gnupg.''' from __future__ import absolute_import from datetime import datetime @@ -30,6 +27,7 @@ from time import mktime import codecs import encodings +import exceptions import os import psutil import threading @@ -255,7 +253,8 @@ def _find_binary(binary=None): our process real uid has exec permissions. :param str binary: The path to the GnuPG binary. - :raises: :exc:RuntimeError if it appears that GnuPG is not installed. + :raises: :exc:`~exceptions.RuntimeError` if it appears that GnuPG is not + installed. :rtype: str :returns: The absolute path to the GnuPG binary to use, if no exceptions occur. @@ -304,22 +303,31 @@ def _has_readwrite(path): """ return os.access(path, os.R_OK ^ os.W_OK) -def _is_file(input): +def _is_file(filename): """Check that the size of the thing which is supposed to be a filename has size greater than zero, without following symbolic links or using :func:os.path.isfile. - :param input: An object to check. + :param filename: An object to check. :rtype: bool - :returns: True if :param:input is file-like, False otherwise. + :returns: True if **filename** is file-like, False otherwise. """ try: - assert os.lstat(input).st_size > 0, "not a file: %s" % input - except (AssertionError, TypeError, IOError, OSError) as err: - log.error(err.message, exc_info=1) - return False + statinfo = os.lstat(filename) + log.debug("lstat(%r) with type=%s gave us %r" + % (repr(filename), type(filename), repr(statinfo))) + if not (statinfo.st_size > 0): + raise ValueError("'%s' appears to be an empty file!" % filename) + except OSError as oserr: + log.error(oserr) + if filename == '-': + log.debug("Got '-' for filename, assuming sys.stdin...") + return True + except (ValueError, TypeError, IOError) as err: + log.error(err) else: return True + return False def _is_stream(input): """Check that the input is a byte stream. @@ -519,7 +527,7 @@ def _which(executable, flags=os.X_OK): def _write_passphrase(stream, passphrase, encoding): """Write the passphrase from memory to the GnuPG process' stdin. - :type stream: file, :class:BytesIO, or :class:StringIO + :type stream: file, :class:`~io.BytesIO`, or :class:`~io.StringIO` :param stream: The input file descriptor to write the password to. :param str passphrase: The passphrase for the secret key material. :param str encoding: The data encoding expected by GnuPG. Usually, this diff --git a/gnupg/gnupg.py b/gnupg/gnupg.py index 7d6fc30..3053d17 100644 --- a/gnupg/gnupg.py +++ b/gnupg/gnupg.py @@ -7,12 +7,12 @@ # © 2008-2012 Vinay Sajip # © 2005 Steve Traugott # © 2004 A.M. Kuchling -# +# # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) # any later version. -# +# # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details. @@ -21,16 +21,17 @@ =========== A Python interface to GnuPG. -.. moduleauthor:: Isis Agora Lovecruft +.. moduleauthor:: Isis Lovecruft see also :attr:`gnupg.__authors__` -:license: see :attr:`gnupg.__license__` -:info: see +.. license:: see :attr:`gnupg.__license__` +.. info:: https://github.com/isislovecruft/python-gnupg """ from __future__ import absolute_import from codecs import open as open import encodings +import exceptions import functools import os import re @@ -41,7 +42,7 @@ try: except ImportError: from cStringIO import StringIO -## see PEP-328 http://docs.python.org/2.5/whatsnew/pep-328.html +#: see :pep:`328` http://docs.python.org/2.5/whatsnew/pep-328.html from . import _parsers from . import _util from . import _trust @@ -70,35 +71,36 @@ class GPG(GPGBase): """Initialize a GnuPG process wrapper. :param str binary: Name for GnuPG binary executable. If the absolute - path is not given, the evironment variable $PATH is - searched for the executable and checked that the - real uid/gid of the user has sufficient permissions. + path is not given, the environment variable + ``$PATH`` is searched for the executable and + checked that the real uid/gid of the user has + sufficient permissions. :param str homedir: Full pathname to directory containing the public and private keyrings. Default is whatever GnuPG defaults to. - :param str,int,bool verbose: String or numeric value to pass to gpg's - ``--debug-level`` option. See the gpg man - page for the list of valid options. If - False, debug output is not generated by - the gpg binary. If True, defaults to - ``--debug-level basic.`` + :type verbose: :obj:`str` or :obj:`int` or :obj:`bool` + :param verbose: String or numeric value to pass to GnuPG's + ``--debug-level`` option. See the GnuPG man page for + the list of valid options. If False, debug output is + not generated by the GnuPG binary. If True, defaults + to ``--debug-level basic.`` - :param str keyring: Name of keyring file containing public key data, if - unspecified, defaults to 'pubring.gpg' in the - ``homedir`` directory. + :param str keyring: Name of keyring file containing public key data. + If unspecified, defaults to :file:`pubring.gpg` in + the **homedir** directory. :param str secring: Name of alternative secret keyring file to use. If left unspecified, this will default to using - 'secring.gpg' in the :param:homedir directory, and - create that file if it does not exist. + :file:`secring.gpg` in the **homedir** directory, + and create that file if it does not exist. - :param list options: A list of additional options to pass to the GPG + :param list options: A list of additional options to pass to the GnuPG binary. - :raises: :exc:`RuntimeError` with explanation message if there is a - problem invoking gpg. + :raises: A :exc:`~exceptions.RuntimeError` with explanation message + if there is a problem invoking GnuPG. Example: @@ -227,7 +229,7 @@ class GPG(GPGBase): In simpler terms: this function isn't for signing your friends' keys, it's for something like signing an email. - :type data: str or file + :type data: :obj:`str` or :obj:`file` :param data: A string or file stream to sign. :param str default_key: The key to sign with. :param str passphrase: The passphrase to pipe to stdin. @@ -236,7 +238,7 @@ class GPG(GPGBase): :param bool binary: If True, do not ascii armour the output. :param str digest_algo: The hash digest to use. Again, to see which hashes your GnuPG is capable of using, do: - ``$ gpg --with-colons --list-config digestname``. + :command:`$ gpg --with-colons --list-config digestname`. The default, if unspecified, is ``'SHA512'``. """ if 'default_key' in kwargs.items(): @@ -306,15 +308,19 @@ class GPG(GPGBase): return result log.debug('verify_file(): Handling detached verification') sig_fh = None + data_fh = None try: - sig_fh = open(sig_file) + sig_fh = open(sig_file, 'rb') + data_fh = open(file, 'rb') args = ["--verify %s -" % sig_fh.name] proc = self._open_subprocess(args) - writer = _util._threaded_copy_data(file, proc.stdin) - self._collect_output(proc, result, stdin=proc.stdin) + writer = _util._threaded_copy_data(data_fh, proc.stdin) + self._collect_output(proc, result, writer, stdin=proc.stdin) finally: if sig_fh and not sig_fh.closed: sig_fh.close() + if data_fh and not data_fh.closed: + data_fh.close() return result def import_keys(self, key_data): @@ -373,7 +379,7 @@ class GPG(GPGBase): :param str keyids: Each ``keyids`` argument should be a string containing a keyid to request. :param str keyserver: The keyserver to request the ``keyids`` from; - defaults to :property:`gnupg.GPG.keyserver`. + defaults to `gnupg.GPG.keyserver`. """ if keyids: keys = ' '.join([key for key in keyids]) @@ -384,27 +390,27 @@ class GPG(GPGBase): def delete_keys(self, fingerprints, secret=False, subkeys=False): """Delete a key, or list of keys, from the current keyring. - The keys must be refered to by their full fingerprint for GnuPG to + The keys must be referred to by their full fingerprints for GnuPG to delete them. If ``secret=True``, the corresponding secret keyring will - be deleted from :attr:`GPG.secring`. - - :type fingerprints: str or list or tuple + be deleted from :obj:`.secring`. + :type fingerprints: :obj:`str` or :obj:`list` or :obj:`tuple` :param fingerprints: A string, or a list/tuple of strings, - representing the fingerprint(s) for the key(s) to delete. + representing the fingerprint(s) for the key(s) + to delete. :param bool secret: If True, delete the corresponding secret key(s) - also. (default: False) + also. (default: False) :param bool subkeys: If True, delete the secret subkey first, then the - public key. (default: False) Same as: - ``$ gpg --delete-secret-and-public-key 0x12345678`` + public key. (default: False) Same as: + :command:`$gpg --delete-secret-and-public-key 0x12345678`. """ - which='keys' + which = 'keys' if secret: - which='secret-keys' + which = 'secret-keys' if subkeys: - which='secret-and-public-keys' + which = 'secret-and-public-keys' if _is_list_or_tuple(fingerprints): fingerprints = ' '.join(fingerprints) @@ -425,11 +431,11 @@ class GPG(GPGBase): :param bool secret: If True, export only the secret key. :param bool subkeys: If True, export the secret subkeys. """ - which='' + which = '' if subkeys: - which='-secret-subkeys' + which = '-secret-subkeys' elif secret: - which='-secret-keys' + which = '-secret-keys' if _is_list_or_tuple(keyids): keyids = ' '.join(['%s' % k for k in keyids]) @@ -441,7 +447,7 @@ class GPG(GPGBase): ## gpg --export produces no status-fd output; stdout will be empty in ## case of failure #stdout, stderr = p.communicate() - result = self._result_map['delete'](self) # any result will do + result = self._result_map['delete'](self) # any result will do self._collect_output(p, result, stdin=p.stdin) log.debug('Exported:%s%r' % (os.linesep, result.data)) return result.data.decode(self._encoding, self._decode_errors) @@ -467,9 +473,9 @@ class GPG(GPGBase): >>> assert print2 in pubkeys.fingerprints """ - which='public-keys' + which = 'public-keys' if secret: - which='secret-keys' + which = 'secret-keys' args = "--list-%s --fixed-list-mode --fingerprint " % (which,) args += "--with-colons --list-options no-show-photos" args = [args] @@ -547,7 +553,7 @@ class GPG(GPGBase): :param dict input: A dictionary of parameters and values for the new key. :returns: The result mapping with details of the new key, which is a - :class:`parsers.GenKey ` object. + :class:`GenKey ` object. """ args = ["--gen-key --batch"] key = self._result_map['generate'](self) @@ -584,11 +590,11 @@ class GPG(GPGBase): def gen_key_input(self, separate_keyring=False, save_batchfile=False, testing=False, **kwargs): - """Generate a batch file for input to :meth:`GPG.gen_key()`. + """Generate a batch file for input to :meth:`~gnupg.GPG.gen_key`. The GnuPG batch file key generation feature allows unattended key generation by creating a file with special syntax and then providing it - to: ``gpg --gen-key --batch``. Batch files look like this: + to: :command:`gpg --gen-key --batch`. Batch files look like this: | Name-Real: Alice | Name-Email: alice@inter.net @@ -647,9 +653,10 @@ class GPG(GPGBase): :param bool separate_keyring: Specify for the new key to be written to a separate pubring.gpg and secring.gpg. If True, - :meth:`GPG.gen_key` will automatically rename the separate keyring - and secring to whatever the fingerprint of the generated key ends - up being, suffixed with '.pubring' and '.secring' respectively. + :meth:`~gnupg.GPG.gen_key` will automatically rename the separate + keyring and secring to whatever the fingerprint of the generated + key ends up being, suffixed with '.pubring' and '.secring' + respectively. :param bool save_batchfile: Save a copy of the generated batch file to disk in a file named .batch, where is the @@ -663,11 +670,12 @@ class GPG(GPGBase): :param str name_real: The name field of the UID in the generated key. :param str name_comment: The comment in the UID of the generated key. + :param str name_email: The email in the UID of the generated key. - (default: $USER@$(hostname) ) Remember to use UTF-8 encoding for - the entirety of the UID. At least one of ``name_real``, - ``name_comment``, or ``name_email`` must be provided, or else no - user ID is created. + (default: ``$USER`` @ :command:`hostname` ) Remember to use UTF-8 + encoding for the entirety of the UID. At least one of + ``name_real``, ``name_comment``, or ``name_email`` must be + provided, or else no user ID is created. :param str key_type: One of 'RSA', 'DSA', 'ELG-E', or 'default'. (default: 'RSA', if using GnuPG v1.x, otherwise 'default') Starts @@ -706,7 +714,7 @@ class GPG(GPGBase): :param str subkey_usage: Key usage for a subkey; similar to ``key_usage``. - :type expire_date: int or str + :type expire_date: :obj:`int` or :obj:`str` :param expire_date: Can be specified as an iso-date or as [d|w|m|y] Set the expiration date for the key (and the subkey). It may either be entered in ISO date format (2000-08-15) @@ -728,17 +736,17 @@ class GPG(GPGBase): :param str passphrase: The passphrase for the new key. The default is to not use any passphrase. Note that GnuPG>=2.1.x will not allow you to specify a passphrase for batch key generation -- GnuPG will - ignore the ``passphrase`` parameter, stop, and ask the user for + ignore the **passphrase** parameter, stop, and ask the user for the new passphrase. However, we can put the command - '%no-protection' into the batch key generation file to allow a + ``%no-protection`` into the batch key generation file to allow a passwordless key to be created, which can then have its passphrase - set later with '--edit-key'. + set later with ``--edit-key``. :param str preferences: Set the cipher, hash, and compression preference values for this key. This expects the same type of string as the sub-command ‘setpref’ in the --edit-key menu. - :param str revoker: Should be given as 'algo:fpr' [case sensitive]. + :param str revoker: Should be given as 'algo:fpr' (case sensitive). Add a designated revoker to the generated key. Algo is the public key algorithm of the designated revoker (i.e. RSA=1, DSA=17, etc.) fpr is the fingerprint of the designated revoker. The optional @@ -749,18 +757,18 @@ class GPG(GPGBase): preferred keyserver URL for the key. :param str handle: This is an optional parameter only used with the - status lines KEY_CREATED and KEY_NOT_CREATED. string may be up to - 100 characters and should not contain spaces. It is useful for - batch key generation to associate a key parameter block with a - status line. + status lines ``KEY_CREATED`` and ``KEY_NOT_CREATED``. string may + be up to 100 characters and should not contain spaces. It is + useful for batch key generation to associate a key parameter block + with a status line. :rtype: str :returns: A suitable input string for the :meth:`GPG.gen_key` method, the latter of which will create the new keypair. - see - http://www.gnupg.org/documentation/manuals/gnupg-devel/Unattended-GPG-key-generation.html - for more details. + See `this GnuPG Manual section`__ for more details. + + __ http://www.gnupg.org/documentation/manuals/gnupg-devel/Unattended-GPG-key-generation.html """ #: A boolean for determining whether to set subkey_type to 'default' default_type = False @@ -933,20 +941,21 @@ generate keys. Please see >>> decrypted 'The crow flies at midnight.' + :param str cipher_algo: The cipher algorithm to use. To see available algorithms with your version of GnuPG, do: - ``$ gpg --with-colons --list-config ciphername``. + :command:`$ gpg --with-colons --list-config ciphername`. The default ``cipher_algo``, if unspecified, is ``'AES256'``. :param str digest_algo: The hash digest to use. Again, to see which hashes your GnuPG is capable of using, do: - ``$ gpg --with-colons --list-config digestname``. + :command:`$ gpg --with-colons --list-config digestname`. The default, if unspecified, is ``'SHA512'``. :param str compress_algo: The compression algorithm to use. Can be one of ``'ZLIB'``, ``'BZIP2'``, ``'ZIP'``, or ``'Uncompressed'``. - See also: :meth:`GPGBase._encrypt` + .. seealso:: :meth:`._encrypt` """ stream = _make_binary_stream(data, self._encoding) result = self._encrypt(stream, recipients, **kwargs) diff --git a/gnupg/test/test_gnupg.py b/gnupg/test/test_gnupg.py index e223dbc..1f3b37a 100644 --- a/gnupg/test/test_gnupg.py +++ b/gnupg/test/test_gnupg.py @@ -672,44 +672,72 @@ class GPGTestCase(unittest.TestCase): def test_signature_verification_detached(self): """Test that verification of a detached signature of a file works.""" + key = self.generate_key("Paulo S.L.M. Barreto", "anub.is") - with open(os.path.join(_files, 'cypherpunk_manifesto'), 'rb') as cm: - sig = self.gpg.sign(cm, default_key=key.fingerprint, - passphrase='paulos.l.m.barreto', - detach=True, clearsign=False) - self.assertTrue(sig.data, "File signing should succeed") - sigfilename = os.path.join(_files, 'cypherpunk_manifesto.sig') - with open(sigfilename,'w') as sigfile: - sigfile.write(sig.data) - sigfile.seek(0) + datafn = os.path.join(_files, 'cypherpunk_manifesto') + sigfn = os.path.extsep.join([datafn, 'sig']) - verified = self.gpg.verify_file(cm, sigfilename) + datafd = open(datafn, 'rb') + sig = self.gpg.sign(datafd, default_key=key.fingerprint, + passphrase='paulos.l.m.barreto', + detach=True, + clearsign=False) - if key.fingerprint != verified.fingerprint: - log.warn("key fingerprint: %r", key.fingerprint) - log.warn("verified fingerprint: %r", verified.fingerprint) - self.assertEqual(key.fingerprint, verified.fingerprint) + self.assertTrue(sig.data, "File signing should succeed") - if os.path.isfile(sigfilename): - os.unlink(sigfilename) + sigfd = open(sigfn, 'w') + sigfd.write(sig.data) + sigfd.flush() + + datafd.seek(0) + sigfd.seek(0) + + verified = self.gpg.verify_file(datafn, sigfn) + + if key.fingerprint != verified.fingerprint: + log.warn("key fingerprint: %r", key.fingerprint) + log.warn("verified fingerprint: %r", verified.fingerprint) + self.assertEqual(key.fingerprint, verified.fingerprint) + + if os.path.isfile(sigfn): + os.unlink(sigfn) def test_signature_verification_detached_binary(self): """Test that detached signature verification in binary mode fails.""" + key = self.generate_key("Adi Shamir", "rsa.com") - datafile = os.path.join(_files, 'cypherpunk_manifesto') - with open(datafile, 'rb') as cm: - sig = self.gpg.sign(cm, default_key=key.fingerprint, - passphrase='adishamir', - detach=True, binary=True, clearsign=False) - self.assertTrue(sig.data, "File signing should succeed") - with open(datafile+'.sig', 'w') as bs: - bs.write(sig.data) - bs.flush() - with self.assertRaises(UnicodeDecodeError): - print("SIG=%s" % sig) - with open(datafile+'.sig', 'rb') as fsig: - with open(datafile, 'rb') as fdata: - self.gpg.verify_file(fdata, fsig) + datafn = os.path.join(_files, 'cypherpunk_manifesto') + sigfn = os.path.extsep.join([datafn, 'sig']) + + datafd = open(datafn, 'rb') + data = datafd.read() + datafd.close() + + sig = self.gpg.sign(data, default_key=key.fingerprint, + passphrase='adishamir', + detach=True, + binary=True, + clearsign=False) + + self.assertTrue(sig.data, "File signing should succeed") + + sigfd = open(sigfn, 'wb') + sigfd.write(sig.data) + sigfd.flush() + sigfd.close() + + self.assertTrue(sigfd.closed, "Sigfile '%s' should be closed" % sigfn) + with self.assertRaises(UnicodeDecodeError): + print("SIG=%s" % sig) + + verifysig = open(sigfn, 'rb') + verification = self.gpg.verify_file(data, verifysig) + + self.assertTrue(isinstance(verification, gnupg._parsers.Verify)) + self.assertFalse(verification.valid) + + if os.path.isfile(sigfn): + os.unlink(sigfn) def test_deletion(self): """Test that key deletion works.""" diff --git a/setup.cfg b/setup.cfg index fb18565..9103812 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,3 +7,6 @@ verbose = true sign = True identity = 0xa3adb67a2cdb8b35 +[aliases] +upload_all = sdist bdist_egg bdist_wheel upload + diff --git a/setup.py b/setup.py index 3b4154d..a4cd216 100644 --- a/setup.py +++ b/setup.py @@ -23,6 +23,7 @@ from __future__ import absolute_import from __future__ import print_function import setuptools +import sys import os import versioneer @@ -37,6 +38,12 @@ __contact__ = 'isis@patternsinthevoid.net' __url__ = 'https://github.com/isislovecruft/python-gnupg' +def python26(): + """Returns True if we're running on Python2.6.""" + if sys.version[:3] == "2.6": + return True + return False + def get_requirements(): """Extract the list of requirements from our requirements.txt. @@ -64,15 +71,14 @@ def get_requirements(): except (IOError, OSError) as error: print(error) + if python26(): + # Required to make `collections.OrderedDict` available on Python<=2.6 + requirements.append('ordereddict==1.1#a0ed854ee442051b249bfad0f638bbec') + return requirements, links requires, deplinks = get_requirements() -print('Found requirements:') -[print('\t%s' % name) for name in requires] - -print('Found dependency links:') -[print('\t%s' % uri) for uri in deplinks] setuptools.setup( @@ -105,7 +111,8 @@ greater. install_requires=requires, dependency_links=deplinks, - extras_require={'docs': ["Sphinx>=1.1", "repoze.sphinx"]}, + extras_require={'docs': ["Sphinx>=1.1", + "sphinxcontrib-fulltoc==1.0"]}, platforms="Linux, BSD, OSX, Windows", download_url="https://github.com/isislovecruft/python-gnupg/archive/master.zip",