diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5cb6b75b..c96933f0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,13 +11,19 @@ jobs: formatting: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Setup black linter - run: conda create --quiet --name black black + run: conda create --quiet --name black black pyflakes - - name: Lint python code + - name: Lint python code with black run: | export PATH="/usr/share/miniconda/bin:$PATH" source activate black black --check spython + + - name: Check unused imports with pyflakes + run: | + export PATH="/usr/share/miniconda/bin:$PATH" + source activate black + pyflakes spython/oci spython/image spython/instance spython/main diff --git a/CHANGELOG.md b/CHANGELOG.md index 1041d84d..91fbad0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,10 @@ The client here will eventually be released as "spython" (and eventually to singularity on pypi), and the versions here will coincide with these releases. ## [master](https://github.com/singularityhub/singularity-cli/tree/master) + - Small bugfix for docker writer and adding pyflakes for unused imports (0.0.84) + - Adding support for multistage build parsing (0.0.83) + - Singularity Python does not yet support multistage builds (0.0.82) + - stream command should print to stdout given CalledProcessError (0.0.81) - USER regular expression should check for USER at start of line (0.0.80) - add singularity options parameters to send to singularity (0.0.79) - add support for library:// urls (0.0.78) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9a3bcaf0..654af22a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,8 +14,7 @@ all your interactions with the project members and users. ## Pull Request Process -1. Send PRs to the master branch for merge as a pypi release, or development - if the intention is to add to next pypi release. +1. Send PRs to the master branch. 2. Follow the existing code style precedent. This does not need to be strictly defined as there are many thousands of lines of examples. Note the lack of tabs anywhere in the project, parentheses and spacing, documentation diff --git a/docs/api/_sources/changelog.md.txt b/docs/api/_sources/changelog.md.txt index 1c29c8fa..2d073538 100644 --- a/docs/api/_sources/changelog.md.txt +++ b/docs/api/_sources/changelog.md.txt @@ -17,6 +17,11 @@ The client here will eventually be released as "spython" (and eventually to singularity on pypi), and the versions here will coincide with these releases. ## [master](https://github.com/singularityhub/singularity-cli/tree/master) + - Singularity does not support multistage builds (0.0.82) + - stream command should print to stdout given CalledProcessError (0.0.81) + - USER regular expression should check for USER at start of line (0.0.80) + - add singularity options parameters to send to singularity (0.0.79) + - add support for library:// urls (0.0.78) - instance stop with timeout argument (0.0.77) - instance list includes ip address (0.0.76) - export lines aren't ignored from environment, but replaced (0.0.75) diff --git a/docs/api/assets/basic.css b/docs/api/assets/basic.css index ea6972d5..01192852 100644 --- a/docs/api/assets/basic.css +++ b/docs/api/assets/basic.css @@ -4,7 +4,7 @@ * * Sphinx stylesheet -- basic theme. * - * :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @@ -672,6 +672,10 @@ div.code-block-caption + div > div.highlight > pre { margin-top: 0; } +div.doctest > div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; +} + div.code-block-caption span.caption-number { padding: 0.1em 0.3em; font-style: italic; diff --git a/docs/api/assets/doctools.js b/docs/api/assets/doctools.js index b33f87fc..daccd209 100644 --- a/docs/api/assets/doctools.js +++ b/docs/api/assets/doctools.js @@ -4,7 +4,7 @@ * * Sphinx JavaScript utilities for all documentation. * - * :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @@ -283,10 +283,11 @@ var Documentation = { }, initOnKeyListeners: function() { - $(document).keyup(function(event) { + $(document).keydown(function(event) { var activeElementType = document.activeElement.tagName; // don't navigate when in search box or textarea - if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT') { + if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT' + && !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey) { switch (event.keyCode) { case 37: // left var prevHref = $('link[rel="prev"]').prop('href'); diff --git a/docs/api/assets/documentation_options.js b/docs/api/assets/documentation_options.js index d0b73042..deb6527a 100644 --- a/docs/api/assets/documentation_options.js +++ b/docs/api/assets/documentation_options.js @@ -3,7 +3,9 @@ var DOCUMENTATION_OPTIONS = { VERSION: '1', LANGUAGE: 'None', COLLAPSE_INDEX: false, + BUILDER: 'html', FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', HAS_SOURCE: true, SOURCELINK_SUFFIX: '.txt', NAVIGATION_WITH_KEYS: false diff --git a/docs/api/assets/jquery-3.4.1.js b/docs/api/assets/jquery-3.5.1.js similarity index 91% rename from docs/api/assets/jquery-3.4.1.js rename to docs/api/assets/jquery-3.5.1.js index 773ad95c..50937333 100644 --- a/docs/api/assets/jquery-3.4.1.js +++ b/docs/api/assets/jquery-3.5.1.js @@ -1,5 +1,5 @@ /*! - * jQuery JavaScript Library v3.4.1 + * jQuery JavaScript Library v3.5.1 * https://jquery.com/ * * Includes Sizzle.js @@ -9,7 +9,7 @@ * Released under the MIT license * https://jquery.org/license * - * Date: 2019-05-01T21:04Z + * Date: 2020-05-04T22:49Z */ ( function( global, factory ) { @@ -47,13 +47,16 @@ var arr = []; -var document = window.document; - var getProto = Object.getPrototypeOf; var slice = arr.slice; -var concat = arr.concat; +var flat = arr.flat ? function( array ) { + return arr.flat.call( array ); +} : function( array ) { + return arr.concat.apply( [], array ); +}; + var push = arr.push; @@ -86,6 +89,8 @@ var isWindow = function isWindow( obj ) { }; +var document = window.document; + var preservedScriptAttributes = { @@ -142,7 +147,7 @@ function toType( obj ) { var - version = "3.4.1", + version = "3.5.1", // Define a local copy of jQuery jQuery = function( selector, context ) { @@ -150,11 +155,7 @@ var // The jQuery object is actually just the init constructor 'enhanced' // Need init if jQuery is called (just allow error to be thrown if not included) return new jQuery.fn.init( selector, context ); - }, - - // Support: Android <=4.0 only - // Make sure we trim BOM and NBSP - rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g; + }; jQuery.fn = jQuery.prototype = { @@ -220,6 +221,18 @@ jQuery.fn = jQuery.prototype = { return this.eq( -1 ); }, + even: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return ( i + 1 ) % 2; + } ) ); + }, + + odd: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return i % 2; + } ) ); + }, + eq: function( i ) { var len = this.length, j = +i + ( i < 0 ? len : 0 ); @@ -353,9 +366,10 @@ jQuery.extend( { return true; }, - // Evaluates a script in a global context - globalEval: function( code, options ) { - DOMEval( code, { nonce: options && options.nonce } ); + // Evaluates a script in a provided context; falls back to the global one + // if not specified. + globalEval: function( code, options, doc ) { + DOMEval( code, { nonce: options && options.nonce }, doc ); }, each: function( obj, callback ) { @@ -379,13 +393,6 @@ jQuery.extend( { return obj; }, - // Support: Android <=4.0 only - trim: function( text ) { - return text == null ? - "" : - ( text + "" ).replace( rtrim, "" ); - }, - // results is for internal usage only makeArray: function( arr, results ) { var ret = results || []; @@ -472,7 +479,7 @@ jQuery.extend( { } // Flatten any nested arrays - return concat.apply( [], ret ); + return flat( ret ); }, // A global GUID counter for objects @@ -489,7 +496,7 @@ if ( typeof Symbol === "function" ) { // Populate the class2type map jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), -function( i, name ) { +function( _i, name ) { class2type[ "[object " + name + "]" ] = name.toLowerCase(); } ); @@ -511,17 +518,16 @@ function isArrayLike( obj ) { } var Sizzle = /*! - * Sizzle CSS Selector Engine v2.3.4 + * Sizzle CSS Selector Engine v2.3.5 * https://sizzlejs.com/ * * Copyright JS Foundation and other contributors * Released under the MIT license * https://js.foundation/ * - * Date: 2019-04-08 + * Date: 2020-03-14 */ -(function( window ) { - +( function( window ) { var i, support, Expr, @@ -561,59 +567,70 @@ var i, }, // Instance methods - hasOwn = ({}).hasOwnProperty, + hasOwn = ( {} ).hasOwnProperty, arr = [], pop = arr.pop, - push_native = arr.push, + pushNative = arr.push, push = arr.push, slice = arr.slice, + // Use a stripped-down indexOf as it's faster than native // https://jsperf.com/thor-indexof-vs-for/5 indexOf = function( list, elem ) { var i = 0, len = list.length; for ( ; i < len; i++ ) { - if ( list[i] === elem ) { + if ( list[ i ] === elem ) { return i; } } return -1; }, - booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + + "ismap|loop|multiple|open|readonly|required|scoped", // Regular expressions // http://www.w3.org/TR/css3-selectors/#whitespace whitespace = "[\\x20\\t\\r\\n\\f]", - // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier - identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+", + // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram + identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + // Operator (capture 2) "*([*^$|!~]?=)" + whitespace + - // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" - "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + - "*\\]", + + // "Attribute values must be CSS identifiers [capture 5] + // or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + + whitespace + "*\\]", pseudos = ":(" + identifier + ")(?:\\((" + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: // 1. quoted (capture 3; capture 4 or capture 5) "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + // 2. simple (capture 6) "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + // 3. anything else (capture 2) ".*" + ")\\)|)", // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter rwhitespace = new RegExp( whitespace + "+", "g" ), - rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + + whitespace + "+$", "g" ), rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), - rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + + "*" ), rdescend = new RegExp( whitespace + "|>" ), rpseudo = new RegExp( pseudos ), @@ -625,14 +642,16 @@ var i, "TAG": new RegExp( "^(" + identifier + "|[*])" ), "ATTR": new RegExp( "^" + attributes ), "PSEUDO": new RegExp( "^" + pseudos ), - "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + - "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + - "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + // For use in libraries implementing .is() // We use this for POS matching in `select` - "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + - whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + "needsContext": new RegExp( "^" + whitespace + + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) }, rhtml = /HTML$/i, @@ -648,18 +667,21 @@ var i, // CSS escapes // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), - funescape = function( _, escaped, escapedWhitespace ) { - var high = "0x" + escaped - 0x10000; - // NaN means non-codepoint - // Support: Firefox<24 - // Workaround erroneous numeric interpretation of +"0x" - return high !== high || escapedWhitespace ? - escaped : + runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), + funescape = function( escape, nonHex ) { + var high = "0x" + escape.slice( 1 ) - 0x10000; + + return nonHex ? + + // Strip the backslash prefix from a non-hex escape sequence + nonHex : + + // Replace a hexadecimal escape sequence with the encoded Unicode code point + // Support: IE <=11+ + // For values outside the Basic Multilingual Plane (BMP), manually construct a + // surrogate pair high < 0 ? - // BMP codepoint String.fromCharCode( high + 0x10000 ) : - // Supplemental Plane codepoint (surrogate pair) String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); }, @@ -675,7 +697,8 @@ var i, } // Control characters and (dependent upon position) numbers get escaped as code points - return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + return ch.slice( 0, -1 ) + "\\" + + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; } // Other potentially-special ASCII characters get backslash-escaped @@ -700,18 +723,20 @@ var i, // Optimize for push.apply( _, NodeList ) try { push.apply( - (arr = slice.call( preferredDoc.childNodes )), + ( arr = slice.call( preferredDoc.childNodes ) ), preferredDoc.childNodes ); + // Support: Android<4.0 // Detect silently failing push.apply + // eslint-disable-next-line no-unused-expressions arr[ preferredDoc.childNodes.length ].nodeType; } catch ( e ) { push = { apply: arr.length ? // Leverage slice if possible function( target, els ) { - push_native.apply( target, slice.call(els) ); + pushNative.apply( target, slice.call( els ) ); } : // Support: IE<9 @@ -719,8 +744,9 @@ try { function( target, els ) { var j = target.length, i = 0; + // Can't trust NodeList.length - while ( (target[j++] = els[i++]) ) {} + while ( ( target[ j++ ] = els[ i++ ] ) ) {} target.length = j - 1; } }; @@ -744,24 +770,21 @@ function Sizzle( selector, context, results, seed ) { // Try to shortcut find operations (as opposed to filters) in HTML documents if ( !seed ) { - - if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { - setDocument( context ); - } + setDocument( context ); context = context || document; if ( documentIsHTML ) { // If the selector is sufficiently simple, try using a "get*By*" DOM method // (excepting DocumentFragment context, where the methods don't exist) - if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { + if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { // ID selector - if ( (m = match[1]) ) { + if ( ( m = match[ 1 ] ) ) { // Document context if ( nodeType === 9 ) { - if ( (elem = context.getElementById( m )) ) { + if ( ( elem = context.getElementById( m ) ) ) { // Support: IE, Opera, Webkit // TODO: identify versions @@ -780,7 +803,7 @@ function Sizzle( selector, context, results, seed ) { // Support: IE, Opera, Webkit // TODO: identify versions // getElementById can match elements by name instead of ID - if ( newContext && (elem = newContext.getElementById( m )) && + if ( newContext && ( elem = newContext.getElementById( m ) ) && contains( context, elem ) && elem.id === m ) { @@ -790,12 +813,12 @@ function Sizzle( selector, context, results, seed ) { } // Type selector - } else if ( match[2] ) { + } else if ( match[ 2 ] ) { push.apply( results, context.getElementsByTagName( selector ) ); return results; // Class selector - } else if ( (m = match[3]) && support.getElementsByClassName && + } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && context.getElementsByClassName ) { push.apply( results, context.getElementsByClassName( m ) ); @@ -806,11 +829,11 @@ function Sizzle( selector, context, results, seed ) { // Take advantage of querySelectorAll if ( support.qsa && !nonnativeSelectorCache[ selector + " " ] && - (!rbuggyQSA || !rbuggyQSA.test( selector )) && + ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && // Support: IE 8 only // Exclude object elements - (nodeType !== 1 || context.nodeName.toLowerCase() !== "object") ) { + ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { newSelector = selector; newContext = context; @@ -819,27 +842,36 @@ function Sizzle( selector, context, results, seed ) { // descendant combinators, which is not what we want. // In such cases, we work around the behavior by prefixing every selector in the // list with an ID selector referencing the scope context. + // The technique has to be used as well when a leading combinator is used + // as such selectors are not recognized by querySelectorAll. // Thanks to Andrew Dupont for this technique. - if ( nodeType === 1 && rdescend.test( selector ) ) { + if ( nodeType === 1 && + ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { - // Capture the context ID, setting it first if necessary - if ( (nid = context.getAttribute( "id" )) ) { - nid = nid.replace( rcssescape, fcssescape ); - } else { - context.setAttribute( "id", (nid = expando) ); + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + + // We can use :scope instead of the ID hack if the browser + // supports it & if we're not changing the context. + if ( newContext !== context || !support.scope ) { + + // Capture the context ID, setting it first if necessary + if ( ( nid = context.getAttribute( "id" ) ) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", ( nid = expando ) ); + } } // Prefix every selector in the list groups = tokenize( selector ); i = groups.length; while ( i-- ) { - groups[i] = "#" + nid + " " + toSelector( groups[i] ); + groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + + toSelector( groups[ i ] ); } newSelector = groups.join( "," ); - - // Expand context for sibling selectors - newContext = rsibling.test( selector ) && testContext( context.parentNode ) || - context; } try { @@ -872,12 +904,14 @@ function createCache() { var keys = []; function cache( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) if ( keys.push( key + " " ) > Expr.cacheLength ) { + // Only keep the most recent entries delete cache[ keys.shift() ]; } - return (cache[ key + " " ] = value); + return ( cache[ key + " " ] = value ); } return cache; } @@ -896,17 +930,19 @@ function markFunction( fn ) { * @param {Function} fn Passed the created element and returns a boolean result */ function assert( fn ) { - var el = document.createElement("fieldset"); + var el = document.createElement( "fieldset" ); try { return !!fn( el ); - } catch (e) { + } catch ( e ) { return false; } finally { + // Remove from its parent by default if ( el.parentNode ) { el.parentNode.removeChild( el ); } + // release memory in IE el = null; } @@ -918,11 +954,11 @@ function assert( fn ) { * @param {Function} handler The method that will be applied */ function addHandle( attrs, handler ) { - var arr = attrs.split("|"), + var arr = attrs.split( "|" ), i = arr.length; while ( i-- ) { - Expr.attrHandle[ arr[i] ] = handler; + Expr.attrHandle[ arr[ i ] ] = handler; } } @@ -944,7 +980,7 @@ function siblingCheck( a, b ) { // Check if b follows a if ( cur ) { - while ( (cur = cur.nextSibling) ) { + while ( ( cur = cur.nextSibling ) ) { if ( cur === b ) { return -1; } @@ -972,7 +1008,7 @@ function createInputPseudo( type ) { function createButtonPseudo( type ) { return function( elem ) { var name = elem.nodeName.toLowerCase(); - return (name === "input" || name === "button") && elem.type === type; + return ( name === "input" || name === "button" ) && elem.type === type; }; } @@ -1015,7 +1051,7 @@ function createDisabledPseudo( disabled ) { // Where there is no isDisabled, check manually /* jshint -W018 */ elem.isDisabled !== !disabled && - inDisabledFieldset( elem ) === disabled; + inDisabledFieldset( elem ) === disabled; } return elem.disabled === disabled; @@ -1037,21 +1073,21 @@ function createDisabledPseudo( disabled ) { * @param {Function} fn */ function createPositionalPseudo( fn ) { - return markFunction(function( argument ) { + return markFunction( function( argument ) { argument = +argument; - return markFunction(function( seed, matches ) { + return markFunction( function( seed, matches ) { var j, matchIndexes = fn( [], seed.length, argument ), i = matchIndexes.length; // Match elements found at the specified indexes while ( i-- ) { - if ( seed[ (j = matchIndexes[i]) ] ) { - seed[j] = !(matches[j] = seed[j]); + if ( seed[ ( j = matchIndexes[ i ] ) ] ) { + seed[ j ] = !( matches[ j ] = seed[ j ] ); } } - }); - }); + } ); + } ); } /** @@ -1073,7 +1109,7 @@ support = Sizzle.support = {}; */ isXML = Sizzle.isXML = function( elem ) { var namespace = elem.namespaceURI, - docElem = (elem.ownerDocument || elem).documentElement; + docElem = ( elem.ownerDocument || elem ).documentElement; // Support: IE <=8 // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes @@ -1091,7 +1127,11 @@ setDocument = Sizzle.setDocument = function( node ) { doc = node ? node.ownerDocument || node : preferredDoc; // Return early if doc is invalid or already selected - if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { return document; } @@ -1100,10 +1140,14 @@ setDocument = Sizzle.setDocument = function( node ) { docElem = document.documentElement; documentIsHTML = !isXML( document ); - // Support: IE 9-11, Edge + // Support: IE 9 - 11+, Edge 12 - 18+ // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) - if ( preferredDoc !== document && - (subWindow = document.defaultView) && subWindow.top !== subWindow ) { + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( preferredDoc != document && + ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { // Support: IE 11, Edge if ( subWindow.addEventListener ) { @@ -1115,25 +1159,36 @@ setDocument = Sizzle.setDocument = function( node ) { } } + // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, + // Safari 4 - 5 only, Opera <=11.6 - 12.x only + // IE/Edge & older browsers don't support the :scope pseudo-class. + // Support: Safari 6.0 only + // Safari 6.0 supports :scope but it's an alias of :root there. + support.scope = assert( function( el ) { + docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); + return typeof el.querySelectorAll !== "undefined" && + !el.querySelectorAll( ":scope fieldset div" ).length; + } ); + /* Attributes ---------------------------------------------------------------------- */ // Support: IE<8 // Verify that getAttribute really returns attributes and not properties // (excepting IE8 booleans) - support.attributes = assert(function( el ) { + support.attributes = assert( function( el ) { el.className = "i"; - return !el.getAttribute("className"); - }); + return !el.getAttribute( "className" ); + } ); /* getElement(s)By* ---------------------------------------------------------------------- */ // Check if getElementsByTagName("*") returns only elements - support.getElementsByTagName = assert(function( el ) { - el.appendChild( document.createComment("") ); - return !el.getElementsByTagName("*").length; - }); + support.getElementsByTagName = assert( function( el ) { + el.appendChild( document.createComment( "" ) ); + return !el.getElementsByTagName( "*" ).length; + } ); // Support: IE<9 support.getElementsByClassName = rnative.test( document.getElementsByClassName ); @@ -1142,38 +1197,38 @@ setDocument = Sizzle.setDocument = function( node ) { // Check if getElementById returns elements by name // The broken getElementById methods don't pick up programmatically-set names, // so use a roundabout getElementsByName test - support.getById = assert(function( el ) { + support.getById = assert( function( el ) { docElem.appendChild( el ).id = expando; return !document.getElementsByName || !document.getElementsByName( expando ).length; - }); + } ); // ID filter and find if ( support.getById ) { - Expr.filter["ID"] = function( id ) { + Expr.filter[ "ID" ] = function( id ) { var attrId = id.replace( runescape, funescape ); return function( elem ) { - return elem.getAttribute("id") === attrId; + return elem.getAttribute( "id" ) === attrId; }; }; - Expr.find["ID"] = function( id, context ) { + Expr.find[ "ID" ] = function( id, context ) { if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { var elem = context.getElementById( id ); return elem ? [ elem ] : []; } }; } else { - Expr.filter["ID"] = function( id ) { + Expr.filter[ "ID" ] = function( id ) { var attrId = id.replace( runescape, funescape ); return function( elem ) { var node = typeof elem.getAttributeNode !== "undefined" && - elem.getAttributeNode("id"); + elem.getAttributeNode( "id" ); return node && node.value === attrId; }; }; // Support: IE 6 - 7 only // getElementById is not reliable as a find shortcut - Expr.find["ID"] = function( id, context ) { + Expr.find[ "ID" ] = function( id, context ) { if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { var node, i, elems, elem = context.getElementById( id ); @@ -1181,7 +1236,7 @@ setDocument = Sizzle.setDocument = function( node ) { if ( elem ) { // Verify the id attribute - node = elem.getAttributeNode("id"); + node = elem.getAttributeNode( "id" ); if ( node && node.value === id ) { return [ elem ]; } @@ -1189,8 +1244,8 @@ setDocument = Sizzle.setDocument = function( node ) { // Fall back on getElementsByName elems = context.getElementsByName( id ); i = 0; - while ( (elem = elems[i++]) ) { - node = elem.getAttributeNode("id"); + while ( ( elem = elems[ i++ ] ) ) { + node = elem.getAttributeNode( "id" ); if ( node && node.value === id ) { return [ elem ]; } @@ -1203,7 +1258,7 @@ setDocument = Sizzle.setDocument = function( node ) { } // Tag - Expr.find["TAG"] = support.getElementsByTagName ? + Expr.find[ "TAG" ] = support.getElementsByTagName ? function( tag, context ) { if ( typeof context.getElementsByTagName !== "undefined" ) { return context.getElementsByTagName( tag ); @@ -1218,12 +1273,13 @@ setDocument = Sizzle.setDocument = function( node ) { var elem, tmp = [], i = 0, + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too results = context.getElementsByTagName( tag ); // Filter out possible comments if ( tag === "*" ) { - while ( (elem = results[i++]) ) { + while ( ( elem = results[ i++ ] ) ) { if ( elem.nodeType === 1 ) { tmp.push( elem ); } @@ -1235,7 +1291,7 @@ setDocument = Sizzle.setDocument = function( node ) { }; // Class - Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { + Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { return context.getElementsByClassName( className ); } @@ -1256,10 +1312,14 @@ setDocument = Sizzle.setDocument = function( node ) { // See https://bugs.jquery.com/ticket/13378 rbuggyQSA = []; - if ( (support.qsa = rnative.test( document.querySelectorAll )) ) { + if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { + // Build QSA regex // Regex strategy adopted from Diego Perini - assert(function( el ) { + assert( function( el ) { + + var input; + // Select is set to empty string on purpose // This is to test IE's treatment of not explicitly // setting a boolean content attribute, @@ -1273,78 +1333,98 @@ setDocument = Sizzle.setDocument = function( node ) { // Nothing should be selected when empty strings follow ^= or $= or *= // The test attribute must be unknown in Opera but "safe" for WinRT // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section - if ( el.querySelectorAll("[msallowcapture^='']").length ) { + if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); } // Support: IE8 // Boolean attributes and "value" are not treated correctly - if ( !el.querySelectorAll("[selected]").length ) { + if ( !el.querySelectorAll( "[selected]" ).length ) { rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); } // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { - rbuggyQSA.push("~="); + rbuggyQSA.push( "~=" ); + } + + // Support: IE 11+, Edge 15 - 18+ + // IE 11/Edge don't find elements on a `[name='']` query in some cases. + // Adding a temporary attribute to the document before the selection works + // around the issue. + // Interestingly, IE 10 & older don't seem to have the issue. + input = document.createElement( "input" ); + input.setAttribute( "name", "" ); + el.appendChild( input ); + if ( !el.querySelectorAll( "[name='']" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + + whitespace + "*(?:''|\"\")" ); } // Webkit/Opera - :checked should return selected option elements // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked // IE8 throws error here and will not see later tests - if ( !el.querySelectorAll(":checked").length ) { - rbuggyQSA.push(":checked"); + if ( !el.querySelectorAll( ":checked" ).length ) { + rbuggyQSA.push( ":checked" ); } // Support: Safari 8+, iOS 8+ // https://bugs.webkit.org/show_bug.cgi?id=136851 // In-page `selector#id sibling-combinator selector` fails if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { - rbuggyQSA.push(".#.+[+~]"); + rbuggyQSA.push( ".#.+[+~]" ); } - }); - assert(function( el ) { + // Support: Firefox <=3.6 - 5 only + // Old Firefox doesn't throw on a badly-escaped identifier. + el.querySelectorAll( "\\\f" ); + rbuggyQSA.push( "[\\r\\n\\f]" ); + } ); + + assert( function( el ) { el.innerHTML = "" + ""; // Support: Windows 8 Native Apps // The type and name attributes are restricted during .innerHTML assignment - var input = document.createElement("input"); + var input = document.createElement( "input" ); input.setAttribute( "type", "hidden" ); el.appendChild( input ).setAttribute( "name", "D" ); // Support: IE8 // Enforce case-sensitivity of name attribute - if ( el.querySelectorAll("[name=d]").length ) { + if ( el.querySelectorAll( "[name=d]" ).length ) { rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); } // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) // IE8 throws error here and will not see later tests - if ( el.querySelectorAll(":enabled").length !== 2 ) { + if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { rbuggyQSA.push( ":enabled", ":disabled" ); } // Support: IE9-11+ // IE's :disabled selector does not pick up the children of disabled fieldsets docElem.appendChild( el ).disabled = true; - if ( el.querySelectorAll(":disabled").length !== 2 ) { + if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { rbuggyQSA.push( ":enabled", ":disabled" ); } + // Support: Opera 10 - 11 only // Opera 10-11 does not throw on post-comma invalid pseudos - el.querySelectorAll("*,:x"); - rbuggyQSA.push(",.*:"); - }); + el.querySelectorAll( "*,:x" ); + rbuggyQSA.push( ",.*:" ); + } ); } - if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || + if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || docElem.webkitMatchesSelector || docElem.mozMatchesSelector || docElem.oMatchesSelector || - docElem.msMatchesSelector) )) ) { + docElem.msMatchesSelector ) ) ) ) { + + assert( function( el ) { - assert(function( el ) { // Check to see if it's possible to do matchesSelector // on a disconnected node (IE 9) support.disconnectedMatch = matches.call( el, "*" ); @@ -1353,11 +1433,11 @@ setDocument = Sizzle.setDocument = function( node ) { // Gecko does not error, returns false instead matches.call( el, "[s!='']:x" ); rbuggyMatches.push( "!=", pseudos ); - }); + } ); } - rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); - rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); /* Contains ---------------------------------------------------------------------- */ @@ -1374,11 +1454,11 @@ setDocument = Sizzle.setDocument = function( node ) { adown.contains ? adown.contains( bup ) : a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 - )); + ) ); } : function( a, b ) { if ( b ) { - while ( (b = b.parentNode) ) { + while ( ( b = b.parentNode ) ) { if ( b === a ) { return true; } @@ -1407,7 +1487,11 @@ setDocument = Sizzle.setDocument = function( node ) { } // Calculate position if both inputs belong to the same document - compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? a.compareDocumentPosition( b ) : // Otherwise we know they are disconnected @@ -1415,13 +1499,24 @@ setDocument = Sizzle.setDocument = function( node ) { // Disconnected nodes if ( compare & 1 || - (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { + ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { // Choose the first element that is related to our preferred document - if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( a == document || a.ownerDocument == preferredDoc && + contains( preferredDoc, a ) ) { return -1; } - if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( b == document || b.ownerDocument == preferredDoc && + contains( preferredDoc, b ) ) { return 1; } @@ -1434,6 +1529,7 @@ setDocument = Sizzle.setDocument = function( node ) { return compare & 4 ? -1 : 1; } : function( a, b ) { + // Exit early if the nodes are identical if ( a === b ) { hasDuplicate = true; @@ -1449,8 +1545,14 @@ setDocument = Sizzle.setDocument = function( node ) { // Parentless nodes are either documents or disconnected if ( !aup || !bup ) { - return a === document ? -1 : - b === document ? 1 : + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + return a == document ? -1 : + b == document ? 1 : + /* eslint-enable eqeqeq */ aup ? -1 : bup ? 1 : sortInput ? @@ -1464,26 +1566,32 @@ setDocument = Sizzle.setDocument = function( node ) { // Otherwise we need full lists of their ancestors for comparison cur = a; - while ( (cur = cur.parentNode) ) { + while ( ( cur = cur.parentNode ) ) { ap.unshift( cur ); } cur = b; - while ( (cur = cur.parentNode) ) { + while ( ( cur = cur.parentNode ) ) { bp.unshift( cur ); } // Walk down the tree looking for a discrepancy - while ( ap[i] === bp[i] ) { + while ( ap[ i ] === bp[ i ] ) { i++; } return i ? + // Do a sibling check if the nodes have a common ancestor - siblingCheck( ap[i], bp[i] ) : + siblingCheck( ap[ i ], bp[ i ] ) : // Otherwise nodes in our document sort first - ap[i] === preferredDoc ? -1 : - bp[i] === preferredDoc ? 1 : + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + ap[ i ] == preferredDoc ? -1 : + bp[ i ] == preferredDoc ? 1 : + /* eslint-enable eqeqeq */ 0; }; @@ -1495,10 +1603,7 @@ Sizzle.matches = function( expr, elements ) { }; Sizzle.matchesSelector = function( elem, expr ) { - // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { - setDocument( elem ); - } + setDocument( elem ); if ( support.matchesSelector && documentIsHTML && !nonnativeSelectorCache[ expr + " " ] && @@ -1510,12 +1615,13 @@ Sizzle.matchesSelector = function( elem, expr ) { // IE 9's matchesSelector returns false on disconnected nodes if ( ret || support.disconnectedMatch || - // As well, disconnected nodes are said to be in a document - // fragment in IE 9 - elem.document && elem.document.nodeType !== 11 ) { + + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { return ret; } - } catch (e) { + } catch ( e ) { nonnativeSelectorCache( expr, true ); } } @@ -1524,20 +1630,31 @@ Sizzle.matchesSelector = function( elem, expr ) { }; Sizzle.contains = function( context, elem ) { + // Set document vars if needed - if ( ( context.ownerDocument || context ) !== document ) { + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( context.ownerDocument || context ) != document ) { setDocument( context ); } return contains( context, elem ); }; Sizzle.attr = function( elem, name ) { + // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( elem.ownerDocument || elem ) != document ) { setDocument( elem ); } var fn = Expr.attrHandle[ name.toLowerCase() ], + // Don't get fooled by Object.prototype properties (jQuery #13807) val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? fn( elem, name, !documentIsHTML ) : @@ -1547,13 +1664,13 @@ Sizzle.attr = function( elem, name ) { val : support.attributes || !documentIsHTML ? elem.getAttribute( name ) : - (val = elem.getAttributeNode(name)) && val.specified ? + ( val = elem.getAttributeNode( name ) ) && val.specified ? val.value : null; }; Sizzle.escape = function( sel ) { - return (sel + "").replace( rcssescape, fcssescape ); + return ( sel + "" ).replace( rcssescape, fcssescape ); }; Sizzle.error = function( msg ) { @@ -1576,7 +1693,7 @@ Sizzle.uniqueSort = function( results ) { results.sort( sortOrder ); if ( hasDuplicate ) { - while ( (elem = results[i++]) ) { + while ( ( elem = results[ i++ ] ) ) { if ( elem === results[ i ] ) { j = duplicates.push( i ); } @@ -1604,17 +1721,21 @@ getText = Sizzle.getText = function( elem ) { nodeType = elem.nodeType; if ( !nodeType ) { + // If no nodeType, this is expected to be an array - while ( (node = elem[i++]) ) { + while ( ( node = elem[ i++ ] ) ) { + // Do not traverse comment nodes ret += getText( node ); } } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements // innerText usage removed for consistency of new lines (jQuery #11153) if ( typeof elem.textContent === "string" ) { return elem.textContent; } else { + // Traverse its children for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { ret += getText( elem ); @@ -1623,6 +1744,7 @@ getText = Sizzle.getText = function( elem ) { } else if ( nodeType === 3 || nodeType === 4 ) { return elem.nodeValue; } + // Do not include comment or processing instruction nodes return ret; @@ -1650,19 +1772,21 @@ Expr = Sizzle.selectors = { preFilter: { "ATTR": function( match ) { - match[1] = match[1].replace( runescape, funescape ); + match[ 1 ] = match[ 1 ].replace( runescape, funescape ); // Move the given value to match[3] whether quoted or unquoted - match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); + match[ 3 ] = ( match[ 3 ] || match[ 4 ] || + match[ 5 ] || "" ).replace( runescape, funescape ); - if ( match[2] === "~=" ) { - match[3] = " " + match[3] + " "; + if ( match[ 2 ] === "~=" ) { + match[ 3 ] = " " + match[ 3 ] + " "; } return match.slice( 0, 4 ); }, "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] 1 type (only|nth|...) 2 what (child|of-type) @@ -1673,22 +1797,25 @@ Expr = Sizzle.selectors = { 7 sign of y-component 8 y of y-component */ - match[1] = match[1].toLowerCase(); + match[ 1 ] = match[ 1 ].toLowerCase(); + + if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { - if ( match[1].slice( 0, 3 ) === "nth" ) { // nth-* requires argument - if ( !match[3] ) { - Sizzle.error( match[0] ); + if ( !match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); } // numeric x and y parameters for Expr.filter.CHILD // remember that false/true cast respectively to 0/1 - match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); - match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + match[ 4 ] = +( match[ 4 ] ? + match[ 5 ] + ( match[ 6 ] || 1 ) : + 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); + match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); - // other types prohibit arguments - } else if ( match[3] ) { - Sizzle.error( match[0] ); + // other types prohibit arguments + } else if ( match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); } return match; @@ -1696,26 +1823,28 @@ Expr = Sizzle.selectors = { "PSEUDO": function( match ) { var excess, - unquoted = !match[6] && match[2]; + unquoted = !match[ 6 ] && match[ 2 ]; - if ( matchExpr["CHILD"].test( match[0] ) ) { + if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { return null; } // Accept quoted arguments as-is - if ( match[3] ) { - match[2] = match[4] || match[5] || ""; + if ( match[ 3 ] ) { + match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; // Strip excess characters from unquoted arguments } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) - (excess = tokenize( unquoted, true )) && + ( excess = tokenize( unquoted, true ) ) && + // advance to the next closing parenthesis - (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { // excess is a negative index - match[0] = match[0].slice( 0, excess ); - match[2] = unquoted.slice( 0, excess ); + match[ 0 ] = match[ 0 ].slice( 0, excess ); + match[ 2 ] = unquoted.slice( 0, excess ); } // Return only captures needed by the pseudo filter method (type and argument) @@ -1728,7 +1857,9 @@ Expr = Sizzle.selectors = { "TAG": function( nodeNameSelector ) { var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); return nodeNameSelector === "*" ? - function() { return true; } : + function() { + return true; + } : function( elem ) { return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; }; @@ -1738,10 +1869,16 @@ Expr = Sizzle.selectors = { var pattern = classCache[ className + " " ]; return pattern || - (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && - classCache( className, function( elem ) { - return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); - }); + ( pattern = new RegExp( "(^|" + whitespace + + ")" + className + "(" + whitespace + "|$)" ) ) && classCache( + className, function( elem ) { + return pattern.test( + typeof elem.className === "string" && elem.className || + typeof elem.getAttribute !== "undefined" && + elem.getAttribute( "class" ) || + "" + ); + } ); }, "ATTR": function( name, operator, check ) { @@ -1757,6 +1894,8 @@ Expr = Sizzle.selectors = { result += ""; + /* eslint-disable max-len */ + return operator === "=" ? result === check : operator === "!=" ? result !== check : operator === "^=" ? check && result.indexOf( check ) === 0 : @@ -1765,10 +1904,12 @@ Expr = Sizzle.selectors = { operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : false; + /* eslint-enable max-len */ + }; }, - "CHILD": function( type, what, argument, first, last ) { + "CHILD": function( type, what, _argument, first, last ) { var simple = type.slice( 0, 3 ) !== "nth", forward = type.slice( -4 ) !== "last", ofType = what === "of-type"; @@ -1780,7 +1921,7 @@ Expr = Sizzle.selectors = { return !!elem.parentNode; } : - function( elem, context, xml ) { + function( elem, _context, xml ) { var cache, uniqueCache, outerCache, node, nodeIndex, start, dir = simple !== forward ? "nextSibling" : "previousSibling", parent = elem.parentNode, @@ -1794,7 +1935,7 @@ Expr = Sizzle.selectors = { if ( simple ) { while ( dir ) { node = elem; - while ( (node = node[ dir ]) ) { + while ( ( node = node[ dir ] ) ) { if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { @@ -1802,6 +1943,7 @@ Expr = Sizzle.selectors = { return false; } } + // Reverse direction for :only-* (if we haven't yet done so) start = dir = type === "only" && !start && "nextSibling"; } @@ -1817,22 +1959,22 @@ Expr = Sizzle.selectors = { // ...in a gzip-friendly way node = parent; - outerCache = node[ expando ] || (node[ expando ] = {}); + outerCache = node[ expando ] || ( node[ expando ] = {} ); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); + ( outerCache[ node.uniqueID ] = {} ); cache = uniqueCache[ type ] || []; nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; diff = nodeIndex && cache[ 2 ]; node = nodeIndex && parent.childNodes[ nodeIndex ]; - while ( (node = ++nodeIndex && node && node[ dir ] || + while ( ( node = ++nodeIndex && node && node[ dir ] || // Fallback to seeking `elem` from the start - (diff = nodeIndex = 0) || start.pop()) ) { + ( diff = nodeIndex = 0 ) || start.pop() ) ) { // When found, cache indexes on `parent` and break if ( node.nodeType === 1 && ++diff && node === elem ) { @@ -1842,16 +1984,18 @@ Expr = Sizzle.selectors = { } } else { + // Use previously-cached element index if available if ( useCache ) { + // ...in a gzip-friendly way node = elem; - outerCache = node[ expando ] || (node[ expando ] = {}); + outerCache = node[ expando ] || ( node[ expando ] = {} ); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); + ( outerCache[ node.uniqueID ] = {} ); cache = uniqueCache[ type ] || []; nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; @@ -1861,9 +2005,10 @@ Expr = Sizzle.selectors = { // xml :nth-child(...) // or :nth-last-child(...) or :nth(-last)?-of-type(...) if ( diff === false ) { + // Use the same loop as above to seek `elem` from the start - while ( (node = ++nodeIndex && node && node[ dir ] || - (diff = nodeIndex = 0) || start.pop()) ) { + while ( ( node = ++nodeIndex && node && node[ dir ] || + ( diff = nodeIndex = 0 ) || start.pop() ) ) { if ( ( ofType ? node.nodeName.toLowerCase() === name : @@ -1872,12 +2017,13 @@ Expr = Sizzle.selectors = { // Cache the index of each encountered element if ( useCache ) { - outerCache = node[ expando ] || (node[ expando ] = {}); + outerCache = node[ expando ] || + ( node[ expando ] = {} ); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); + ( outerCache[ node.uniqueID ] = {} ); uniqueCache[ type ] = [ dirruns, diff ]; } @@ -1898,6 +2044,7 @@ Expr = Sizzle.selectors = { }, "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive // http://www.w3.org/TR/selectors/#pseudo-classes // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters @@ -1917,15 +2064,15 @@ Expr = Sizzle.selectors = { if ( fn.length > 1 ) { args = [ pseudo, pseudo, "", argument ]; return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? - markFunction(function( seed, matches ) { + markFunction( function( seed, matches ) { var idx, matched = fn( seed, argument ), i = matched.length; while ( i-- ) { - idx = indexOf( seed, matched[i] ); - seed[ idx ] = !( matches[ idx ] = matched[i] ); + idx = indexOf( seed, matched[ i ] ); + seed[ idx ] = !( matches[ idx ] = matched[ i ] ); } - }) : + } ) : function( elem ) { return fn( elem, 0, args ); }; @@ -1936,8 +2083,10 @@ Expr = Sizzle.selectors = { }, pseudos: { + // Potentially complex pseudos - "not": markFunction(function( selector ) { + "not": markFunction( function( selector ) { + // Trim the selector passed to compile // to avoid treating leading and trailing // spaces as combinators @@ -1946,39 +2095,40 @@ Expr = Sizzle.selectors = { matcher = compile( selector.replace( rtrim, "$1" ) ); return matcher[ expando ] ? - markFunction(function( seed, matches, context, xml ) { + markFunction( function( seed, matches, _context, xml ) { var elem, unmatched = matcher( seed, null, xml, [] ), i = seed.length; // Match elements unmatched by `matcher` while ( i-- ) { - if ( (elem = unmatched[i]) ) { - seed[i] = !(matches[i] = elem); + if ( ( elem = unmatched[ i ] ) ) { + seed[ i ] = !( matches[ i ] = elem ); } } - }) : - function( elem, context, xml ) { - input[0] = elem; + } ) : + function( elem, _context, xml ) { + input[ 0 ] = elem; matcher( input, null, xml, results ); + // Don't keep the element (issue #299) - input[0] = null; + input[ 0 ] = null; return !results.pop(); }; - }), + } ), - "has": markFunction(function( selector ) { + "has": markFunction( function( selector ) { return function( elem ) { return Sizzle( selector, elem ).length > 0; }; - }), + } ), - "contains": markFunction(function( text ) { + "contains": markFunction( function( text ) { text = text.replace( runescape, funescape ); return function( elem ) { return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; }; - }), + } ), // "Whether an element is represented by a :lang() selector // is based solely on the element's language value @@ -1988,25 +2138,26 @@ Expr = Sizzle.selectors = { // The identifier C does not have to be a valid language name." // http://www.w3.org/TR/selectors/#lang-pseudo "lang": markFunction( function( lang ) { + // lang value must be a valid identifier - if ( !ridentifier.test(lang || "") ) { + if ( !ridentifier.test( lang || "" ) ) { Sizzle.error( "unsupported lang: " + lang ); } lang = lang.replace( runescape, funescape ).toLowerCase(); return function( elem ) { var elemLang; do { - if ( (elemLang = documentIsHTML ? + if ( ( elemLang = documentIsHTML ? elem.lang : - elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { + elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { elemLang = elemLang.toLowerCase(); return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; } - } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); return false; }; - }), + } ), // Miscellaneous "target": function( elem ) { @@ -2019,7 +2170,9 @@ Expr = Sizzle.selectors = { }, "focus": function( elem ) { - return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + return elem === document.activeElement && + ( !document.hasFocus || document.hasFocus() ) && + !!( elem.type || elem.href || ~elem.tabIndex ); }, // Boolean properties @@ -2027,16 +2180,20 @@ Expr = Sizzle.selectors = { "disabled": createDisabledPseudo( true ), "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked var nodeName = elem.nodeName.toLowerCase(); - return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + return ( nodeName === "input" && !!elem.checked ) || + ( nodeName === "option" && !!elem.selected ); }, "selected": function( elem ) { + // Accessing this property makes selected-by-default // options in Safari work properly if ( elem.parentNode ) { + // eslint-disable-next-line no-unused-expressions elem.parentNode.selectedIndex; } @@ -2045,6 +2202,7 @@ Expr = Sizzle.selectors = { // Contents "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), // but not by others (comment: 8; processing instruction: 7; etc.) @@ -2058,7 +2216,7 @@ Expr = Sizzle.selectors = { }, "parent": function( elem ) { - return !Expr.pseudos["empty"]( elem ); + return !Expr.pseudos[ "empty" ]( elem ); }, // Element/input types @@ -2082,39 +2240,40 @@ Expr = Sizzle.selectors = { // Support: IE<8 // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" - ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); + ( ( attr = elem.getAttribute( "type" ) ) == null || + attr.toLowerCase() === "text" ); }, // Position-in-collection - "first": createPositionalPseudo(function() { + "first": createPositionalPseudo( function() { return [ 0 ]; - }), + } ), - "last": createPositionalPseudo(function( matchIndexes, length ) { + "last": createPositionalPseudo( function( _matchIndexes, length ) { return [ length - 1 ]; - }), + } ), - "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { return [ argument < 0 ? argument + length : argument ]; - }), + } ), - "even": createPositionalPseudo(function( matchIndexes, length ) { + "even": createPositionalPseudo( function( matchIndexes, length ) { var i = 0; for ( ; i < length; i += 2 ) { matchIndexes.push( i ); } return matchIndexes; - }), + } ), - "odd": createPositionalPseudo(function( matchIndexes, length ) { + "odd": createPositionalPseudo( function( matchIndexes, length ) { var i = 1; for ( ; i < length; i += 2 ) { matchIndexes.push( i ); } return matchIndexes; - }), + } ), - "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { var i = argument < 0 ? argument + length : argument > length ? @@ -2124,19 +2283,19 @@ Expr = Sizzle.selectors = { matchIndexes.push( i ); } return matchIndexes; - }), + } ), - "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { var i = argument < 0 ? argument + length : argument; for ( ; ++i < length; ) { matchIndexes.push( i ); } return matchIndexes; - }) + } ) } }; -Expr.pseudos["nth"] = Expr.pseudos["eq"]; +Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; // Add button/input type pseudos for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { @@ -2167,37 +2326,39 @@ tokenize = Sizzle.tokenize = function( selector, parseOnly ) { while ( soFar ) { // Comma and first run - if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( !matched || ( match = rcomma.exec( soFar ) ) ) { if ( match ) { + // Don't consume trailing commas as valid - soFar = soFar.slice( match[0].length ) || soFar; + soFar = soFar.slice( match[ 0 ].length ) || soFar; } - groups.push( (tokens = []) ); + groups.push( ( tokens = [] ) ); } matched = false; // Combinators - if ( (match = rcombinators.exec( soFar )) ) { + if ( ( match = rcombinators.exec( soFar ) ) ) { matched = match.shift(); - tokens.push({ + tokens.push( { value: matched, + // Cast descendant combinators to space - type: match[0].replace( rtrim, " " ) - }); + type: match[ 0 ].replace( rtrim, " " ) + } ); soFar = soFar.slice( matched.length ); } // Filters for ( type in Expr.filter ) { - if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || - (match = preFilters[ type ]( match ))) ) { + if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || + ( match = preFilters[ type ]( match ) ) ) ) { matched = match.shift(); - tokens.push({ + tokens.push( { value: matched, type: type, matches: match - }); + } ); soFar = soFar.slice( matched.length ); } } @@ -2214,6 +2375,7 @@ tokenize = Sizzle.tokenize = function( selector, parseOnly ) { soFar.length : soFar ? Sizzle.error( selector ) : + // Cache the tokens tokenCache( selector, groups ).slice( 0 ); }; @@ -2223,7 +2385,7 @@ function toSelector( tokens ) { len = tokens.length, selector = ""; for ( ; i < len; i++ ) { - selector += tokens[i].value; + selector += tokens[ i ].value; } return selector; } @@ -2236,9 +2398,10 @@ function addCombinator( matcher, combinator, base ) { doneName = done++; return combinator.first ? + // Check against closest ancestor/preceding element function( elem, context, xml ) { - while ( (elem = elem[ dir ]) ) { + while ( ( elem = elem[ dir ] ) ) { if ( elem.nodeType === 1 || checkNonElements ) { return matcher( elem, context, xml ); } @@ -2253,7 +2416,7 @@ function addCombinator( matcher, combinator, base ) { // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching if ( xml ) { - while ( (elem = elem[ dir ]) ) { + while ( ( elem = elem[ dir ] ) ) { if ( elem.nodeType === 1 || checkNonElements ) { if ( matcher( elem, context, xml ) ) { return true; @@ -2261,27 +2424,29 @@ function addCombinator( matcher, combinator, base ) { } } } else { - while ( (elem = elem[ dir ]) ) { + while ( ( elem = elem[ dir ] ) ) { if ( elem.nodeType === 1 || checkNonElements ) { - outerCache = elem[ expando ] || (elem[ expando ] = {}); + outerCache = elem[ expando ] || ( elem[ expando ] = {} ); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {}); + uniqueCache = outerCache[ elem.uniqueID ] || + ( outerCache[ elem.uniqueID ] = {} ); if ( skip && skip === elem.nodeName.toLowerCase() ) { elem = elem[ dir ] || elem; - } else if ( (oldCache = uniqueCache[ key ]) && + } else if ( ( oldCache = uniqueCache[ key ] ) && oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { // Assign to newCache so results back-propagate to previous elements - return (newCache[ 2 ] = oldCache[ 2 ]); + return ( newCache[ 2 ] = oldCache[ 2 ] ); } else { + // Reuse newcache so results back-propagate to previous elements uniqueCache[ key ] = newCache; // A match means we're done; a fail means we have to keep checking - if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { + if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { return true; } } @@ -2297,20 +2462,20 @@ function elementMatcher( matchers ) { function( elem, context, xml ) { var i = matchers.length; while ( i-- ) { - if ( !matchers[i]( elem, context, xml ) ) { + if ( !matchers[ i ]( elem, context, xml ) ) { return false; } } return true; } : - matchers[0]; + matchers[ 0 ]; } function multipleContexts( selector, contexts, results ) { var i = 0, len = contexts.length; for ( ; i < len; i++ ) { - Sizzle( selector, contexts[i], results ); + Sizzle( selector, contexts[ i ], results ); } return results; } @@ -2323,7 +2488,7 @@ function condense( unmatched, map, filter, context, xml ) { mapped = map != null; for ( ; i < len; i++ ) { - if ( (elem = unmatched[i]) ) { + if ( ( elem = unmatched[ i ] ) ) { if ( !filter || filter( elem, context, xml ) ) { newUnmatched.push( elem ); if ( mapped ) { @@ -2343,14 +2508,18 @@ function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postS if ( postFinder && !postFinder[ expando ] ) { postFinder = setMatcher( postFinder, postSelector ); } - return markFunction(function( seed, results, context, xml ) { + return markFunction( function( seed, results, context, xml ) { var temp, i, elem, preMap = [], postMap = [], preexisting = results.length, // Get initial elements from seed or context - elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + elems = seed || multipleContexts( + selector || "*", + context.nodeType ? [ context ] : context, + [] + ), // Prefilter to get matcher input, preserving a map for seed-results synchronization matcherIn = preFilter && ( seed || !selector ) ? @@ -2358,6 +2527,7 @@ function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postS elems, matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, postFinder || ( seed ? preFilter : preexisting || postFilter ) ? @@ -2381,8 +2551,8 @@ function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postS // Un-match failing elements by moving them back to matcherIn i = temp.length; while ( i-- ) { - if ( (elem = temp[i]) ) { - matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + if ( ( elem = temp[ i ] ) ) { + matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); } } } @@ -2390,25 +2560,27 @@ function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postS if ( seed ) { if ( postFinder || preFilter ) { if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts temp = []; i = matcherOut.length; while ( i-- ) { - if ( (elem = matcherOut[i]) ) { + if ( ( elem = matcherOut[ i ] ) ) { + // Restore matcherIn since elem is not yet a final match - temp.push( (matcherIn[i] = elem) ); + temp.push( ( matcherIn[ i ] = elem ) ); } } - postFinder( null, (matcherOut = []), temp, xml ); + postFinder( null, ( matcherOut = [] ), temp, xml ); } // Move matched elements from seed to results to keep them synchronized i = matcherOut.length; while ( i-- ) { - if ( (elem = matcherOut[i]) && - (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { + if ( ( elem = matcherOut[ i ] ) && + ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) { - seed[temp] = !(results[temp] = elem); + seed[ temp ] = !( results[ temp ] = elem ); } } } @@ -2426,14 +2598,14 @@ function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postS push.apply( results, matcherOut ); } } - }); + } ); } function matcherFromTokens( tokens ) { var checkContext, matcher, j, len = tokens.length, - leadingRelative = Expr.relative[ tokens[0].type ], - implicitRelative = leadingRelative || Expr.relative[" "], + leadingRelative = Expr.relative[ tokens[ 0 ].type ], + implicitRelative = leadingRelative || Expr.relative[ " " ], i = leadingRelative ? 1 : 0, // The foundational matcher ensures that elements are reachable from top-level context(s) @@ -2445,38 +2617,43 @@ function matcherFromTokens( tokens ) { }, implicitRelative, true ), matchers = [ function( elem, context, xml ) { var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( - (checkContext = context).nodeType ? + ( checkContext = context ).nodeType ? matchContext( elem, context, xml ) : matchAnyContext( elem, context, xml ) ); + // Avoid hanging onto element (issue #299) checkContext = null; return ret; } ]; for ( ; i < len; i++ ) { - if ( (matcher = Expr.relative[ tokens[i].type ]) ) { - matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { + matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; } else { - matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); // Return special upon seeing a positional matcher if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling j = ++i; for ( ; j < len; j++ ) { - if ( Expr.relative[ tokens[j].type ] ) { + if ( Expr.relative[ tokens[ j ].type ] ) { break; } } return setMatcher( i > 1 && elementMatcher( matchers ), i > 1 && toSelector( - // If the preceding token was a descendant combinator, insert an implicit any-element `*` - tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) + + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens + .slice( 0, i - 1 ) + .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) ).replace( rtrim, "$1" ), matcher, i < j && matcherFromTokens( tokens.slice( i, j ) ), - j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), j < len && toSelector( tokens ) ); } @@ -2497,28 +2674,40 @@ function matcherFromGroupMatchers( elementMatchers, setMatchers ) { unmatched = seed && [], setMatched = [], contextBackup = outermostContext, + // We must always have either seed elements or outermost context - elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), + elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ), + // Use integer dirruns iff this is the outermost matcher - dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), + dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), len = elems.length; if ( outermost ) { - outermostContext = context === document || context || outermost; + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + outermostContext = context == document || context || outermost; } // Add elements passing elementMatchers directly to results // Support: IE<9, Safari // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id - for ( ; i !== len && (elem = elems[i]) != null; i++ ) { + for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { if ( byElement && elem ) { j = 0; - if ( !context && elem.ownerDocument !== document ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( !context && elem.ownerDocument != document ) { setDocument( elem ); xml = !documentIsHTML; } - while ( (matcher = elementMatchers[j++]) ) { - if ( matcher( elem, context || document, xml) ) { + while ( ( matcher = elementMatchers[ j++ ] ) ) { + if ( matcher( elem, context || document, xml ) ) { results.push( elem ); break; } @@ -2530,8 +2719,9 @@ function matcherFromGroupMatchers( elementMatchers, setMatchers ) { // Track unmatched elements for set filters if ( bySet ) { + // They will have gone through all possible matchers - if ( (elem = !matcher && elem) ) { + if ( ( elem = !matcher && elem ) ) { matchedCount--; } @@ -2555,16 +2745,17 @@ function matcherFromGroupMatchers( elementMatchers, setMatchers ) { // numerically zero. if ( bySet && i !== matchedCount ) { j = 0; - while ( (matcher = setMatchers[j++]) ) { + while ( ( matcher = setMatchers[ j++ ] ) ) { matcher( unmatched, setMatched, context, xml ); } if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting if ( matchedCount > 0 ) { while ( i-- ) { - if ( !(unmatched[i] || setMatched[i]) ) { - setMatched[i] = pop.call( results ); + if ( !( unmatched[ i ] || setMatched[ i ] ) ) { + setMatched[ i ] = pop.call( results ); } } } @@ -2605,13 +2796,14 @@ compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { cached = compilerCache[ selector + " " ]; if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element if ( !match ) { match = tokenize( selector ); } i = match.length; while ( i-- ) { - cached = matcherFromTokens( match[i] ); + cached = matcherFromTokens( match[ i ] ); if ( cached[ expando ] ) { setMatchers.push( cached ); } else { @@ -2620,7 +2812,10 @@ compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { } // Cache the compiled function - cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + cached = compilerCache( + selector, + matcherFromGroupMatchers( elementMatchers, setMatchers ) + ); // Save selector and tokenization cached.selector = selector; @@ -2640,7 +2835,7 @@ compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { select = Sizzle.select = function( selector, context, results, seed ) { var i, tokens, token, type, find, compiled = typeof selector === "function" && selector, - match = !seed && tokenize( (selector = compiled.selector || selector) ); + match = !seed && tokenize( ( selector = compiled.selector || selector ) ); results = results || []; @@ -2649,11 +2844,12 @@ select = Sizzle.select = function( selector, context, results, seed ) { if ( match.length === 1 ) { // Reduce context if the leading compound selector is an ID - tokens = match[0] = match[0].slice( 0 ); - if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && - context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[1].type ] ) { + tokens = match[ 0 ] = match[ 0 ].slice( 0 ); + if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { - context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; + context = ( Expr.find[ "ID" ]( token.matches[ 0 ] + .replace( runescape, funescape ), context ) || [] )[ 0 ]; if ( !context ) { return results; @@ -2666,20 +2862,22 @@ select = Sizzle.select = function( selector, context, results, seed ) { } // Fetch a seed set for right-to-left matching - i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; while ( i-- ) { - token = tokens[i]; + token = tokens[ i ]; // Abort if we hit a combinator - if ( Expr.relative[ (type = token.type) ] ) { + if ( Expr.relative[ ( type = token.type ) ] ) { break; } - if ( (find = Expr.find[ type ]) ) { + if ( ( find = Expr.find[ type ] ) ) { + // Search, expanding context for leading sibling combinators - if ( (seed = find( - token.matches[0].replace( runescape, funescape ), - rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context - )) ) { + if ( ( seed = find( + token.matches[ 0 ].replace( runescape, funescape ), + rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) || + context + ) ) ) { // If seed is empty or no tokens remain, we can return early tokens.splice( i, 1 ); @@ -2710,7 +2908,7 @@ select = Sizzle.select = function( selector, context, results, seed ) { // One-time assignments // Sort stability -support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; +support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; // Support: Chrome 14-35+ // Always assume duplicates if they aren't passed to the comparison function @@ -2721,58 +2919,59 @@ setDocument(); // Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) // Detached nodes confoundingly follow *each other* -support.sortDetached = assert(function( el ) { +support.sortDetached = assert( function( el ) { + // Should return 1, but returns 4 (following) - return el.compareDocumentPosition( document.createElement("fieldset") ) & 1; -}); + return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; +} ); // Support: IE<8 // Prevent attribute/property "interpolation" // https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx -if ( !assert(function( el ) { +if ( !assert( function( el ) { el.innerHTML = ""; - return el.firstChild.getAttribute("href") === "#" ; -}) ) { + return el.firstChild.getAttribute( "href" ) === "#"; +} ) ) { addHandle( "type|href|height|width", function( elem, name, isXML ) { if ( !isXML ) { return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); } - }); + } ); } // Support: IE<9 // Use defaultValue in place of getAttribute("value") -if ( !support.attributes || !assert(function( el ) { +if ( !support.attributes || !assert( function( el ) { el.innerHTML = ""; el.firstChild.setAttribute( "value", "" ); return el.firstChild.getAttribute( "value" ) === ""; -}) ) { - addHandle( "value", function( elem, name, isXML ) { +} ) ) { + addHandle( "value", function( elem, _name, isXML ) { if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { return elem.defaultValue; } - }); + } ); } // Support: IE<9 // Use getAttributeNode to fetch booleans when getAttribute lies -if ( !assert(function( el ) { - return el.getAttribute("disabled") == null; -}) ) { +if ( !assert( function( el ) { + return el.getAttribute( "disabled" ) == null; +} ) ) { addHandle( booleans, function( elem, name, isXML ) { var val; if ( !isXML ) { return elem[ name ] === true ? name.toLowerCase() : - (val = elem.getAttributeNode( name )) && val.specified ? + ( val = elem.getAttributeNode( name ) ) && val.specified ? val.value : - null; + null; } - }); + } ); } return Sizzle; -})( window ); +} )( window ); @@ -3141,7 +3340,7 @@ jQuery.each( { parents: function( elem ) { return dir( elem, "parentNode" ); }, - parentsUntil: function( elem, i, until ) { + parentsUntil: function( elem, _i, until ) { return dir( elem, "parentNode", until ); }, next: function( elem ) { @@ -3156,10 +3355,10 @@ jQuery.each( { prevAll: function( elem ) { return dir( elem, "previousSibling" ); }, - nextUntil: function( elem, i, until ) { + nextUntil: function( elem, _i, until ) { return dir( elem, "nextSibling", until ); }, - prevUntil: function( elem, i, until ) { + prevUntil: function( elem, _i, until ) { return dir( elem, "previousSibling", until ); }, siblings: function( elem ) { @@ -3169,7 +3368,13 @@ jQuery.each( { return siblings( elem.firstChild ); }, contents: function( elem ) { - if ( typeof elem.contentDocument !== "undefined" ) { + if ( elem.contentDocument != null && + + // Support: IE 11+ + // elements with no `data` attribute has an object + // `contentDocument` with a `null` prototype. + getProto( elem.contentDocument ) ) { + return elem.contentDocument; } @@ -3512,7 +3717,7 @@ jQuery.extend( { var fns = arguments; return jQuery.Deferred( function( newDefer ) { - jQuery.each( tuples, function( i, tuple ) { + jQuery.each( tuples, function( _i, tuple ) { // Map tuples (progress, done, fail) to arguments (done, fail, progress) var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; @@ -3965,7 +4170,7 @@ var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { // ...except when executing function values } else { bulk = fn; - fn = function( elem, key, value ) { + fn = function( elem, _key, value ) { return bulk.call( jQuery( elem ), value ); }; } @@ -4000,7 +4205,7 @@ var rmsPrefix = /^-ms-/, rdashAlpha = /-([a-z])/g; // Used by camelCase as callback to replace() -function fcamelCase( all, letter ) { +function fcamelCase( _all, letter ) { return letter.toUpperCase(); } @@ -4528,27 +4733,6 @@ var isHiddenWithinTree = function( elem, el ) { jQuery.css( elem, "display" ) === "none"; }; -var swap = function( elem, options, callback, args ) { - var ret, name, - old = {}; - - // Remember the old values, and insert the new ones - for ( name in options ) { - old[ name ] = elem.style[ name ]; - elem.style[ name ] = options[ name ]; - } - - ret = callback.apply( elem, args || [] ); - - // Revert the old values - for ( name in options ) { - elem.style[ name ] = old[ name ]; - } - - return ret; -}; - - function adjustCSS( elem, prop, valueParts, tween ) { @@ -4719,11 +4903,40 @@ var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); -// We have to close these tags to support XHTML (#13200) -var wrapMap = { +( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; // Support: IE <=9 only - option: [ 1, "" ], + // IE <=9 replaces "; + support.option = !!div.lastChild; +} )(); + + +// We have to close these tags to support XHTML (#13200) +var wrapMap = { // XHTML parsers do not magically insert elements in the // same way that tag soup parsers do. So we cannot shorten @@ -4736,12 +4949,14 @@ var wrapMap = { _default: [ 0, "", "" ] }; -// Support: IE <=9 only -wrapMap.optgroup = wrapMap.option; - wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; wrapMap.th = wrapMap.td; +// Support: IE <=9 only +if ( !support.option ) { + wrapMap.optgroup = wrapMap.option = [ 1, "" ]; +} + function getAll( context, tag ) { @@ -4874,32 +5089,6 @@ function buildFragment( elems, context, scripts, selection, ignored ) { } -( function() { - var fragment = document.createDocumentFragment(), - div = fragment.appendChild( document.createElement( "div" ) ), - input = document.createElement( "input" ); - - // Support: Android 4.0 - 4.3 only - // Check state lost if the name is set (#11217) - // Support: Windows Web Apps (WWA) - // `name` and `type` must use .setAttribute for WWA (#14901) - input.setAttribute( "type", "radio" ); - input.setAttribute( "checked", "checked" ); - input.setAttribute( "name", "t" ); - - div.appendChild( input ); - - // Support: Android <=4.1 only - // Older WebKit doesn't clone checked state correctly in fragments - support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; - - // Support: IE <=11 only - // Make sure textarea (and checkbox) defaultValue is properly cloned - div.innerHTML = ""; - support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; -} )(); - - var rkeyEvent = /^key/, rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, @@ -5008,8 +5197,8 @@ jQuery.event = { special, handlers, type, namespaces, origType, elemData = dataPriv.get( elem ); - // Don't attach events to noData or text/comment nodes (but allow plain objects) - if ( !elemData ) { + // Only attach events to objects that accept data + if ( !acceptData( elem ) ) { return; } @@ -5033,7 +5222,7 @@ jQuery.event = { // Init the element's event structure and main handler, if this is the first if ( !( events = elemData.events ) ) { - events = elemData.events = {}; + events = elemData.events = Object.create( null ); } if ( !( eventHandle = elemData.handle ) ) { eventHandle = elemData.handle = function( e ) { @@ -5191,12 +5380,15 @@ jQuery.event = { dispatch: function( nativeEvent ) { - // Make a writable jQuery.Event from the native event object - var event = jQuery.event.fix( nativeEvent ); - var i, j, ret, matched, handleObj, handlerQueue, args = new Array( arguments.length ), - handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [], + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( nativeEvent ), + + handlers = ( + dataPriv.get( this, "events" ) || Object.create( null ) + )[ event.type ] || [], special = jQuery.event.special[ event.type ] || {}; // Use the fix-ed jQuery.Event rather than the (read-only) native event @@ -5771,13 +5963,6 @@ jQuery.fn.extend( { var - /* eslint-disable max-len */ - - // See https://github.com/eslint/eslint/issues/3229 - rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi, - - /* eslint-enable */ - // Support: IE <=10 - 11, Edge 12 - 13 only // In IE/Edge using regex groups here causes severe slowdowns. // See https://connect.microsoft.com/IE/feedback/details/1736512/ @@ -5814,7 +5999,7 @@ function restoreScript( elem ) { } function cloneCopyEvent( src, dest ) { - var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; + var i, l, type, pdataOld, udataOld, udataCur, events; if ( dest.nodeType !== 1 ) { return; @@ -5822,13 +6007,11 @@ function cloneCopyEvent( src, dest ) { // 1. Copy private data: events, handlers, etc. if ( dataPriv.hasData( src ) ) { - pdataOld = dataPriv.access( src ); - pdataCur = dataPriv.set( dest, pdataOld ); + pdataOld = dataPriv.get( src ); events = pdataOld.events; if ( events ) { - delete pdataCur.handle; - pdataCur.events = {}; + dataPriv.remove( dest, "handle events" ); for ( type in events ) { for ( i = 0, l = events[ type ].length; i < l; i++ ) { @@ -5864,7 +6047,7 @@ function fixInput( src, dest ) { function domManip( collection, args, callback, ignored ) { // Flatten any nested arrays - args = concat.apply( [], args ); + args = flat( args ); var fragment, first, scripts, hasScripts, node, doc, i = 0, @@ -5939,7 +6122,7 @@ function domManip( collection, args, callback, ignored ) { if ( jQuery._evalUrl && !node.noModule ) { jQuery._evalUrl( node.src, { nonce: node.nonce || node.getAttribute( "nonce" ) - } ); + }, doc ); } } else { DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); @@ -5976,7 +6159,7 @@ function remove( elem, selector, keepData ) { jQuery.extend( { htmlPrefilter: function( html ) { - return html.replace( rxhtmlTag, "<$1>" ); + return html; }, clone: function( elem, dataAndEvents, deepDataAndEvents ) { @@ -6238,6 +6421,27 @@ var getStyles = function( elem ) { return view.getComputedStyle( elem ); }; +var swap = function( elem, options, callback ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.call( elem ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; +}; + + var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); @@ -6295,7 +6499,7 @@ var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); } var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, - reliableMarginLeftVal, + reliableTrDimensionsVal, reliableMarginLeftVal, container = document.createElement( "div" ), div = document.createElement( "div" ); @@ -6330,6 +6534,35 @@ var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); scrollboxSize: function() { computeStyleTests(); return scrollboxSizeVal; + }, + + // Support: IE 9 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Behavior in IE 9 is more subtle than in newer versions & it passes + // some versions of this test; make sure not to make it pass there! + reliableTrDimensions: function() { + var table, tr, trChild, trStyle; + if ( reliableTrDimensionsVal == null ) { + table = document.createElement( "table" ); + tr = document.createElement( "tr" ); + trChild = document.createElement( "div" ); + + table.style.cssText = "position:absolute;left:-11111px"; + tr.style.height = "1px"; + trChild.style.height = "9px"; + + documentElement + .appendChild( table ) + .appendChild( tr ) + .appendChild( trChild ); + + trStyle = window.getComputedStyle( tr ); + reliableTrDimensionsVal = parseInt( trStyle.height ) > 3; + + documentElement.removeChild( table ); + } + return reliableTrDimensionsVal; } } ); } )(); @@ -6454,7 +6687,7 @@ var fontWeight: "400" }; -function setPositiveNumber( elem, value, subtract ) { +function setPositiveNumber( _elem, value, subtract ) { // Any relative (+/-) values have already been // normalized at this point @@ -6559,17 +6792,26 @@ function getWidthOrHeight( elem, dimension, extra ) { } - // Fall back to offsetWidth/offsetHeight when value is "auto" - // This happens for inline elements with no explicit setting (gh-3571) - // Support: Android <=4.1 - 4.3 only - // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) - // Support: IE 9-11 only - // Also use offsetWidth/offsetHeight for when box sizing is unreliable - // We use getClientRects() to check for hidden/disconnected. - // In those cases, the computed value can be trusted to be border-box + // Support: IE 9 - 11 only + // Use offsetWidth/offsetHeight for when box sizing is unreliable. + // In those cases, the computed value can be trusted to be border-box. if ( ( !support.boxSizingReliable() && isBorderBox || + + // Support: IE 10 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Interestingly, in some cases IE 9 doesn't suffer from this issue. + !support.reliableTrDimensions() && nodeName( elem, "tr" ) || + + // Fall back to offsetWidth/offsetHeight when value is "auto" + // This happens for inline elements with no explicit setting (gh-3571) val === "auto" || + + // Support: Android <=4.1 - 4.3 only + // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && + + // Make sure the element is visible & connected elem.getClientRects().length ) { isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; @@ -6764,7 +7006,7 @@ jQuery.extend( { } } ); -jQuery.each( [ "height", "width" ], function( i, dimension ) { +jQuery.each( [ "height", "width" ], function( _i, dimension ) { jQuery.cssHooks[ dimension ] = { get: function( elem, computed, extra ) { if ( computed ) { @@ -7537,7 +7779,7 @@ jQuery.fn.extend( { clearQueue = type; type = undefined; } - if ( clearQueue && type !== false ) { + if ( clearQueue ) { this.queue( type || "fx", [] ); } @@ -7620,7 +7862,7 @@ jQuery.fn.extend( { } } ); -jQuery.each( [ "toggle", "show", "hide" ], function( i, name ) { +jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { var cssFn = jQuery.fn[ name ]; jQuery.fn[ name ] = function( speed, easing, callback ) { return speed == null || typeof speed === "boolean" ? @@ -7841,7 +8083,7 @@ boolHook = { } }; -jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) { +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { var getter = attrHandle[ name ] || jQuery.find.attr; attrHandle[ name ] = function( elem, name, isXML ) { @@ -8465,7 +8707,9 @@ jQuery.extend( jQuery.event, { special.bindType || type; // jQuery handler - handle = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] && + handle = ( + dataPriv.get( cur, "events" ) || Object.create( null ) + )[ event.type ] && dataPriv.get( cur, "handle" ); if ( handle ) { handle.apply( cur, data ); @@ -8576,7 +8820,10 @@ if ( !support.focusin ) { jQuery.event.special[ fix ] = { setup: function() { - var doc = this.ownerDocument || this, + + // Handle: regular nodes (via `this.ownerDocument`), window + // (via `this.document`) & document (via `this`). + var doc = this.ownerDocument || this.document || this, attaches = dataPriv.access( doc, fix ); if ( !attaches ) { @@ -8585,7 +8832,7 @@ if ( !support.focusin ) { dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); }, teardown: function() { - var doc = this.ownerDocument || this, + var doc = this.ownerDocument || this.document || this, attaches = dataPriv.access( doc, fix ) - 1; if ( !attaches ) { @@ -8601,7 +8848,7 @@ if ( !support.focusin ) { } var location = window.location; -var nonce = Date.now(); +var nonce = { guid: Date.now() }; var rquery = ( /\?/ ); @@ -8733,7 +8980,7 @@ jQuery.fn.extend( { rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && ( this.checked || !rcheckableType.test( type ) ); } ) - .map( function( i, elem ) { + .map( function( _i, elem ) { var val = jQuery( this ).val(); if ( val == null ) { @@ -9346,7 +9593,8 @@ jQuery.extend( { // Add or update anti-cache param if needed if ( s.cache === false ) { cacheURL = cacheURL.replace( rantiCache, "$1" ); - uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce++ ) + uncached; + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + + uncached; } // Put hash and anti-cache on the URL that will be requested (gh-1732) @@ -9479,6 +9727,11 @@ jQuery.extend( { response = ajaxHandleResponses( s, jqXHR, responses ); } + // Use a noop converter for missing script + if ( !isSuccess && jQuery.inArray( "script", s.dataTypes ) > -1 ) { + s.converters[ "text script" ] = function() {}; + } + // Convert no matter what (that way responseXXX fields are always set) response = ajaxConvert( s, response, jqXHR, isSuccess ); @@ -9569,7 +9822,7 @@ jQuery.extend( { } } ); -jQuery.each( [ "get", "post" ], function( i, method ) { +jQuery.each( [ "get", "post" ], function( _i, method ) { jQuery[ method ] = function( url, data, callback, type ) { // Shift arguments if data argument was omitted @@ -9590,8 +9843,17 @@ jQuery.each( [ "get", "post" ], function( i, method ) { }; } ); +jQuery.ajaxPrefilter( function( s ) { + var i; + for ( i in s.headers ) { + if ( i.toLowerCase() === "content-type" ) { + s.contentType = s.headers[ i ] || ""; + } + } +} ); + -jQuery._evalUrl = function( url, options ) { +jQuery._evalUrl = function( url, options, doc ) { return jQuery.ajax( { url: url, @@ -9609,7 +9871,7 @@ jQuery._evalUrl = function( url, options ) { "text script": function() {} }, dataFilter: function( response ) { - jQuery.globalEval( response, options ); + jQuery.globalEval( response, options, doc ); } } ); }; @@ -9931,7 +10193,7 @@ var oldCallbacks = [], jQuery.ajaxSetup( { jsonp: "callback", jsonpCallback: function() { - var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) ); + var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce.guid++ ) ); this[ callback ] = true; return callback; } @@ -10148,23 +10410,6 @@ jQuery.fn.load = function( url, params, callback ) { -// Attach a bunch of functions for handling common AJAX events -jQuery.each( [ - "ajaxStart", - "ajaxStop", - "ajaxComplete", - "ajaxError", - "ajaxSuccess", - "ajaxSend" -], function( i, type ) { - jQuery.fn[ type ] = function( fn ) { - return this.on( type, fn ); - }; -} ); - - - - jQuery.expr.pseudos.animated = function( elem ) { return jQuery.grep( jQuery.timers, function( fn ) { return elem === fn.elem; @@ -10221,6 +10466,12 @@ jQuery.offset = { options.using.call( elem, props ); } else { + if ( typeof props.top === "number" ) { + props.top += "px"; + } + if ( typeof props.left === "number" ) { + props.left += "px"; + } curElem.css( props ); } } @@ -10371,7 +10622,7 @@ jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( // Blink bug: https://bugs.chromium.org/p/chromium/issues/detail?id=589347 // getComputedStyle returns percent when specified for top/left/bottom/right; // rather than make the css module depend on the offset module, just check for it here -jQuery.each( [ "top", "left" ], function( i, prop ) { +jQuery.each( [ "top", "left" ], function( _i, prop ) { jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition, function( elem, computed ) { if ( computed ) { @@ -10434,25 +10685,19 @@ jQuery.each( { Height: "height", Width: "width" }, function( name, type ) { } ); -jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " + - "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + - "change select submit keydown keypress keyup contextmenu" ).split( " " ), - function( i, name ) { - - // Handle event binding - jQuery.fn[ name ] = function( data, fn ) { - return arguments.length > 0 ? - this.on( name, null, data, fn ) : - this.trigger( name ); +jQuery.each( [ + "ajaxStart", + "ajaxStop", + "ajaxComplete", + "ajaxError", + "ajaxSuccess", + "ajaxSend" +], function( _i, type ) { + jQuery.fn[ type ] = function( fn ) { + return this.on( type, fn ); }; } ); -jQuery.fn.extend( { - hover: function( fnOver, fnOut ) { - return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); - } -} ); - @@ -10474,9 +10719,33 @@ jQuery.fn.extend( { return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn ); + }, + + hover: function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); } } ); +jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup contextmenu" ).split( " " ), + function( _i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( data, fn ) { + return arguments.length > 0 ? + this.on( name, null, data, fn ) : + this.trigger( name ); + }; + } ); + + + + +// Support: Android <=4.0 only +// Make sure we trim BOM and NBSP +var rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g; + // Bind a function to a context, optionally partially applying any // arguments. // jQuery.proxy is deprecated to promote standards (specifically Function#bind) @@ -10539,6 +10808,11 @@ jQuery.isNumeric = function( obj ) { !isNaN( obj - parseFloat( obj ) ); }; +jQuery.trim = function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); +}; @@ -10587,7 +10861,7 @@ jQuery.noConflict = function( deep ) { // Expose jQuery and $ identifiers, even in AMD // (#7102#comment:10, https://github.com/jquery/jquery/pull/557) // and CommonJS for browser emulators (#13566) -if ( !noGlobal ) { +if ( typeof noGlobal === "undefined" ) { window.jQuery = window.$ = jQuery; } diff --git a/docs/api/assets/jquery.js b/docs/api/assets/jquery.js index a1c07fd8..b0614034 100644 --- a/docs/api/assets/jquery.js +++ b/docs/api/assets/jquery.js @@ -1,2 +1,2 @@ -/*! jQuery v3.4.1 | (c) JS Foundation and other contributors | jquery.org/license */ -!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],E=C.document,r=Object.getPrototypeOf,s=t.slice,g=t.concat,u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.4.1",k=function(e,t){return new k.fn.init(e,t)},p=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;function d(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp($),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+$),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ne=function(e,t,n){var r="0x"+t-65536;return r!=r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(m.childNodes),m.childNodes),t[m.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&((e?e.ownerDocument||e:m)!==C&&T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!A[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&U.test(t)){(s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=k),o=(l=h(t)).length;while(o--)l[o]="#"+s+" "+xe(l[o]);c=l.join(","),f=ee.test(t)&&ye(e.parentNode)||e}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){A(t,!0)}finally{s===k&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[k]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:m;return r!==C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),m!==C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=k,!C.getElementsByName||!C.getElementsByName(k).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+k+"-]").length||v.push("~="),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+k+"+*").length||v.push(".#.+[+~]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",$)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e===C||e.ownerDocument===m&&y(m,e)?-1:t===C||t.ownerDocument===m&&y(m,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===C?-1:t===C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]===m?-1:s[r]===m?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if((e.ownerDocument||e)!==C&&T(e),d.matchesSelector&&E&&!A[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){A(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=p[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&p(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?k.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?k.grep(e,function(e){return e===n!==r}):"string"!=typeof n?k.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(k.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:L.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof k?t[0]:t,k.merge(this,k.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),D.test(r[1])&&k.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(k):k.makeArray(e,this)}).prototype=k.fn,q=k(E);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}k.fn.extend({has:function(e){var t=k(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?k.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;nx",y.noCloneChecked=!!me.cloneNode(!0).lastChild.defaultValue;var Te=/^key/,Ce=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ee=/^([^.]*)(?:\.(.+)|)/;function ke(){return!0}function Se(){return!1}function Ne(e,t){return e===function(){try{return E.activeElement}catch(e){}}()==("focus"===t)}function Ae(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)Ae(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Se;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return k().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=k.guid++)),e.each(function(){k.event.add(this,t,i,r,n)})}function De(e,i,o){o?(Q.set(e,i,!1),k.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=Q.get(this,i);if(1&e.isTrigger&&this[i]){if(r.length)(k.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),Q.set(this,i,r),t=o(this,i),this[i](),r!==(n=Q.get(this,i))||t?Q.set(this,i,!1):n={},r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n.value}else r.length&&(Q.set(this,i,{value:k.event.trigger(k.extend(r[0],k.Event.prototype),r.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Q.get(e,i)&&k.event.add(e,i,ke)}k.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.get(t);if(v){n.handler&&(n=(o=n).handler,i=o.selector),i&&k.find.matchesSelector(ie,i),n.guid||(n.guid=k.guid++),(u=v.events)||(u=v.events={}),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof k&&k.event.triggered!==e.type?k.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(R)||[""]).length;while(l--)d=g=(s=Ee.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=k.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=k.event.special[d]||{},c=k.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&k.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),k.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.hasData(e)&&Q.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(R)||[""]).length;while(l--)if(d=g=(s=Ee.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d){f=k.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||k.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)k.event.remove(e,d+t[l],n,r,!0);k.isEmptyObject(u)&&Q.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=k.event.fix(e),u=new Array(arguments.length),l=(Q.get(this,"events")||{})[s.type]||[],c=k.event.special[s.type]||{};for(u[0]=s,t=1;t\x20\t\r\n\f]*)[^>]*)\/>/gi,qe=/\s*$/g;function Oe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&k(e).children("tbody")[0]||e}function Pe(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Re(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Me(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(Q.hasData(e)&&(o=Q.access(e),a=Q.set(t,o),l=o.events))for(i in delete a.handle,a.events={},l)for(n=0,r=l[i].length;n")},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=oe(e);if(!(y.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||k.isXMLDoc(e)))for(a=ve(c),r=0,i=(o=ve(e)).length;r").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Vt,Gt=[],Yt=/(=)\?(?=&|$)|\?\?/;k.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Gt.pop()||k.expando+"_"+kt++;return this[e]=!0,e}}),k.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Yt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Yt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Yt,"$1"+r):!1!==e.jsonp&&(e.url+=(St.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||k.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?k(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Gt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Vt=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Vt.childNodes.length),k.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=D.exec(e))?[t.createElement(i[1])]:(i=we([e],t,o),o&&o.length&&k(o).remove(),k.merge([],i.childNodes)));var r,i,o},k.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(k.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},k.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){k.fn[t]=function(e){return this.on(t,e)}}),k.expr.pseudos.animated=function(t){return k.grep(k.timers,function(e){return t===e.elem}).length},k.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=k.css(e,"position"),c=k(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=k.css(e,"top"),u=k.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,k.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},k.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){k.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===k.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===k.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=k(e).offset()).top+=k.css(e,"borderTopWidth",!0),i.left+=k.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-k.css(r,"marginTop",!0),left:t.left-i.left-k.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===k.css(e,"position"))e=e.offsetParent;return e||ie})}}),k.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;k.fn[t]=function(e){return _(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),k.each(["top","left"],function(e,n){k.cssHooks[n]=ze(y.pixelPosition,function(e,t){if(t)return t=_e(e,n),$e.test(t)?k(e).position()[n]+"px":t})}),k.each({Height:"height",Width:"width"},function(a,s){k.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){k.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return _(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?k.css(e,t,i):k.style(e,t,n,i)},s,n?e:void 0,n)}})}),k.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){k.fn[n]=function(e,t){return 0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,j=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function qe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function Le(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function He(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=$e(y.pixelPosition,function(e,t){if(t)return t=Be(e,n),Me.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0'); - if (DOCUMENTATION_OPTIONS.FILE_SUFFIX === '') { + var requestUrl = ""; + var linkUrl = ""; + if (DOCUMENTATION_OPTIONS.BUILDER === 'dirhtml') { // dirhtml builder var dirname = item[0] + '/'; if (dirname.match(/\/index\/$/)) { @@ -253,15 +260,17 @@ var Search = { } else if (dirname == 'index/') { dirname = ''; } - listItem.append($('').attr('href', - DOCUMENTATION_OPTIONS.URL_ROOT + dirname + - highlightstring + item[2]).html(item[1])); + requestUrl = DOCUMENTATION_OPTIONS.URL_ROOT + dirname; + linkUrl = requestUrl; + } else { // normal html builders - listItem.append($('').attr('href', - item[0] + DOCUMENTATION_OPTIONS.FILE_SUFFIX + - highlightstring + item[2]).html(item[1])); + requestUrl = DOCUMENTATION_OPTIONS.URL_ROOT + item[0] + DOCUMENTATION_OPTIONS.FILE_SUFFIX; + linkUrl = item[0] + DOCUMENTATION_OPTIONS.LINK_SUFFIX; } + listItem.append($('').attr('href', + linkUrl + + highlightstring + item[2]).html(item[1])); if (item[3]) { listItem.append($(' (' + item[3] + ')')); Search.output.append(listItem); @@ -269,7 +278,7 @@ var Search = { displayNextItem(); }); } else if (DOCUMENTATION_OPTIONS.HAS_SOURCE) { - $.ajax({url: DOCUMENTATION_OPTIONS.URL_ROOT + item[0] + DOCUMENTATION_OPTIONS.FILE_SUFFIX, + $.ajax({url: requestUrl, dataType: "text", complete: function(jqxhr, textstatus) { var data = jqxhr.responseText; @@ -424,7 +433,7 @@ var Search = { for (j = 0; j < _files.length; j++) { file = _files[j]; if (!(file in scoreMap)) - scoreMap[file] = {} + scoreMap[file] = {}; scoreMap[file][word] = o.score; } }); @@ -432,7 +441,7 @@ var Search = { // create the mapping for (j = 0; j < files.length; j++) { file = files[j]; - if (file in fileMap) + if (file in fileMap && fileMap[file].indexOf(word) === -1) fileMap[file].push(word); else fileMap[file] = [word]; diff --git a/docs/api/changelog.html b/docs/api/changelog.html index aae72db9..389c1350 100644 --- a/docs/api/changelog.html +++ b/docs/api/changelog.html @@ -8,7 +8,7 @@ - CHANGELOG — Singularity Python API 1 documentation + <no title> — Singularity Python API 1 documentation @@ -21,10 +21,10 @@ - - - - + + + + @@ -83,12 +83,8 @@ -
  • spython.oci
  • -
  • spython.tests.test_client
  • -
  • spython.tests.test_instances
  • -
  • spython.tests.test_utils
  • spython.utils.fileio
  • spython.utils.terminal
  • diff --git a/docs/api/modules/spython/client.html b/docs/api/modules/spython/client.html index 847d049e..22bca830 100644 --- a/docs/api/modules/spython/client.html +++ b/docs/api/modules/spython/client.html @@ -21,10 +21,10 @@ - - - - + + + + @@ -84,7 +84,6 @@ @@ -282,7 +281,7 @@

    Source code for spython.client

         os.putenv("SINGULARITY_MESSAGELEVEL", level)
     
         # Import logger to set
    -    from spython.logger import bot
    +    from spython.logger import bot
     
         bot.debug("Logging level %s" % level)
         import spython
    @@ -332,13 +331,13 @@ 

    Source code for spython.client

     
         # Does the user want help for a subcommand?
         if args.command == "recipe":
    -        from .recipe import main as func
    +        from .recipe import main as func
     
         elif args.command == "shell":
    -        from .shell import main as func
    +        from .shell import main as func
     
         elif args.command == "test":
    -        from .test import main as func
    +        from .test import main as func
     
         else:
             print_help()
    diff --git a/docs/api/modules/spython/client/recipe.html b/docs/api/modules/spython/client/recipe.html
    index de2c931f..e3f0ec24 100644
    --- a/docs/api/modules/spython/client/recipe.html
    +++ b/docs/api/modules/spython/client/recipe.html
    @@ -21,10 +21,10 @@
       
         
           
    -        
    -        
    -        
    -        
    +        
    +        
    +        
    +        
         
         
     
    @@ -84,7 +84,6 @@
                 
                   
     
                 
    @@ -156,11 +155,11 @@ 

    Source code for spython.client.recipe

     # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
     # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
     
    -from spython.main.parse.writers import get_writer
    -from spython.main.parse.parsers import get_parser
    +from spython.main.parse.writers import get_writer
    +from spython.main.parse.parsers import get_parser
     
    -from spython.logger import bot
    -from spython.utils import write_file, write_json
    +from spython.logger import bot
    +from spython.utils import write_file, write_json
     
     import json
     import sys
    diff --git a/docs/api/modules/spython/client/shell.html b/docs/api/modules/spython/client/shell.html
    index 18adfeff..bc54b070 100644
    --- a/docs/api/modules/spython/client/shell.html
    +++ b/docs/api/modules/spython/client/shell.html
    @@ -21,10 +21,10 @@
       
         
           
    -        
    -        
    -        
    -        
    +        
    +        
    +        
    +        
         
         
     
    @@ -84,7 +84,6 @@
                 
                   
     
                 
    @@ -179,9 +178,9 @@ 

    Source code for spython.client.shell

         """prepare a client to embed in a shell with recipe parsers and writers.
         """
         # The client will announce itself (backend/database) unless it's get
    -    from spython.main import get_client
    -    from spython.main.parse import parsers
    -    from spython.main.parse import writers
    +    from spython.main import get_client
    +    from spython.main.parse import parsers
    +    from spython.main.parse import writers
     
         client = get_client()
     
    @@ -200,7 +199,7 @@ 

    Source code for spython.client.shell

         client = prepare_client(image)  # pylint: disable=unused-variable
     
         try:
    -        from IPython import embed
    +        from IPython import embed
         except ImportError:
             return python(image)
     
    diff --git a/docs/api/modules/spython/client/test.html b/docs/api/modules/spython/client/test.html
    index 02773fe6..565c47d4 100644
    --- a/docs/api/modules/spython/client/test.html
    +++ b/docs/api/modules/spython/client/test.html
    @@ -21,10 +21,10 @@
       
         
           
    -        
    -        
    -        
    -        
    +        
    +        
    +        
    +        
         
         
     
    @@ -84,7 +84,6 @@
                 
                   
     
                 
    diff --git a/docs/api/modules/spython/image.html b/docs/api/modules/spython/image.html
    index cbcbe5f1..9b178e96 100644
    --- a/docs/api/modules/spython/image.html
    +++ b/docs/api/modules/spython/image.html
    @@ -21,10 +21,10 @@
       
         
           
    -        
    -        
    -        
    -        
    +        
    +        
    +        
    +        
         
         
     
    @@ -84,7 +84,6 @@
                 
                   
     
                 
    @@ -157,18 +156,18 @@ 

    Source code for spython.image

     import hashlib
     import os
     import re
    -from spython.logger import bot
    -from spython.utils import split_uri
    +from spython.logger import bot
    +from spython.utils import split_uri
     
     
     
    [docs]class ImageBase(object): - def __str__(self): + def __str__(self): protocol = getattr(self, "protocol", None) if protocol: return "%s://%s" % (protocol, self.image) return self.image - def __repr__(self): + def __repr__(self): return self.__str__()
    [docs] def parse_image_name(self, image): @@ -186,7 +185,7 @@

    Source code for spython.image

     
     
     
    [docs]class Image(ImageBase): - def __init__(self, image=None): + def __init__(self, image=None): """An image here is an image file or a record. The user can choose to load the image when starting the client, or update the main client with an image. The image object is kept diff --git a/docs/api/modules/spython/image/cmd.html b/docs/api/modules/spython/image/cmd.html index dd20e8e2..fad7bb75 100644 --- a/docs/api/modules/spython/image/cmd.html +++ b/docs/api/modules/spython/image/cmd.html @@ -21,10 +21,10 @@ - - - - + + + + @@ -84,7 +84,6 @@ @@ -178,12 +177,12 @@

    Source code for spython.image.cmd

         class ImageClient(object):
             group = "image"
     
    -    from spython.main.base.logger import println
    -    from spython.main.base.command import init_command, run_command
    -    from .utils import compress, decompress
    -    from .create import create
    -    from .importcmd import importcmd
    -    from .export import export
    +    from spython.main.base.logger import println
    +    from spython.main.base.command import init_command, run_command
    +    from .utils import compress, decompress
    +    from .create import create
    +    from .importcmd import importcmd
    +    from .export import export
     
         ImageClient.create = create
         ImageClient.imprt = importcmd
    diff --git a/docs/api/modules/spython/image/cmd/create.html b/docs/api/modules/spython/image/cmd/create.html
    index c5798b69..e12a75d5 100644
    --- a/docs/api/modules/spython/image/cmd/create.html
    +++ b/docs/api/modules/spython/image/cmd/create.html
    @@ -21,10 +21,10 @@
       
         
           
    -        
    -        
    -        
    -        
    +        
    +        
    +        
    +        
         
         
     
    @@ -84,7 +84,6 @@
                 
                   
     
                 
    @@ -160,10 +159,10 @@ 

    Source code for spython.image.cmd.create

     
     
     import os
    -from spython.logger import bot
    +from spython.logger import bot
     
     
    -
    [docs]def create(self, image_path, size=1024, sudo=False): +
    [docs]def create(self, image_path, size=1024, sudo=False, singularity_options=None): """create will create a a new image Parameters @@ -171,13 +170,13 @@

    Source code for spython.image.cmd.create

             image_path: full path to image
             size: image sizein MiB, default is 1024MiB
             filesystem: supported file systems ext3/ext4 (ext[2/3]: default ext3
    -
    +        singularity_options: a list of options to provide to the singularity client
         """
    -    from spython.utils import check_install
    +    from spython.utils import check_install
     
         check_install()
     
    -    cmd = self.init_command("image.create")
    +    cmd = self.init_command("image.create", singularity_options)
         cmd = cmd + ["--size", str(size), image_path]
     
         output = self.run_command(cmd, sudo=sudo)
    diff --git a/docs/api/modules/spython/image/cmd/export.html b/docs/api/modules/spython/image/cmd/export.html
    index 39a521e9..99c7b3a4 100644
    --- a/docs/api/modules/spython/image/cmd/export.html
    +++ b/docs/api/modules/spython/image/cmd/export.html
    @@ -21,10 +21,10 @@
       
         
           
    -        
    -        
    -        
    -        
    +        
    +        
    +        
    +        
         
         
     
    @@ -84,7 +84,6 @@
                 
                   
     
                 
    @@ -159,7 +158,7 @@ 

    Source code for spython.image.cmd.export

     # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
     
     
    -from spython.logger import bot
    +from spython.logger import bot
     import tempfile
     
     
    @@ -173,7 +172,7 @@ 

    Source code for spython.image.cmd.export

            tmptar: if defined, use custom temporary path for tar export
     
         """
    -    from spython.utils import check_install
    +    from spython.utils import check_install
     
         check_install()
     
    diff --git a/docs/api/modules/spython/image/cmd/importcmd.html b/docs/api/modules/spython/image/cmd/importcmd.html
    index d377d7e3..60a69092 100644
    --- a/docs/api/modules/spython/image/cmd/importcmd.html
    +++ b/docs/api/modules/spython/image/cmd/importcmd.html
    @@ -21,10 +21,10 @@
       
         
           
    -        
    -        
    -        
    -        
    +        
    +        
    +        
    +        
         
         
     
    @@ -84,7 +84,6 @@
                 
                   
     
                 
    @@ -169,7 +168,7 @@ 

    Source code for spython.image.cmd.importcmd

            import_type: if not specified, imports whatever function is given
            
         """
    -    from spython.utils import check_install
    +    from spython.utils import check_install
     
         check_install()
     
    diff --git a/docs/api/modules/spython/image/cmd/utils.html b/docs/api/modules/spython/image/cmd/utils.html
    index 21995667..dfeb4765 100644
    --- a/docs/api/modules/spython/image/cmd/utils.html
    +++ b/docs/api/modules/spython/image/cmd/utils.html
    @@ -21,10 +21,10 @@
       
         
           
    -        
    -        
    -        
    -        
    +        
    +        
    +        
    +        
         
         
     
    @@ -84,7 +84,6 @@
                 
                   
     
                 
    @@ -160,7 +159,7 @@ 

    Source code for spython.image.cmd.utils

     
     
     import os
    -from spython.logger import bot
    +from spython.logger import bot
     
     
     
    [docs]def compress(self, image_path): diff --git a/docs/api/modules/spython/instance.html b/docs/api/modules/spython/instance.html index 35deff4c..b1e26d88 100644 --- a/docs/api/modules/spython/instance.html +++ b/docs/api/modules/spython/instance.html @@ -21,10 +21,10 @@ - - - - + + + + @@ -84,7 +84,6 @@ @@ -154,12 +153,12 @@

    Source code for spython.instance

     # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
     # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
     
    -from spython.image import ImageBase
    +from spython.image import ImageBase
     import os
     
     
     
    [docs]class Instance(ImageBase): - def __init__(self, image, start=True, name=None, **kwargs): + def __init__(self, image, start=True, name=None, **kwargs): """An instance is an image running as an instance with services. This class has functions appended under cmd/__init__ and is instantiated when the user calls Client. @@ -236,13 +235,13 @@

    Source code for spython.instance

             elif "container_image" in kwargs:
                 self._image = kwargs["container_image"]
     
    -    def __str__(self):
    +    def __str__(self):
             if hasattr(self, "name"):
                 if self.protocol:
                     return "%s://%s" % (self.protocol, self.name)
             return os.path.basename(self._image)
     
    -    def __repr__(self):
    +    def __repr__(self):
             return self.__str__()
    diff --git a/docs/api/modules/spython/instance/cmd.html b/docs/api/modules/spython/instance/cmd.html index 8bc0ccf4..8d18cc02 100644 --- a/docs/api/modules/spython/instance/cmd.html +++ b/docs/api/modules/spython/instance/cmd.html @@ -21,10 +21,10 @@ - - - - + + + + @@ -84,7 +84,6 @@ @@ -163,18 +162,18 @@

    Source code for spython.instance.cmd

             client via the environment variable MESSAGELEVEL.
     
         """
    -    from spython.instance import Instance
    +    from spython.instance import Instance
     
    -    from spython.main.base.logger import println
    -    from spython.main.instances import list_instances
    -    from spython.utils import run_command as run_cmd
    +    from spython.main.base.logger import println
    +    from spython.main.instances import list_instances
    +    from spython.utils import run_command as run_cmd
     
         # run_command uses run_cmd, but wraps to catch error
    -    from spython.main.base.command import init_command, run_command
    -    from spython.main.base.generate import RobotNamer
    -    from .start import start
    -    from .stop import stop
    -    from .logs import error_logs, output_logs, _logs
    +    from spython.main.base.command import init_command, run_command
    +    from spython.main.base.generate import RobotNamer
    +    from .start import start
    +    from .stop import stop
    +    from .logs import error_logs, output_logs, _logs
     
         Instance.RobotNamer = RobotNamer()
         Instance._init_command = init_command
    diff --git a/docs/api/modules/spython/instance/cmd/iutils.html b/docs/api/modules/spython/instance/cmd/iutils.html
    index 252083c2..88f1d6c5 100644
    --- a/docs/api/modules/spython/instance/cmd/iutils.html
    +++ b/docs/api/modules/spython/instance/cmd/iutils.html
    @@ -21,10 +21,10 @@
       
         
           
    -        
    -        
    -        
    -        
    +        
    +        
    +        
    +        
         
         
     
    @@ -84,7 +84,6 @@
                 
                   
     
                 
    @@ -158,8 +157,8 @@ 

    Source code for spython.instance.cmd.iutils

     # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
     # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
     
    -from spython.instance import Instance
    -from spython.logger import bot
    +from spython.instance import Instance
    +from spython.logger import bot
     
     
     
    [docs]def parse_table(table_string, header, remove_rows=1): @@ -189,11 +188,11 @@

    Source code for spython.instance.cmd.iutils

         return parsed
    -
    [docs]def get(self, name, return_json=False, quiet=False): +
    [docs]def get(self, name, return_json=False, quiet=False, singularity_options=None): """get is a list for a single instance. It is assumed to be running, and we need to look up the PID, etc. """ - from spython.utils import check_install + from spython.utils import check_install check_install() @@ -203,7 +202,7 @@

    Source code for spython.instance.cmd.iutils

         if "version 3" in self.version():
             subgroup = ["instance", "list"]
     
    -    cmd = self._init_command(subgroup)
    +    cmd = self._init_command(subgroup, singularity_options)
     
         cmd.append(name)
         output = self.run_command(cmd, quiet=True)
    diff --git a/docs/api/modules/spython/instance/cmd/logs.html b/docs/api/modules/spython/instance/cmd/logs.html
    index 00c56e4a..7035551f 100644
    --- a/docs/api/modules/spython/instance/cmd/logs.html
    +++ b/docs/api/modules/spython/instance/cmd/logs.html
    @@ -21,10 +21,10 @@
       
         
           
    -        
    -        
    -        
    -        
    +        
    +        
    +        
    +        
         
         
     
    @@ -84,7 +84,6 @@
                 
                   
     
                 
    @@ -159,8 +158,8 @@ 

    Source code for spython.instance.cmd.logs

     # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
     
     
    -from spython.utils import get_userhome, get_username
    -from spython.logger import bot
    +from spython.utils import get_userhome, get_username
    +from spython.logger import bot
     import platform
     import os
     
    @@ -195,7 +194,7 @@ 

    Source code for spython.instance.cmd.logs

         """A shared function to print log files. The only differing element is
            the extension (err or out)
         """
    -    from spython.utils import check_install, run_command
    +    from spython.utils import check_install, run_command
     
         check_install()
     
    diff --git a/docs/api/modules/spython/instance/cmd/start.html b/docs/api/modules/spython/instance/cmd/start.html
    index aea796e0..abed7842 100644
    --- a/docs/api/modules/spython/instance/cmd/start.html
    +++ b/docs/api/modules/spython/instance/cmd/start.html
    @@ -21,10 +21,10 @@
       
         
           
    -        
    -        
    -        
    -        
    +        
    +        
    +        
    +        
         
         
     
    @@ -84,7 +84,6 @@
                 
                   
     
                 
    @@ -159,11 +158,18 @@ 

    Source code for spython.instance.cmd.start

     # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
     
     
    -from spython.logger import bot
    +from spython.logger import bot
     
     
     
    [docs]def start( - self, image=None, name=None, args=None, sudo=False, options=None, capture=False + self, + image=None, + name=None, + args=None, + sudo=False, + options=None, + capture=False, + singularity_options=None, ): """start an instance. This is done by default when an instance is created. @@ -174,6 +180,7 @@

    Source code for spython.instance.cmd.start

            sudo: if the user wants to run the command with sudo
            capture: capture output, default is False. With True likely to hang.
            args: arguments to provide to the instance (supported Singularity 3.1+)
    +       singularity_options: a list of options to provide to the singularity client
            options: a list of tuples, each an option to give to the start command
                     [("--bind", "/tmp"),...]
     
    @@ -181,7 +188,7 @@ 

    Source code for spython.instance.cmd.start

            singularity [...] instance.start [...] <container path> <instance name>
     
         """
    -    from spython.utils import run_command, check_install
    +    from spython.utils import run_command, check_install
     
         check_install()
     
    @@ -203,7 +210,7 @@ 

    Source code for spython.instance.cmd.start

         if "version 3" in self.version():
             subgroup = ["instance", "start"]
     
    -    cmd = self._init_command(subgroup)
    +    cmd = self._init_command(subgroup, singularity_options)
     
         # Add options, if they are provided
         if not isinstance(options, list):
    diff --git a/docs/api/modules/spython/instance/cmd/stop.html b/docs/api/modules/spython/instance/cmd/stop.html
    index 048ce2c8..eb877067 100644
    --- a/docs/api/modules/spython/instance/cmd/stop.html
    +++ b/docs/api/modules/spython/instance/cmd/stop.html
    @@ -21,10 +21,10 @@
       
         
           
    -        
    -        
    -        
    -        
    +        
    +        
    +        
    +        
         
         
     
    @@ -84,7 +84,6 @@
                 
                   
     
                 
    @@ -159,16 +158,17 @@ 

    Source code for spython.instance.cmd.stop

     # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
     
     
    -from spython.logger import bot
    +from spython.logger import bot
     
     
    -
    [docs]def stop(self, name=None, sudo=False, timeout=None): +
    [docs]def stop(self, name=None, sudo=False, timeout=None, singularity_options=None): """stop an instance. This is done by default when an instance is created. Parameters ========== name: a name for the instance sudo: if the user wants to run the command with sudo + singularity_options: a list of options to provide to the singularity client timeout: forcebly kill non-stopped instance after the timeout specified in seconds @@ -176,7 +176,7 @@

    Source code for spython.instance.cmd.stop

            singularity [...] instance.stop [...] <instance name>
     
         """
    -    from spython.utils import check_install, run_command
    +    from spython.utils import check_install, run_command
     
         check_install()
     
    @@ -187,7 +187,7 @@ 

    Source code for spython.instance.cmd.stop

             if timeout:
                 subgroup += ["-t", str(timeout)]
     
    -    cmd = self._init_command(subgroup)
    +    cmd = self._init_command(subgroup, singularity_options)
     
         # If name is provided assume referencing an instance
         instance_name = self.name
    diff --git a/docs/api/modules/spython/logger/message.html b/docs/api/modules/spython/logger/message.html
    index e9997ce6..7dbb9f4b 100644
    --- a/docs/api/modules/spython/logger/message.html
    +++ b/docs/api/modules/spython/logger/message.html
    @@ -21,10 +21,10 @@
       
         
           
    -        
    -        
    -        
    -        
    +        
    +        
    +        
    +        
         
         
     
    @@ -84,7 +84,6 @@
                 
                   
     
                 
    @@ -156,8 +155,8 @@ 

    Source code for spython.logger.message

     
     import os
     import sys
    -from .spinner import Spinner
    -from spython.logger import decodeUtf8String
    +from .spinner import Spinner
    +from spython.logger import decodeUtf8String
     
     ABORT = -5
     CRITICAL = -4
    @@ -180,7 +179,7 @@ 

    Source code for spython.logger.message

     
     
     
    [docs]class SingularityMessage: - def __init__(self, MESSAGELEVEL=None): + def __init__(self, MESSAGELEVEL=None): self.level = get_logging_level() self.history = [] self.errorStream = sys.stderr diff --git a/docs/api/modules/spython/logger/progress.html b/docs/api/modules/spython/logger/progress.html index af986283..4c9c609a 100644 --- a/docs/api/modules/spython/logger/progress.html +++ b/docs/api/modules/spython/logger/progress.html @@ -21,10 +21,10 @@ - - - - + + + + @@ -84,7 +84,6 @@ @@ -157,7 +156,7 @@

    Source code for spython.logger.progress

     # Credit to base code goes to https://github.com/kennethreitz/clint/blob/master/clint/textui/progress.py
     
     
    -from __future__ import absolute_import
    +from __future__ import absolute_import
     
     import sys
     import time
    @@ -176,14 +175,14 @@ 

    Source code for spython.logger.progress

     
     
     
    [docs]class ProgressBar(object): - def __enter__(self): + def __enter__(self): return self - def __exit__(self, exc_type, exc_val, exc_tb): + def __exit__(self, exc_type, exc_val, exc_tb): self.done() return False # we're not suppressing exceptions - def __init__( + def __init__( self, label="", width=32, diff --git a/docs/api/modules/spython/logger/spinner.html b/docs/api/modules/spython/logger/spinner.html index 9dc6c981..89f9a136 100644 --- a/docs/api/modules/spython/logger/spinner.html +++ b/docs/api/modules/spython/logger/spinner.html @@ -21,10 +21,10 @@ - - - - + + + + @@ -84,7 +84,6 @@ @@ -158,7 +157,7 @@

    Source code for spython.logger.spinner

     import sys
     import time
     import threading
    -from random import choice
    +from random import choice
     
     
     
    [docs]class Spinner: @@ -189,7 +188,7 @@

    Source code for spython.logger.spinner

     
             return generator
    - def __init__(self, delay=None, generator=None): + def __init__(self, delay=None, generator=None): generator = self.select_generator(generator) if generator == "cursor": diff --git a/docs/api/modules/spython/main.html b/docs/api/modules/spython/main.html index 57003d4e..eddf3e2c 100644 --- a/docs/api/modules/spython/main.html +++ b/docs/api/modules/spython/main.html @@ -21,10 +21,10 @@ - - - - + + + + @@ -84,7 +84,6 @@ @@ -166,22 +165,22 @@

    Source code for spython.main

            debug: turn on debugging mode
     
         """
    -    from spython.utils import get_singularity_version
    -    from .base import Client as client
    +    from spython.utils import get_singularity_version
    +    from .base import Client as client
     
         client.quiet = quiet
         client.debug = debug
     
         # Do imports here, can be customized
    -    from .apps import apps
    -    from .build import build
    -    from .execute import execute, shell
    -    from .help import helpcmd
    -    from .inspect import inspect
    -    from .instances import list_instances, stopall  # global instance commands
    -    from .run import run
    -    from .pull import pull
    -    from .export import export, _export
    +    from .apps import apps
    +    from .build import build
    +    from .execute import execute, shell
    +    from .help import helpcmd
    +    from .inspect import inspect
    +    from .instances import list_instances, stopall  # global instance commands
    +    from .run import run
    +    from .pull import pull
    +    from .export import export, _export
     
         # Actions
         client.apps = apps
    @@ -197,12 +196,12 @@ 

    Source code for spython.main

         client.pull = pull
     
         # Command Groups, Images
    -    from spython.image.cmd import generate_image_commands  # deprecated
    +    from spython.image.cmd import generate_image_commands  # deprecated
     
         client.image = generate_image_commands()
     
         # Commands Groups, Instances
    -    from spython.instance.cmd import (
    +    from spython.instance.cmd import (
             generate_instance_commands,
         )  # instance level commands
     
    @@ -212,7 +211,7 @@ 

    Source code for spython.main

     
         # Commands Groups, OCI (Singularity version 3 and up)
         if "version 3" in get_singularity_version():
    -        from spython.oci.cmd import generate_oci_commands
    +        from spython.oci.cmd import generate_oci_commands
     
             client.oci = generate_oci_commands()()  # first () runs function, second
             # initializes OciImage class
    diff --git a/docs/api/modules/spython/main/apps.html b/docs/api/modules/spython/main/apps.html
    index 82509b9c..646c9513 100644
    --- a/docs/api/modules/spython/main/apps.html
    +++ b/docs/api/modules/spython/main/apps.html
    @@ -21,10 +21,10 @@
       
         
           
    -        
    -        
    -        
    -        
    +        
    +        
    +        
    +        
         
         
     
    @@ -84,7 +84,6 @@
                 
                   
     
                 
    @@ -162,7 +161,8 @@ 

    Source code for spython.main.apps

            return list of SCIF apps in image. The Singularity software serves
            a scientific filesystem integration that will install apps to
            /scif/apps and associated data to /scif/data. For more information 
    -       about SCIF, see https://sci-f.github.io
    +       about SCIF, see https://sci-f.github.io. Note that this seems
    +       to be deprecated in Singularity 3.x.
     
            Parameters
            ==========
    @@ -170,7 +170,7 @@ 

    Source code for spython.main.apps

            image_path: full path to the image
     
         """
    -    from spython.utils import check_install
    +    from spython.utils import check_install
     
         check_install()
     
    diff --git a/docs/api/modules/spython/main/base.html b/docs/api/modules/spython/main/base.html
    index f301a9bf..18e9ef9f 100644
    --- a/docs/api/modules/spython/main/base.html
    +++ b/docs/api/modules/spython/main/base.html
    @@ -21,10 +21,10 @@
       
         
           
    -        
    -        
    -        
    -        
    +        
    +        
    +        
    +        
         
         
     
    @@ -84,7 +84,6 @@
                 
                   
     
                 
    @@ -157,18 +156,18 @@ 

    Source code for spython.main.base

     # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
     
     
    -from spython.logger import bot
    -from spython.utils import (
    +from spython.logger import bot
    +from spython.utils import (
         check_install,
         get_singularity_version,
         get_singularity_version_info,
     )
     
    -from .command import generate_bind_list, init_command, run_command
    -from .flags import parse_verbosity
    -from .sutils import get_uri, load, setenv, get_filename
    -from .logger import println, init_level
    -from .generate import RobotNamer
    +from .command import generate_bind_list, init_command, run_command
    +from .flags import parse_verbosity
    +from .sutils import get_uri, load, setenv, get_filename
    +from .logger import println, init_level
    +from .generate import RobotNamer
     
     import json
     import sys
    @@ -177,17 +176,17 @@ 

    Source code for spython.main.base

     
     
     
    [docs]class Client: - def __str__(self): + def __str__(self): base = "[singularity-python]" if hasattr(self, "simage"): if self.simage.image not in [None, ""]: base = "%s[%s]" % (base, self.simage) return base - def __repr__(self): + def __repr__(self): return self.__str__() - def __init__(self): + def __init__(self): """the base client for singularity, will have commands added to it. upon init, store verbosity requested in environment MESSAGELEVEL. """ diff --git a/docs/api/modules/spython/main/base/command.html b/docs/api/modules/spython/main/base/command.html index 63d56a4d..ea2cf74e 100644 --- a/docs/api/modules/spython/main/base/command.html +++ b/docs/api/modules/spython/main/base/command.html @@ -21,10 +21,10 @@ - - - - + + + + @@ -84,7 +84,6 @@ @@ -159,9 +158,9 @@

    Source code for spython.main.base.command

     # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
     
     
    -from spython.utils import run_command as run_cmd
    +from spython.utils import run_command as run_cmd
     
    -from spython.logger import bot
    +from spython.logger import bot
     
     import subprocess
     import sys
    @@ -174,13 +173,13 @@ 

    Source code for spython.main.base.command

            Parameters
            ==========
            action: the main action to perform (e.g., build)
    -       flags: one or more additional flags (e.g, volumes) 
    -              not implemented yet.
    +       flags: one or more additional singularity options
         """
    +    flags = flags or []
     
         if not isinstance(action, list):
             action = [action]
    -    cmd = ["singularity"] + action
    +    cmd = ["singularity"] + flags + action
     
         if self.quiet:
             cmd.insert(1, "--quiet")
    diff --git a/docs/api/modules/spython/main/base/flags.html b/docs/api/modules/spython/main/base/flags.html
    index bd09c8f0..f3399456 100644
    --- a/docs/api/modules/spython/main/base/flags.html
    +++ b/docs/api/modules/spython/main/base/flags.html
    @@ -21,10 +21,10 @@
       
         
           
    -        
    -        
    -        
    -        
    +        
    +        
    +        
    +        
         
         
     
    @@ -84,7 +84,6 @@
                 
                   
     
                 
    diff --git a/docs/api/modules/spython/main/base/generate.html b/docs/api/modules/spython/main/base/generate.html
    index 01db558b..23adc89f 100644
    --- a/docs/api/modules/spython/main/base/generate.html
    +++ b/docs/api/modules/spython/main/base/generate.html
    @@ -21,10 +21,10 @@
       
         
           
    -        
    -        
    -        
    -        
    +        
    +        
    +        
    +        
         
         
     
    @@ -84,7 +84,6 @@
                 
                   
     
                 
    @@ -161,7 +160,7 @@ 

    Source code for spython.main.base.generate

     # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
     
     
    -from random import choice
    +from random import choice
     
     
     
    [docs]class RobotNamer: diff --git a/docs/api/modules/spython/main/base/logger.html b/docs/api/modules/spython/main/base/logger.html index 830c8317..5869ae99 100644 --- a/docs/api/modules/spython/main/base/logger.html +++ b/docs/api/modules/spython/main/base/logger.html @@ -21,10 +21,10 @@ - - - - + + + + @@ -84,7 +84,6 @@ @@ -160,7 +159,7 @@

    Source code for spython.main.base.logger

     
     
     import os
    -from spython.logger import decodeUtf8String
    +from spython.logger import decodeUtf8String
     
     
     
    [docs]def init_level(self, quiet=False): diff --git a/docs/api/modules/spython/main/base/sutils.html b/docs/api/modules/spython/main/base/sutils.html index ac3a0899..df78a903 100644 --- a/docs/api/modules/spython/main/base/sutils.html +++ b/docs/api/modules/spython/main/base/sutils.html @@ -21,10 +21,10 @@ - - - - + + + + @@ -84,7 +84,6 @@ @@ -162,7 +161,7 @@

    Source code for spython.main.base.sutils

     # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
     
     
    -from spython.logger import bot
    +from spython.logger import bot
     import os
     import re
     
    @@ -175,8 +174,8 @@ 

    Source code for spython.main.base.sutils

            image: the image path or uri to load (e.g., docker://ubuntu 
     
         """
    -    from spython.image import Image
    -    from spython.instance import Instance
    +    from spython.image import Image
    +    from spython.instance import Instance
     
         self.simage = Image(image)
     
    diff --git a/docs/api/modules/spython/main/build.html b/docs/api/modules/spython/main/build.html
    index 9ff10c0f..c257091b 100644
    --- a/docs/api/modules/spython/main/build.html
    +++ b/docs/api/modules/spython/main/build.html
    @@ -21,10 +21,10 @@
       
         
           
    -        
    -        
    -        
    -        
    +        
    +        
    +        
    +        
         
         
     
    @@ -84,7 +84,6 @@
                 
                   
     
                 
    @@ -157,8 +156,8 @@ 

    Source code for spython.main.build

     # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
     
     
    -from spython.logger import bot
    -from spython.utils import stream_command
    +from spython.logger import bot
    +from spython.utils import stream_command
     import re
     import os
     
    @@ -180,6 +179,7 @@ 

    Source code for spython.main.build

         quiet=False,
         return_result=False,
         sudo_options=None,
    +    singularity_options=None,
     ):
     
         """build a singularity image, optionally for an isolated build
    @@ -205,18 +205,18 @@ 

    Source code for spython.main.build

            sudo: give sudo to the command (or not) default is True for build
            sudo_options: options to pass to sudo (e.g. --preserve-env=SINGULARITY_CACHEDIR,SINGULARITY_TMPDIR)
            options: for all other options, specify them in this list.   
    +       singularity_options: a list of options to provide to the singularity client
            quiet: quiet verbose printing from the client.
            return_result: if True, return complete error code / message dictionary
         """
    -    from spython.utils import check_install
    +    from spython.utils import check_install
     
         check_install()
     
    -    cmd = self._init_command("build")
    +    cmd = self._init_command("build", singularity_options)
     
         # If no extra options
    -    if not options:
    -        options = []
    +    options = options or []
     
         if "version 3" in self.version():
             ext = "sif"
    @@ -237,7 +237,7 @@ 

    Source code for spython.main.build

                 bot.exit("Cannot find %s, exiting." % image)
     
         if image is None:
    -        if re.search("(docker|shub)://", recipe) and not robot_name:
    +        if re.search("(docker|shub|library)://", recipe) and not robot_name:
                 image = self._get_filename(recipe, ext)
             else:
                 image = "%s.%s" % (self.RobotNamer.generate(), ext)
    diff --git a/docs/api/modules/spython/main/execute.html b/docs/api/modules/spython/main/execute.html
    index 6114baaf..e70326f6 100644
    --- a/docs/api/modules/spython/main/execute.html
    +++ b/docs/api/modules/spython/main/execute.html
    @@ -21,10 +21,10 @@
       
         
           
    -        
    -        
    -        
    -        
    +        
    +        
    +        
    +        
         
         
     
    @@ -84,7 +84,6 @@
                 
                   
     
                 
    @@ -157,8 +156,8 @@ 

    Source code for spython.main.execute

     # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
     
     
    -from spython.logger import bot
    -from spython.utils import stream_command, which
    +from spython.logger import bot
    +from spython.utils import stream_command, which
     
     import os
     
    @@ -175,6 +174,7 @@ 

    Source code for spython.main.execute

         nv=False,
         return_result=False,
         options=None,
    +    singularity_options=None,
         sudo=False,
         quiet=True,
     ):
    @@ -190,6 +190,7 @@ 

    Source code for spython.main.execute

             contain: This option disables the automatic sharing of writable
                      filesystems on your host
             options: an optional list of options to provide to execute.
    +        singularity_options: a list of options to provide to the singularity client
             bind: list or single string of bind paths.
                  This option allows you to map directories on your host system to
                  directories within your container using bind mounts
    @@ -197,11 +198,11 @@ 

    Source code for spython.main.execute

             return_result: if True, return entire json object with return code
                            and message result not (default)
         """
    -    from spython.utils import check_install
    +    from spython.utils import check_install
     
         check_install()
     
    -    cmd = self._init_command("exec")
    +    cmd = self._init_command("exec", singularity_options)
     
         # nv option leverages any GPU cards
         if nv:
    @@ -265,6 +266,7 @@ 

    Source code for spython.main.execute

         bind=None,
         nv=False,
         options=None,
    +    singularity_options=None,
         sudo=False,
     ):
         """ shell into a container. A user is advised to use singularity to do
    @@ -279,16 +281,17 @@ 

    Source code for spython.main.execute

             contain: This option disables the automatic sharing of writable
                      filesystems on your host
             options: an optional list of options to provide to shell.
    +        singularity_options: a list of options to provide to the singularity client
             bind: list or single string of bind paths.
                  This option allows you to map directories on your host system to
                  directories within your container using bind mounts
             nv: if True, load Nvidia Drivers in runtime (default False)
         """
    -    from spython.utils import check_install
    +    from spython.utils import check_install
     
         check_install()
     
    -    cmd = self._init_command("shell")
    +    cmd = self._init_command("shell", singularity_options)
     
         # nv option leverages any GPU cards
         if nv:
    diff --git a/docs/api/modules/spython/main/export.html b/docs/api/modules/spython/main/export.html
    index 0dca8a0a..6648a83a 100644
    --- a/docs/api/modules/spython/main/export.html
    +++ b/docs/api/modules/spython/main/export.html
    @@ -21,10 +21,10 @@
       
         
           
    -        
    -        
    -        
    -        
    +        
    +        
    +        
    +        
         
         
     
    @@ -84,7 +84,6 @@
                 
                   
     
                 
    @@ -157,13 +156,21 @@ 

    Source code for spython.main.export

     # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
     
     
    -from spython.logger import bot
    +from spython.logger import bot
     import tempfile
     import shutil
     import os
     
     
    -def export(self, image_path, pipe=False, output_file=None, command=None, sudo=False):
    +def export(
    +    self,
    +    image_path,
    +    pipe=False,
    +    output_file=None,
    +    command=None,
    +    sudo=False,
    +    singularity_options=None,
    +):
     
         """export will export an image, sudo must be used. If we have Singularity
            versions after 3, export is replaced with building into a sandbox.
    @@ -172,10 +179,11 @@ 

    Source code for spython.main.export

            ==========
            image_path: full path to image
            pipe: export to pipe and not file (default, False)
    +       singularity_options: a list of options to provide to the singularity client
            output_file: if pipe=False, export tar to this file. If not specified, 
            will generate temporary directory.
         """
    -    from spython.utils import check_install
    +    from spython.utils import check_install
     
         check_install()
     
    @@ -191,19 +199,35 @@ 

    Source code for spython.main.export

                 output_file = self._get_filename(basename, "sandbox", pwd=False)
     
             return self.build(
    -            recipe=image_path, image=output_file, sandbox=True, force=True, sudo=sudo
    +            recipe=image_path,
    +            image=output_file,
    +            sandbox=True,
    +            force=True,
    +            sudo=sudo,
    +            singularity_options=singularity_options,
             )
     
         # If not version 3, run deprecated command
         elif "2.5" in self.version():
             return self._export(
    -            image_path=image_path, pipe=pipe, output_file=output_file, command=command
    +            image_path=image_path,
    +            pipe=pipe,
    +            output_file=output_file,
    +            command=command,
    +            singularity_options=singularity_options,
             )
     
         bot.warning("Unsupported version of Singularity, %s" % self.version())
     
     
    -def _export(self, image_path, pipe=False, output_file=None, command=None):
    +def _export(
    +    self,
    +    image_path,
    +    pipe=False,
    +    output_file=None,
    +    command=None,
    +    singularity_options=None,
    +):
         """ the older deprecated function, running export for previous
                    versions of Singularity that support it
     
    @@ -221,7 +245,7 @@ 

    Source code for spython.main.export

     
         """
         sudo = True
    -    cmd = self._init_command("export")
    +    cmd = self._init_command("export", singularity_options)
     
         # If the user has specified export to pipe, we don't need a file
         if pipe:
    diff --git a/docs/api/modules/spython/main/help.html b/docs/api/modules/spython/main/help.html
    index 43bba4fa..2b95ed8e 100644
    --- a/docs/api/modules/spython/main/help.html
    +++ b/docs/api/modules/spython/main/help.html
    @@ -21,10 +21,10 @@
       
         
           
    -        
    -        
    -        
    -        
    +        
    +        
    +        
    +        
         
         
     
    @@ -84,7 +84,6 @@
                 
                   
     
                 
    @@ -165,7 +164,7 @@ 

    Source code for spython.main.help

             command: the command to get help for, if none, prints general help
     
         """
    -    from spython.utils import check_install
    +    from spython.utils import check_install
     
         check_install()
     
    diff --git a/docs/api/modules/spython/main/inspect.html b/docs/api/modules/spython/main/inspect.html
    index f98e6a88..f52ac884 100644
    --- a/docs/api/modules/spython/main/inspect.html
    +++ b/docs/api/modules/spython/main/inspect.html
    @@ -21,10 +21,10 @@
       
         
           
    -        
    -        
    -        
    -        
    +        
    +        
    +        
    +        
         
         
     
    @@ -84,7 +84,6 @@
                 
                   
     
                 
    @@ -157,12 +156,14 @@ 

    Source code for spython.main.inspect

     # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
     
     import json as jsonp
    -from spython.logger import bot
    +from spython.logger import bot
     
    -from spython.utils import check_install, run_command
    +from spython.utils import check_install, run_command
     
     
    -
    [docs]def inspect(self, image=None, json=True, app=None, quiet=True): +
    [docs]def inspect( + self, image=None, json=True, app=None, quiet=True, singularity_options=None +): """inspect will show labels, defile, runscript, and tests for an image Parameters @@ -171,6 +172,7 @@

    Source code for spython.main.inspect

            json: print json instead of raw text (default True)
            quiet: Don't print result to the screen (default True)
            app: if defined, return help in context of an app
    +       singularity_options: a list of options to provide to the singularity client
     
         """
         check_install()
    @@ -183,7 +185,7 @@ 

    Source code for spython.main.inspect

         if not image:
             bot.exit("Please provide an image to inspect.")
     
    -    cmd = self._init_command("inspect")
    +    cmd = self._init_command("inspect", singularity_options)
         if app:
             cmd = cmd + ["--app", app]
     
    diff --git a/docs/api/modules/spython/main/instances.html b/docs/api/modules/spython/main/instances.html
    index 1872a090..0dd2a102 100644
    --- a/docs/api/modules/spython/main/instances.html
    +++ b/docs/api/modules/spython/main/instances.html
    @@ -21,10 +21,10 @@
       
         
           
    -        
    -        
    -        
    -        
    +        
    +        
    +        
    +        
         
         
     
    @@ -84,7 +84,6 @@
                 
                   
     
                 
    @@ -157,11 +156,18 @@ 

    Source code for spython.main.instances

     # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
     
     
    -from spython.logger import bot
    -from spython.utils import run_command
    +from spython.logger import bot
    +from spython.utils import run_command
     
     
    -
    [docs]def list_instances(self, name=None, return_json=False, quiet=False, sudo=False): +
    [docs]def list_instances( + self, + name=None, + return_json=False, + quiet=False, + sudo=False, + singularity_options=None, +): """list instances. For Singularity, this is provided as a command sub group. @@ -174,6 +180,7 @@

    Source code for spython.main.instances

            ==========
            return_json: return a json list of instances instead of objects (False)
            name: if defined, return the list for just one instance (used to ged pid)
    +       singularity_options: a list of options to provide to the singularity client
     
            Return Code  --   Reason
            0 -- Instances Found
    @@ -181,8 +188,8 @@ 

    Source code for spython.main.instances

            255 -- Couldn't get UID
     
         """
    -    from spython.instance.cmd.iutils import parse_table
    -    from spython.utils import check_install
    +    from spython.instance.cmd.iutils import parse_table
    +    from spython.utils import check_install
     
         check_install()
     
    @@ -191,7 +198,7 @@ 

    Source code for spython.main.instances

         if "version 3" in self.version():
             subgroup = ["instance", "list"]
     
    -    cmd = self._init_command(subgroup)
    +    cmd = self._init_command(subgroup, singularity_options)
     
         # If the user has provided a name, we want to see a particular instance
         if name is not None:
    @@ -255,7 +262,7 @@ 

    Source code for spython.main.instances

         return instances
    -
    [docs]def stopall(self, sudo=False, quiet=True): +
    [docs]def stopall(self, sudo=False, quiet=True, singularity_options=None): """stop ALL instances. This command is only added to the command group as it doesn't make sense to call from a single instance @@ -265,7 +272,7 @@

    Source code for spython.main.instances

                  instances)
     
         """
    -    from spython.utils import check_install
    +    from spython.utils import check_install
     
         check_install()
     
    @@ -274,7 +281,7 @@ 

    Source code for spython.main.instances

         if "version 3" in self.version():
             subgroup = ["instance", "stop"]
     
    -    cmd = self._init_command(subgroup)
    +    cmd = self._init_command(subgroup, singularity_options)
         cmd = cmd + ["--all"]
         output = run_command(cmd, sudo=sudo, quiet=quiet)
     
    diff --git a/docs/api/modules/spython/main/parse/recipe.html b/docs/api/modules/spython/main/parse/recipe.html
    index e9c29803..a4242f0f 100644
    --- a/docs/api/modules/spython/main/parse/recipe.html
    +++ b/docs/api/modules/spython/main/parse/recipe.html
    @@ -21,10 +21,10 @@
       
         
           
    -        
    -        
    -        
    -        
    +        
    +        
    +        
    +        
         
         
     
    @@ -84,7 +84,6 @@
                 
                   
     
                 
    @@ -172,7 +171,7 @@ 

    Source code for spython.main.parse.recipe

     
         """
     
    -    def __init__(self, recipe=None):
    +    def __init__(self, recipe=None):
     
             self.cmd = None
             self.comments = []
    @@ -188,7 +187,7 @@ 

    Source code for spython.main.parse.recipe

     
             self.source = recipe
     
    -    def __str__(self):
    +    def __str__(self):
             """ show the user the recipe object, along with the type. E.g.,
            
                 [spython-recipe][source:Singularity]
    @@ -231,7 +230,7 @@ 

    Source code for spython.main.parse.recipe

     
             return result
    - def __repr__(self): + def __repr__(self): return self.__str__()
    diff --git a/docs/api/modules/spython/main/pull.html b/docs/api/modules/spython/main/pull.html index 6e68f601..4fdb732e 100644 --- a/docs/api/modules/spython/main/pull.html +++ b/docs/api/modules/spython/main/pull.html @@ -21,10 +21,10 @@ - - - - + + + + @@ -84,7 +84,6 @@ @@ -157,8 +156,8 @@

    Source code for spython.main.pull

     # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
     
     
    -from spython.logger import bot
    -from spython.utils import stream_command, ScopedEnvVar
    +from spython.logger import bot
    +from spython.utils import stream_command, ScopedEnvVar
     import os
     import re
     
    @@ -173,6 +172,7 @@ 

    Source code for spython.main.pull

         capture=False,
         stream=False,
         quiet=False,
    +    singularity_options=None,
     ):
     
         """pull will pull a singularity hub or Docker image
    @@ -180,6 +180,7 @@ 

    Source code for spython.main.pull

            Parameters
            ==========
            image: the complete image uri. If not provided, the client loaded is used
    +       singularity_options: a list of options to provide to the singularity client
            pull_folder: if not defined, pulls to $PWD (''). If defined, pulls to
                         user specified location instead.
     
    @@ -189,11 +190,11 @@ 

    Source code for spython.main.pull

            ext: if no name specified, the default extension to use.
     
         """
    -    from spython.utils import check_install
    +    from spython.utils import check_install
     
         check_install()
     
    -    cmd = self._init_command("pull")
    +    cmd = self._init_command("pull", singularity_options)
     
         # Quiet is honored if set by the client, or user
         quiet = quiet or self.quiet
    @@ -209,9 +210,9 @@ 

    Source code for spython.main.pull

         if image is None:
             bot.exit("You must provide an image uri, or use client.load() first.")
     
    -    # Singularity Only supports shub and Docker pull
    -    if not re.search("^(shub|docker)://", image):
    -        bot.exit("pull only valid for docker and shub. Use sregistry client.")
    +    # Singularity Only supports shub, docker and library pull
    +    if not re.search("^(shub|docker|library)://", image):
    +        bot.exit("pull only valid for docker, shub and library. Use sregistry client.")
     
         # If we still don't have a custom name, base off of image uri.
         if name is None:
    diff --git a/docs/api/modules/spython/main/run.html b/docs/api/modules/spython/main/run.html
    index 9f6892da..5d8f71aa 100644
    --- a/docs/api/modules/spython/main/run.html
    +++ b/docs/api/modules/spython/main/run.html
    @@ -21,10 +21,10 @@
       
         
           
    -        
    -        
    -        
    -        
    +        
    +        
    +        
    +        
         
         
     
    @@ -84,7 +84,6 @@
                 
                   
     
                 
    @@ -157,8 +156,8 @@ 

    Source code for spython.main.run

     # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
     
     
    -from spython.logger import bot
    -from spython.utils import stream_command
    +from spython.logger import bot
    +from spython.utils import stream_command
     import json
     
     
    @@ -174,6 +173,7 @@ 

    Source code for spython.main.run

         stream=False,
         nv=False,
         options=None,
    +    singularity_options=None,
         return_result=False,
     ):
         """
    @@ -187,6 +187,7 @@ 

    Source code for spython.main.run

             app: if not None, execute a command in context of an app
             writable: This option makes the file system accessible as read/write
             options: an optional list of options to provide to run.
    +        singularity_options: a list of options to provide to the singularity client
             contain: This option disables the automatic sharing of writable
                      filesystems on your host
             bind: list or single string of bind paths.
    @@ -198,11 +199,11 @@ 

    Source code for spython.main.run

                  and message result (default is False)
     
         """
    -    from spython.utils import check_install
    +    from spython.utils import check_install
     
         check_install()
     
    -    cmd = self._init_command("run")
    +    cmd = self._init_command("run", singularity_options)
     
         # nv option leverages any GPU cards
         if nv:
    diff --git a/docs/api/modules/spython/oci.html b/docs/api/modules/spython/oci.html
    index 30547860..f48d954d 100644
    --- a/docs/api/modules/spython/oci.html
    +++ b/docs/api/modules/spython/oci.html
    @@ -21,10 +21,10 @@
       
         
           
    -        
    -        
    -        
    -        
    +        
    +        
    +        
    +        
         
         
     
    @@ -84,7 +84,6 @@
                 
                   
     
                 
    @@ -154,8 +153,8 @@ 

    Source code for spython.oci

     # Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
     # with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
     
    -from spython.image import ImageBase
    -from spython.logger import bot
    +from spython.image import ImageBase
    +from spython.logger import bot
     import os
     
     
    @@ -164,7 +163,7 @@ 

    Source code for spython.oci

         # Default functions of client don't use sudo
         sudo = False
     
    -    def __init__(
    +    def __init__(
             self, container_id=None, bundle=None, create=True, sudo=True, **kwargs
         ):
             """ An Oci Image is an Image Base with OCI functions appended
    @@ -218,12 +217,12 @@ 

    Source code for spython.oci

     
         # Naming
     
    -    def __str__(self):
    +    def __str__(self):
             if self.container_id is not None:
                 return "[singularity-python-oci:%s]" % self.container_id
             return "[singularity-python-oci]"
     
    -    def __repr__(self):
    +    def __repr__(self):
             return self.__str__()
     
         # Commands
    @@ -279,7 +278,7 @@ 

    Source code for spython.oci

                        not implemented yet.
     
             """
    -        from spython.main.base.command import init_command
    +        from spython.main.base.command import init_command
     
             if not isinstance(action, list):
                 action = [action]
    diff --git a/docs/api/modules/spython/tests/test_client.html b/docs/api/modules/spython/tests/test_client.html
    deleted file mode 100644
    index bd21d493..00000000
    --- a/docs/api/modules/spython/tests/test_client.html
    +++ /dev/null
    @@ -1,247 +0,0 @@
    -
    -
    -
    -
    -  
    -
    -  
    -  
    -  
    -  
    -  spython.tests.test_client — Singularity Python API 1 documentation
    -  
    -
    -  
    -  
    -  
    -  
    -
    -  
    -  
    -  
    -    
    -      
    -        
    -        
    -        
    -        
    -    
    -    
    -
    -    
    -
    -  
    -  
    -  
    -    
    -     
    -
    -
    -
    -
    -   
    -  
    - - - -
    - - - - - -
    - -
    - - - - - - - - - - - - - - - - - -
    - -
      - -
    • Docs »
    • - -
    • Module code »
    • - -
    • spython.tests.test_client
    • - - -
    • - -
    • - -
    - - -
    -
    -
    -
    - -

    Source code for spython.tests.test_client

    -#!/usr/bin/python
    -
    -# Copyright (C) 2017-2020 Vanessa Sochat.
    -
    -# This Source Code Form is subject to the terms of the
    -# Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
    -# with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
    -
    -from spython.main import Client
    -import shutil
    -import os
    -
    -
    -
    [docs]def test_build_from_docker(tmp_path): - container = str(tmp_path / "container.sif") - - created_container = Client.build( - "docker://busybox:1.30.1", image=container, sudo=False - ) - assert created_container == container - assert os.path.exists(created_container)
    - - -
    [docs]def test_export(): - sandbox = "busybox:1.30.sandbox" - created_sandbox = Client.export("docker://busybox:1.30.1") - assert created_sandbox == sandbox - assert os.path.exists(created_sandbox) - shutil.rmtree(created_sandbox)
    - - -
    [docs]def test_docker_pull(docker_container): - tmp_path, container = docker_container - print(container) - ext = "sif" if Client.version_info().major >= 3 else "simg" - assert container == str(tmp_path / ("busybox:1.30.1." + ext)) - assert os.path.exists(container)
    - - -
    [docs]def test_execute(docker_container): - result = Client.execute(docker_container[1], "ls /") - print(result) - assert "tmp\nusr\nvar" in result
    - - -
    [docs]def test_execute_with_return_code(docker_container): - result = Client.execute(docker_container[1], "ls /", return_result=True) - print(result) - assert "tmp\nusr\nvar" in result["message"] - assert result["return_code"] == 0
    - - -
    [docs]def test_inspect(docker_container): - result = Client.inspect(docker_container[1]) - assert "attributes" in result or "data" in result
    -
    - -
    - -
    - - -
    -
    - -
    - -
    - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/api/modules/spython/tests/test_instances.html b/docs/api/modules/spython/tests/test_instances.html deleted file mode 100644 index 53ea8780..00000000 --- a/docs/api/modules/spython/tests/test_instances.html +++ /dev/null @@ -1,261 +0,0 @@ - - - - - - - - - - - spython.tests.test_instances — Singularity Python API 1 documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - -
    - - - - - -
    - -
    - - - - - - - - - - - - - - - - - -
    - -
      - -
    • Docs »
    • - -
    • Module code »
    • - -
    • spython.tests.test_instances
    • - - -
    • - -
    • - -
    - - -
    -
    -
    -
    - -

    Source code for spython.tests.test_instances

    -#!/usr/bin/python
    -
    -# Copyright (C) 2017-2020 Vanessa Sochat.
    -
    -# This Source Code Form is subject to the terms of the
    -# Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
    -# with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
    -
    -import pytest
    -from spython.main import Client
    -
    -
    -
    [docs]def test_instance_class(): - instance = Client.instance("docker://ubuntu", start=False) - assert instance.get_uri() == "instance://" + instance.name - assert instance.name != "" - - name = "coolName" - instance = Client.instance("docker://busybox:1.30.1", start=False, name=name) - assert instance.get_uri() == "instance://" + instance.name - assert instance.name == name
    - - -
    [docs]def test_has_no_instances(): - instances = Client.instances() - assert instances == []
    - - -
    [docs]class TestInstanceFuncs(object): -
    [docs] @pytest.fixture(autouse=True) - def cleanup(self): - yield - Client.instance_stopall()
    - -
    [docs] def test_instance_cmds(self, docker_container): - image = docker_container[1] - myinstance = Client.instance(image) - assert myinstance.get_uri().startswith("instance://") - - print("...Case 2: List instances") - instances = Client.instances() - assert len(instances) == 1 - instances = Client.instances(return_json=True) - assert len(instances) == 1 - assert isinstance(instances[0], dict) - - print("...Case 3: Commands to instances") - result = Client.execute(myinstance, ["echo", "hello"]) - assert result == "hello\n" - - print("...Case 4: Return value from instance") - result = Client.execute(myinstance, "ls /", return_result=True) - print(result) - assert "tmp\nusr\nvar" in result["message"] - assert result["return_code"] == 0 - - print("...Case 5: Stop instances") - myinstance.stop() - instances = Client.instances() - assert instances == [] - myinstance1 = Client.instance(image) - myinstance2 = Client.instance(image) - assert myinstance1 is not None - assert myinstance2 is not None - instances = Client.instances() - assert len(instances) == 2 - Client.instance_stopall() - instances = Client.instances() - assert instances == []
    -
    - -
    - -
    - - -
    -
    - -
    - -
    - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/api/modules/spython/tests/test_utils.html b/docs/api/modules/spython/tests/test_utils.html deleted file mode 100644 index 0fff93a1..00000000 --- a/docs/api/modules/spython/tests/test_utils.html +++ /dev/null @@ -1,367 +0,0 @@ - - - - - - - - - - - spython.tests.test_utils — Singularity Python API 1 documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - -
    - - - - - -
    - -
    - - - - - - - - - - - - - - - - - -
    - -
      - -
    • Docs »
    • - -
    • Module code »
    • - -
    • spython.tests.test_utils
    • - - -
    • - -
    • - -
    - - -
    -
    -
    -
    - -

    Source code for spython.tests.test_utils

    -#!/usr/bin/python
    -
    -# Copyright (C) 2017-2020 Vanessa Sochat.
    -
    -# This Source Code Form is subject to the terms of the
    -# Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
    -# with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
    -
    -import os
    -import pytest
    -from semver import VersionInfo
    -from spython.utils import ScopedEnvVar
    -
    -
    -
    [docs]def test_write_read_files(tmp_path): - """test_write_read_files will test the functions write_file and read_file - """ - print("Testing utils.write_file...") - from spython.utils import write_file - - tmpfile = str(tmp_path / "written_file.txt") - assert not os.path.exists(tmpfile) - write_file(tmpfile, "hello!") - assert os.path.exists(tmpfile) - - print("Testing utils.read_file...") - from spython.utils import read_file - - content = read_file(tmpfile)[0] - assert content == "hello!"
    - - -
    [docs]def test_write_bad_json(tmp_path): - from spython.utils import write_json - - bad_json = {"Wakkawakkawakka'}": [{True}, "2", 3]} - tmpfile = str(tmp_path / "json_file.txt") - assert not os.path.exists(tmpfile) - with pytest.raises(TypeError): - write_json(bad_json, tmpfile)
    - - -
    [docs]def test_write_json(tmp_path): - import json - from spython.utils import write_json - - good_json = {"Wakkawakkawakka": [True, "2", 3]} - tmpfile = str(tmp_path / "good_json_file.txt") - assert not os.path.exists(tmpfile) - write_json(good_json, tmpfile) - with open(tmpfile, "r") as f: - content = json.loads(f.read()) - assert isinstance(content, dict) - assert "Wakkawakkawakka" in content
    - - -
    [docs]def test_check_install(): - """check install is used to check if a particular software is installed. - If no command is provided, singularity is assumed to be the test case""" - print("Testing utils.check_install") - from spython.utils import check_install - - is_installed = check_install() - assert is_installed - is_not_installed = check_install("fakesoftwarename") - assert not is_not_installed
    - - -
    [docs]def test_check_get_singularity_version(): - """check that the singularity version is found to be that installed""" - from spython.utils import get_singularity_version - - version = get_singularity_version() - assert version != "" - with ScopedEnvVar("SPYTHON_SINGULARITY_VERSION", "3.0"): - version = get_singularity_version() - assert version == "3.0"
    - - -
    [docs]def test_check_get_singularity_version_info(): - """Check that the version_info is correct""" - from spython.utils import get_singularity_version_info - - with ScopedEnvVar("SPYTHON_SINGULARITY_VERSION", "2.3.1"): - version = get_singularity_version_info() - assert version == VersionInfo(2, 3, 1) - assert version > VersionInfo(2, 3, 0) - assert version < VersionInfo(3, 0, 0) - - with ScopedEnvVar("SPYTHON_SINGULARITY_VERSION", "singularity version 3.2.1-1"): - version = get_singularity_version_info() - assert version == VersionInfo(3, 2, 1, "1") - assert version > VersionInfo(2, 0, 0) - assert version < VersionInfo(3, 3, 0) - assert version > VersionInfo(3, 2, 0) - assert version < VersionInfo(3, 2, 1) - - with ScopedEnvVar("SPYTHON_SINGULARITY_VERSION", "2.6.1-pull/124.1d068a7"): - version = get_singularity_version_info() - assert version == VersionInfo(2, 6, 1, "pull", "124.1d068a7") - assert version > VersionInfo(2, 6, 0) - assert version < VersionInfo(2, 7, 0)
    - - -
    [docs]def test_get_installdir(): - """get install directory should return the base of where singularity - is installed - """ - print("Testing utils.get_installdir") - from spython.utils import get_installdir - - whereami = get_installdir() - print(whereami) - assert whereami.endswith("spython")
    - - -
    [docs]def test_split_uri(): - from spython.utils import split_uri - - protocol, image = split_uri("docker://ubuntu") - assert protocol == "docker" - assert image == "ubuntu" - - protocol, image = split_uri("http://image/path/with/slash/") - assert protocol == "http" - assert image == "image/path/with/slash" - - protocol, image = split_uri("no/proto/") - assert protocol == "" - assert image == "no/proto"
    - - -
    [docs]def test_remove_uri(): - print("Testing utils.remove_uri") - from spython.utils import remove_uri - - assert remove_uri("docker://ubuntu") == "ubuntu" - assert ( - remove_uri("shub://vanessa/singularity-images") == "vanessa/singularity-images" - ) - assert remove_uri("vanessa/singularity-images") == "vanessa/singularity-images"
    - - -
    [docs]def test_decode(): - from spython.logger import decodeUtf8String - - out = decodeUtf8String(str("Hello")) - assert isinstance(out, str) - assert out == "Hello" - out = decodeUtf8String(bytes(b"Hello")) - assert isinstance(out, str) - assert out == "Hello"
    - - -
    [docs]def test_ScopedEnvVar(): - assert "FOO" not in os.environ - with ScopedEnvVar("FOO", "bar") as e: - assert e.name == "FOO" - assert e.value == "bar" - assert os.environ["FOO"] == "bar" - with ScopedEnvVar("FOO", "baz"): - assert os.environ["FOO"] == "baz" - assert os.environ["FOO"] == "bar" - # None removes it - with ScopedEnvVar("FOO", None): - assert "FOO" not in os.environ - # But empty string is allowed - with ScopedEnvVar("FOO", ""): - assert os.environ["FOO"] == "" - assert os.environ["FOO"] == "bar" - assert "FOO" not in os.environ - # Unset a non-existing variable - with ScopedEnvVar("FOO", None): - assert "FOO" not in os.environ - assert "FOO" not in os.environ
    -
    - -
    - -
    - - -
    -
    - -
    - -
    - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/api/modules/spython/utils/fileio.html b/docs/api/modules/spython/utils/fileio.html index a20eb46c..065fa483 100644 --- a/docs/api/modules/spython/utils/fileio.html +++ b/docs/api/modules/spython/utils/fileio.html @@ -21,10 +21,10 @@ - - - - + + + + @@ -84,7 +84,6 @@ @@ -161,7 +160,7 @@

    Source code for spython.utils.fileio

     import errno
     import os
     import json
    -from spython.logger import bot
    +from spython.logger import bot
     import sys
     
     
    diff --git a/docs/api/modules/spython/utils/terminal.html b/docs/api/modules/spython/utils/terminal.html
    index 34e0b51b..1128d879 100644
    --- a/docs/api/modules/spython/utils/terminal.html
    +++ b/docs/api/modules/spython/utils/terminal.html
    @@ -21,10 +21,10 @@
       
         
           
    -        
    -        
    -        
    -        
    +        
    +        
    +        
    +        
         
         
     
    @@ -84,7 +84,6 @@
                 
                   
     
                 
    @@ -162,8 +161,8 @@ 

    Source code for spython.utils.terminal

     import pwd
     import re
     import semver
    -from spython.logger import bot
    -from spython.logger import decodeUtf8String
    +from spython.logger import bot
    +from spython.logger import decodeUtf8String
     import subprocess
     import sys
     import shlex
    @@ -284,13 +283,16 @@ 

    Source code for spython.utils.terminal

         """
         cmd = _process_sudo_cmd(cmd, sudo, sudo_options)
     
    -    process = subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True)
    +    process = subprocess.Popen(
    +        cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True
    +    )
         for line in iter(process.stdout.readline, ""):
             if not re.search(no_newline_regexp, line):
                 yield line
         process.stdout.close()
         return_code = process.wait()
         if return_code:
    +        print(process.stderr.read(), file=sys.stderr)
             raise subprocess.CalledProcessError(return_code, cmd)
    @@ -380,7 +382,7 @@

    Source code for spython.utils.terminal

     
     
     
    [docs]def remove_uri(container): - """remove_uri will remove docker:// or shub:// from the uri + """remove_uri will remove docker:// or shub:// or library:// from the uri """ return split_uri(container)[1]
    diff --git a/docs/api/objects.inv b/docs/api/objects.inv index 55903a78..965f66ec 100644 Binary files a/docs/api/objects.inv and b/docs/api/objects.inv differ diff --git a/docs/api/py-modindex.html b/docs/api/py-modindex.html index 4de8bc32..cb252d0c 100644 --- a/docs/api/py-modindex.html +++ b/docs/api/py-modindex.html @@ -21,10 +21,10 @@ - - - - + + + + @@ -87,7 +87,6 @@ @@ -350,21 +349,6 @@

    Python Module Index

        spython.tests - - -     - spython.tests.test_client - - - -     - spython.tests.test_instances - - - -     - spython.tests.test_utils -     diff --git a/docs/api/search.html b/docs/api/search.html index 3b0aa2b7..ec704b6a 100644 --- a/docs/api/search.html +++ b/docs/api/search.html @@ -21,11 +21,11 @@ - - - - - + + + + + @@ -85,7 +85,6 @@ diff --git a/docs/api/searchindex.js b/docs/api/searchindex.js index 3fa7e237..eddb2243 100644 --- a/docs/api/searchindex.js +++ b/docs/api/searchindex.js @@ -1 +1 @@ -Search.setIndex({docnames:["changelog","index","source/modules","source/spython","source/spython.client","source/spython.image","source/spython.image.cmd","source/spython.instance","source/spython.instance.cmd","source/spython.logger","source/spython.main","source/spython.main.base","source/spython.main.parse","source/spython.tests","source/spython.utils"],envversion:{"sphinx.domains.c":1,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":1,"sphinx.domains.javascript":1,"sphinx.domains.math":2,"sphinx.domains.python":1,"sphinx.domains.rst":1,"sphinx.domains.std":1,"sphinx.ext.intersphinx":1,"sphinx.ext.todo":2,"sphinx.ext.viewcode":1,sphinx:56},filenames:["changelog.md","index.rst","source/modules.rst","source/spython.rst","source/spython.client.rst","source/spython.image.rst","source/spython.image.cmd.rst","source/spython.instance.rst","source/spython.instance.cmd.rst","source/spython.logger.rst","source/spython.main.rst","source/spython.main.base.rst","source/spython.main.parse.rst","source/spython.tests.rst","source/spython.utils.rst"],objects:{"":{spython:[3,0,0,"-"]},"spython.client":{get_parser:[4,1,1,""],main:[4,1,1,""],recipe:[4,0,0,"-"],set_verbosity:[4,1,1,""],shell:[4,0,0,"-"],test:[4,0,0,"-"],version:[4,1,1,""]},"spython.client.recipe":{main:[4,1,1,""]},"spython.client.shell":{ipython:[4,1,1,""],main:[4,1,1,""],prepare_client:[4,1,1,""],python:[4,1,1,""],run_bpython:[4,1,1,""]},"spython.client.test":{main:[4,1,1,""]},"spython.image":{Image:[5,2,1,""],ImageBase:[5,2,1,""],cmd:[6,0,0,"-"]},"spython.image.Image":{get_hash:[5,3,1,""]},"spython.image.ImageBase":{parse_image_name:[5,3,1,""]},"spython.image.cmd":{"export":[6,0,0,"-"],create:[6,0,0,"-"],generate_image_commands:[6,1,1,""],importcmd:[6,0,0,"-"],utils:[6,0,0,"-"]},"spython.image.cmd.create":{create:[6,1,1,""]},"spython.image.cmd.export":{"export":[6,1,1,""]},"spython.image.cmd.importcmd":{importcmd:[6,1,1,""]},"spython.image.cmd.utils":{compress:[6,1,1,""],decompress:[6,1,1,""]},"spython.instance":{Instance:[7,2,1,""],cmd:[8,0,0,"-"]},"spython.instance.Instance":{RobotNamer:[7,4,1,""],debug:[7,4,1,""],error_logs:[7,3,1,""],generate_name:[7,3,1,""],get_uri:[7,3,1,""],instance:[7,4,1,""],output_logs:[7,3,1,""],parse_image_name:[7,3,1,""],quiet:[7,4,1,""],run_command:[7,3,1,""],start:[7,3,1,""],stop:[7,3,1,""],version:[7,3,1,""]},"spython.instance.cmd":{generate_instance_commands:[8,1,1,""],iutils:[8,0,0,"-"],start:[8,0,0,"-"],stop:[8,0,0,"-"]},"spython.instance.cmd.iutils":{get:[8,1,1,""],parse_table:[8,1,1,""]},"spython.instance.cmd.start":{start:[8,1,1,""]},"spython.instance.cmd.stop":{stop:[8,1,1,""]},"spython.logger":{message:[9,0,0,"-"],progress:[9,0,0,"-"],spinner:[9,0,0,"-"]},"spython.logger.message":{SingularityMessage:[9,2,1,""],convert2boolean:[9,1,1,""],get_logging_level:[9,1,1,""],get_user_color_preference:[9,1,1,""]},"spython.logger.message.SingularityMessage":{abort:[9,3,1,""],addColor:[9,3,1,""],critical:[9,3,1,""],custom:[9,3,1,""],debug:[9,3,1,""],emit:[9,3,1,""],emitError:[9,3,1,""],emitOutput:[9,3,1,""],error:[9,3,1,""],exit:[9,3,1,""],get_logs:[9,3,1,""],info:[9,3,1,""],isEnabledFor:[9,3,1,""],is_quiet:[9,3,1,""],log:[9,3,1,""],newline:[9,3,1,""],println:[9,3,1,""],show_progress:[9,3,1,""],spinner:[9,4,1,""],table:[9,3,1,""],useColor:[9,3,1,""],verbose1:[9,3,1,""],verbose2:[9,3,1,""],verbose3:[9,3,1,""],verbose:[9,3,1,""],warning:[9,3,1,""],write:[9,3,1,""]},"spython.logger.progress":{ProgressBar:[9,2,1,""],bar:[9,1,1,""]},"spython.logger.progress.ProgressBar":{done:[9,3,1,""],format_time:[9,3,1,""],show:[9,3,1,""]},"spython.logger.spinner":{Spinner:[9,2,1,""]},"spython.logger.spinner.Spinner":{balloons_cursor:[9,3,1,""],changing_arrows:[9,3,1,""],delay:[9,4,1,""],run:[9,3,1,""],select_generator:[9,3,1,""],spinning:[9,4,1,""],spinning_cursor:[9,3,1,""],start:[9,3,1,""],stop:[9,3,1,""]},"spython.main":{apps:[10,0,0,"-"],base:[11,0,0,"-"],build:[10,0,0,"-"],execute:[10,0,0,"-"],get_client:[10,1,1,""],help:[10,0,0,"-"],inspect:[10,0,0,"-"],instances:[10,0,0,"-"],parse:[12,0,0,"-"],pull:[10,0,0,"-"],run:[10,0,0,"-"]},"spython.main.apps":{apps:[10,1,1,""]},"spython.main.base":{Client:[11,2,1,""],command:[11,0,0,"-"],flags:[11,0,0,"-"],generate:[11,0,0,"-"],logger:[11,0,0,"-"],sutils:[11,0,0,"-"]},"spython.main.base.Client":{"export":[11,3,1,""],RobotNamer:[11,4,1,""],apps:[11,3,1,""],build:[11,3,1,""],debug:[11,4,1,""],execute:[11,3,1,""],help:[11,3,1,""],image:[11,4,1,""],inspect:[11,3,1,""],instance:[11,4,1,""],instance_stopall:[11,3,1,""],instances:[11,3,1,""],load:[11,3,1,""],oci:[11,4,1,""],pull:[11,3,1,""],quiet:[11,4,1,""],run:[11,3,1,""],setenv:[11,3,1,""],shell:[11,3,1,""],version:[11,3,1,""],version_info:[11,3,1,""]},"spython.main.base.command":{generate_bind_list:[11,1,1,""],init_command:[11,1,1,""],run_command:[11,1,1,""],send_command:[11,1,1,""]},"spython.main.base.flags":{parse_verbosity:[11,1,1,""]},"spython.main.base.generate":{RobotNamer:[11,2,1,""],main:[11,1,1,""]},"spython.main.base.generate.RobotNamer":{generate:[11,3,1,""]},"spython.main.base.logger":{init_level:[11,1,1,""],println:[11,1,1,""]},"spython.main.base.sutils":{get_filename:[11,1,1,""],get_uri:[11,1,1,""],load:[11,1,1,""],setenv:[11,1,1,""]},"spython.main.build":{build:[10,1,1,""]},"spython.main.execute":{execute:[10,1,1,""],shell:[10,1,1,""]},"spython.main.help":{helpcmd:[10,1,1,""]},"spython.main.inspect":{inspect:[10,1,1,""],parse_labels:[10,1,1,""]},"spython.main.instances":{list_instances:[10,1,1,""],stopall:[10,1,1,""]},"spython.main.parse":{recipe:[12,0,0,"-"]},"spython.main.parse.recipe":{Recipe:[12,2,1,""]},"spython.main.parse.recipe.Recipe":{json:[12,3,1,""]},"spython.main.pull":{pull:[10,1,1,""]},"spython.main.run":{run:[10,1,1,""]},"spython.tests":{test_client:[13,0,0,"-"],test_instances:[13,0,0,"-"],test_utils:[13,0,0,"-"]},"spython.tests.test_client":{test_build_from_docker:[13,1,1,""],test_docker_pull:[13,1,1,""],test_execute:[13,1,1,""],test_execute_with_return_code:[13,1,1,""],test_export:[13,1,1,""],test_inspect:[13,1,1,""]},"spython.tests.test_instances":{TestInstanceFuncs:[13,2,1,""],test_has_no_instances:[13,1,1,""],test_instance_class:[13,1,1,""]},"spython.tests.test_instances.TestInstanceFuncs":{cleanup:[13,3,1,""],test_instance_cmds:[13,3,1,""]},"spython.tests.test_utils":{test_ScopedEnvVar:[13,1,1,""],test_check_get_singularity_version:[13,1,1,""],test_check_get_singularity_version_info:[13,1,1,""],test_check_install:[13,1,1,""],test_decode:[13,1,1,""],test_get_installdir:[13,1,1,""],test_remove_uri:[13,1,1,""],test_split_uri:[13,1,1,""],test_write_bad_json:[13,1,1,""],test_write_json:[13,1,1,""],test_write_read_files:[13,1,1,""]},"spython.utils":{fileio:[14,0,0,"-"],terminal:[14,0,0,"-"]},"spython.utils.fileio":{mkdir_p:[14,1,1,""],read_file:[14,1,1,""],read_json:[14,1,1,""],write_file:[14,1,1,""],write_json:[14,1,1,""]},"spython.utils.terminal":{check_install:[14,1,1,""],format_container_name:[14,1,1,""],get_installdir:[14,1,1,""],get_singularity_version:[14,1,1,""],get_singularity_version_info:[14,1,1,""],get_userhome:[14,1,1,""],get_username:[14,1,1,""],remove_uri:[14,1,1,""],run_command:[14,1,1,""],split_uri:[14,1,1,""],stream_command:[14,1,1,""],which:[14,1,1,""]},spython:{client:[4,0,0,"-"],image:[5,0,0,"-"],instance:[7,0,0,"-"],logger:[9,0,0,"-"],main:[10,0,0,"-"],tests:[13,0,0,"-"],utils:[14,0,0,"-"],version:[3,0,0,"-"]}},objnames:{"0":["py","module","Python module"],"1":["py","function","Python function"],"2":["py","class","Python class"],"3":["py","method","Python method"],"4":["py","attribute","Python attribute"]},objtypes:{"0":"py:module","1":"py:function","2":"py:class","3":"py:method","4":"py:attribute"},terms:{"1024mib":6,"95m":9,"boolean":[0,7,9,10,11],"byte":11,"case":[7,10,13],"char":11,"class":[5,7,9,11,12,13],"default":[0,6,7,8,9,10,11,14],"export":[0,3,5,11],"function":[0,4,6,9,10,11,13,14],"import":[0,6,10],"int":9,"new":[6,11,14],"public":[3,12,14],"return":[0,5,7,8,9,10,11,12,13,14],"static":9,"true":[6,7,8,9,10,11,14],Added:0,Adding:0,For:[7,10,11,14],The:[0,1,6,8,10,11,12,14],These:6,Will:9,With:[7,8],__init__:6,abil:0,abl:7,abort:9,about:[10,11],accept:0,access:[10,11],account:0,action:[0,11],actual:[5,11],add:[0,7,14],addcolor:9,added:[0,6,7,9,10,11],adding:0,addit:[0,6,11],address:0,adjust:0,advis:[10,11],after:[7,8,11],alia:[7,11],all:[0,9,10,11,14],allow:[10,11],along:7,also:[11,12],ani:[10,11],api:0,app:[2,3,11],appear:11,append:14,applic:[11,14],arbitari:[10,11],aren:0,arg:[0,4,7,8,9,10,11],argpars:11,argument:[0,7,8,10,11],around:4,ascii:9,associ:[10,11],assum:[8,9,13],attempt:[11,14],attribut:[0,12],automat:[10,11],avail:[11,14],back:[0,11,14],backward:0,balloons_cursor:9,bar:[7,9,14],base:[0,3,4,5,7,9,10,12,13,14],basic:0,been:0,behaviour:0,being:9,bind:[0,7,8,10,11],bindlist:11,bootstrap:11,both:[4,11],bourn:11,bpython:4,branch:0,bug:0,build:[0,2,3,11,12],build_fold:[10,11],builder:[10,11],built:[10,11],bundl:11,call:[0,7,8,9,10,11],can:[0,3,4,7,11,12,14],capabl:11,captur:[7,8,10,11,14],carriage_return:9,chang:0,changelog:1,changing_arrow:9,charact:[9,14],check:[0,9,11,13,14],check_instal:14,classmethod:7,cleanup:13,cli:0,client:[0,1,2,3,6,7,8,10,11,14],close:[0,14],cmd:[0,3,5,7,11,12,14],code:[0,3,10,11,12,14],coincid:0,col_width:9,color:9,column:[8,9],com:[7,10,11],command:[0,3,6,7,8,10,12,13,14],comment:12,commit:0,commonli:6,complet:[5,7,9,10,11],compos:0,compress:6,condit:0,configur:9,consol:[0,4,7,14],contain:[0,1,7,8,10,11,14],content:[1,2],context:[10,11],continu:0,convers:0,convert2boolean:9,convert:[0,3,4,10,11],copi:[0,3,12,14],copyright:[3,12,14],correct:[0,13],couldn:[10,11],count:9,crash:0,creat:[3,5,7,8,9,10,11,14],criteria:5,critic:[0,9],crypto:11,current:9,custom:[6,9,10,11],data:[10,11,14],debug:[0,6,7,8,9,10,11],decompress:6,defil:[10,11],defin:[0,4,6,10,11,14],delai:9,delim:11,delimit:11,deprec:[0,6,11],deriv:[7,11],descriptor:11,dest:0,destin:0,determin:[4,7,9,14],dict:14,dictionari:[9,10,11,12],differ:[0,10,11],dimens:8,directli:[10,11],directori:[0,10,11,13,14],disabl:[10,11],displai:11,distribut:[3,12,14],docker:[0,3,5,7,10,11,14],docker_contain:13,dockerfil:0,dockerpars:[4,12],dockerrecip:0,dockerwrit:4,document:0,doe:[0,11],doesn:[7,10,11],don:[7,8,10,11,14],done:[7,8,9,10,11],driver:[10,11],each:[0,7,8,14],effect:14,either:[4,11,12],emb:4,emit:9,emiterror:9,emitoutput:9,empti:[0,11,14],empty_char:9,enabl:9,encod:9,end:14,enhanc:0,entir:[10,11],entri:9,entrypoint:[0,12],env:[0,10,11],environ:[0,3,4,6,8,9,10,11],environment:9,error:[0,9,10,11],error_log:7,etc:8,eventu:0,everi:9,exampl:[11,14],except:14,exec:[0,11],execut:[0,2,3,11],exist:[5,7,11],exit:[9,11],expand:0,expect:[8,10,11],expected_s:9,expos:[6,10,11],express:[7,14],ext3:[6,10,11],ext4:6,ext:[6,10,11],extens:[10,11],fail:0,fals:[0,6,7,8,9,10,11,14],file:[0,3,4,5,6,10,11,12,14],fileio:[2,3],filenam:[11,14],filesystem:[6,10,11],filled_char:9,find:[7,14],first:[5,9,11],fix:[0,10],flag:[0,3,10],flexibl:0,folder:[10,11],follow:11,forc:[0,10,11],forcebl:[7,8],form:[3,12,14],format:[0,14],format_container_nam:14,format_tim:9,found:[10,11,13,14],free:12,from:[0,5,6,7,8,10,11,14],fromhead:0,full:[0,6,10,11,14],full_path:[10,11],fun:[10,11],ged:[10,11],gener:[0,3,7,9,10],generate_bind_list:11,generate_image_command:[6,11],generate_instance_command:8,generate_nam:7,get:[0,5,7,8,10,11,13,14],get_client:10,get_filenam:11,get_hash:5,get_installdir:14,get_log:9,get_logging_level:9,get_pars:4,get_singularity_vers:[7,11,14],get_singularity_version_info:[11,14],get_uri:[7,11],get_user_color_prefer:9,get_userhom:14,get_usernam:14,github:[0,7,10,11],give:[4,5,7,8,10,11],given:[0,6,11],global:11,group:[0,6,8,10,11],guidanc:0,haikun:11,handl:[0,5,7],hang:[0,7,8],hardcod:0,has:[0,9,11],hash:[0,5],have:[0,7,11,14],header:[0,8],help:[2,3,4,11],helpcmd:10,helper:6,here:0,hide:9,highli:[10,11],histori:9,hold:[6,8],home:14,honor:0,host:[10,11],how:0,howev:[10,11],http:[3,7,10,11,12,14],hub:[10,11],ideal:8,ignor:0,imag:[0,1,2,3,4,7,8,10,11,14],image_path:[6,10,11],imagebas:[5,7],imagecli:11,implement:[0,11],import_typ:6,importcmd:[3,5],imprt:6,includ:[0,9,10,11,12],incompat:0,increas:11,index:1,indic:[7,8],info:[0,9],inform:[9,10,11],init:10,init_command:11,init_level:11,initi:[10,11],input:6,input_sourc:6,inspect:[0,2,3,11],inspir:11,instal:[10,11,12,13,14],instanc:[0,1,2,3,11],instance_stopal:11,instead:[10,11],integ:8,integr:[10,11],intend:[5,12],interact:[11,12],interest:[7,14],interpret:9,ipython:4,is_quiet:9,isenabledfor:9,isol:[10,11],issu:[0,7,10,11],issuecom:7,item:0,iter:[9,10,11],iutil:[3,7],join:9,join_newlin:9,json:[0,8,10,11,12,14],json_obj:14,jsonifi:8,just:[7,10,11],kei:9,kill:[0,7,8],know:0,kwarg:7,label:[9,10,11,12],last:0,later:7,launch:11,length:[9,11],level:[0,4,5,6,8,9,11],libexecdir:[10,11],licens:[1,3,12,14],like:[0,7,8],line:[0,7,11,14],lint:11,linux:[10,11],list:[0,7,8,9,10,11,14],list_inst:10,load:[0,5,7,10,11],local:11,locat:[10,11,12],log:[0,7,9,11],logger:[0,1,2,3,10],look:[8,10,11],main:[0,1,2,3,4,6,7,8,9],mainli:6,make:[10,11],manag:11,manual:0,map:[10,11],master:1,match:8,md5:5,mean:[7,10,11,14],merg:0,messag:[0,2,3,4,10,11],messagelevel:[6,8,9],metadata:11,mib:6,migrat:0,min_level:9,miss:0,mkdir:14,mkdir_p:14,mode:[10,14],modul:[1,2],more:[0,10,11],most:10,mount:[10,11],move:0,mozilla:[3,12,14],mpl:[1,3,12,14],much:11,multipl:0,must:[6,8,9,11],name:[0,7,8,9,10,11,14],namespac:[5,7],need:[7,8,10,11,14],newlin:[7,9,14],nicer:14,no_newline_regexp:[7,14],non:[0,7,8,11],none:[5,6,7,8,9,10,11,12,14],normal:11,now:0,number:[8,9],nvidia:[0,10,11],object:[5,7,9,10,11,12,13,14],obtain:[3,12,14],oci:[0,11],one:[0,3,7,10,11,12,14],onli:[0,5,9,10,11],open:14,option:[0,4,7,8,9,10,11,14],orb:0,org:[3,12,14],origin:[0,10,12],other:[7,10,11,14],otherwis:[0,11],out:9,outfil:4,output:[0,4,7,8,10,11,14],output_fil:11,output_log:7,over:11,overrid:[0,10,11],overriden:11,packag:[0,1,2],param:[9,14],paramet:[0,5,6,7,8,10,11,12,14],parent:[7,11,14],pars:[0,3,5,7,8,10,11,14],parse_image_nam:[5,7],parse_label:10,parse_t:8,parse_verbos:11,parser:[0,4,12],part:14,particular:13,pass:[6,7,8,10,11,14],path:[0,5,6,7,8,10,11,14],paus:0,perform:[10,11],persist:11,pid:[8,10,11],pipe:[7,11,14],point:0,poorli:11,popualt:12,port:12,prefer:[10,11],prefix:9,prepar:4,prepare_cli:4,preserv:[10,11],pretti:14,pretty_print:14,print:[0,4,7,9,10,11,12,14],print_log:7,print_pretti:14,println:[0,9,11],prioriti:5,privat:0,process:11,progess:[7,14],programat:7,progress:[2,3,7,14],progressbar:9,prompt:9,properli:[6,14],protocol:14,provid:[6,7,8,10,11,13],pull:[0,2,3,11],pull_fold:[10,11],pwd:[10,11],pypi:0,python:[0,4,11],quiet:[0,6,7,8,9,10,11,14],raw:[10,11],read:[10,11,14],read_fil:[13,14],read_json:14,readlin:14,reason:[5,10,11],recip:[0,2,3,10,11],recommend:[10,11],refactor:0,registri:[5,7],regular:[7,14],rel:[0,10,11],releas:0,relev:0,remov:[0,8,14],remove_row:8,remove_uri:14,renam:0,replac:[0,11],report:14,repositori:0,repres:0,request:0,requir:[0,10,11],respect:[0,12],result:[0,10,11,14],resum:0,return_cod:9,return_json:[8,10,11],return_result:[10,11],revers:0,ride:11,right:0,robot:[7,10,11],robot_nam:[10,11],robotnam:[7,11],root:[10,11],row:[8,9],run:[0,2,3,7,8,9,11,14],run_bpython:4,run_command:[7,11,14],runscript:[0,10,11,12],runtim:[10,11],same:14,sandbox:[10,11],save:[4,5],sci:[10,11],scientif:[10,11],scif:[10,11],screen:[7,10,11],second:[5,7,8,9],section:0,see:[7,10,11],select:9,select_gener:9,self:[6,8,10,11],selftest:11,semant:14,send:[7,10,11,14],send_command:11,sens:[10,11],sequenc:12,serv:[4,10,11],set:[4,7,9,10,11,14],set_verbos:4,setenv:11,setup:0,share:[10,11],shell:[0,2,3,10,11],shortcut:[7,11],should:[0,7,9,10,11,13,14],show:[9,10,11],show_progress:9,shub:14,sif:[10,11],siflist:11,sign:11,signatur:11,silent:11,simag:11,simpli:[5,7],singl:[8,10,11],singular:[0,3,5,6,7,8,10,11,13,14],singularity_cachedir:[10,11],singularity_tmpdir:[10,11],singularitymessag:9,singularitypars:[4,12],singularityrecip:0,singularitywar:[10,11],size:6,sizein:6,skip:[7,14],slash:14,sochat:[3,12,14],softwar:[10,11,13,14],some:[0,11],sourc:[0,3,4,5,6,7,8,9,10,11,12,13,14],special:14,special_charact:14,specif:[10,11],specifi:[6,7,8,9,10,11],sphinx:0,spin:9,spinner:[2,3],spinning_cursor:9,split:[0,5,7,14],split_uri:14,spython:[0,1],src:0,standard:[9,10,11],start:[0,3,7,9,14],state:0,statement:0,stderr:[9,11],stdin:6,stdout:[7,9,11,14],stop:[0,3,7,9,10,11],stopal:10,stream:[0,9,10,11,14],stream_command:14,string:[7,8,10,11,14],strip:0,structur:14,sub:[10,11],subclass:12,subcommand:6,subject:[3,12,14],submodul:[1,2,5,7],subpackag:[1,2],subprocess:[7,14],success:[0,11],sudo:[0,6,7,8,10,11,14],sudo_opt:[7,10,11,14],suffix:9,summari:11,suppli:[7,14],support:[0,6,7,8,9,10,11],suppress:[10,11],sutil:[3,10],sylab:7,symbol:9,system:[6,10,11],tabl:[8,9],table_str:8,take:[7,10,11,14],taken:0,tar:[6,11],tempfil:0,temporari:[6,11],term:[3,12,14],termin:[0,2,3,7,9,11],test:[0,2,3,10,11,12],test_build_from_dock:13,test_check_get_singularity_vers:13,test_check_get_singularity_version_info:13,test_check_instal:13,test_decod:13,test_docker_pul:13,test_execut:13,test_execute_with_return_cod:13,test_export:13,test_get_installdir:13,test_has_no_inst:13,test_inspect:13,test_instance_class:13,test_instance_cmd:13,test_remove_uri:13,test_scopedenvvar:13,test_split_uri:13,test_write_bad_json:13,test_write_json:13,test_write_read_fil:13,testinstancefunc:13,testscript:11,text:[9,10,11],thei:7,them:[0,10,11],thi:[0,3,4,5,7,8,10,11,12,14],those:14,timeout:[0,7,8],tmp:[7,8],tmp_path:13,tmptar:6,tokenchar:11,tokenlength:11,tool:[10,11],top:8,total:9,track:0,trail:14,tupl:[7,8],turn:10,typo:0,ubuntu:[5,7,11],uid:[10,11,14],under:[1,6,9],unset:0,updat:10,uri:[0,5,7,8,10,11,14],url:0,usag:[7,8,11,14],use:[0,6,7,10,11,12,14],usecolor:9,used:[6,9,10,11,13],useful:[5,10,11],user:[4,7,8,9,10,11,12,14],uses:[7,11,14],using:[10,11],usual:9,utf:11,util:[1,2,3,5],valu:[0,10,11],vanessa:[3,12,14],variabl:[0,6,8,9,11],vault:0,verbos:[6,8,9,10,11],verbose1:9,verbose2:9,verbose3:9,verifi:11,version:[0,1,2,4,5,7,11,12,13,14],version_info:[11,13],via:[6,8],volum:[11,12],want:[7,8],warn:[0,9],whatev:6,when:[0,7,8],where:[8,10,11,13,14],which:[9,10,11,14],width:9,within:[10,11],withour:[10,11],without:14,won:[7,14],work:[0,1],workdir:12,wrap:9,wrapper:[4,11],writabl:[0,10,11],write:[9,10,11,12,14],write_fil:[13,14],write_json:14,writer:[0,4,12],x1b:9,yes:11,yet:11,yield:14,you:[3,10,11,12,14],your:[9,10,11]},titles:["CHANGELOG","Welcome to Singularity Python API\u2019s documentation!","spython","spython package","spython.client package","spython.image package","spython.image.cmd package","spython.instance package","spython.instance.cmd package","spython.logger package","spython.main package","spython.main.base package","spython.main.parse package","spython.tests package","spython.utils package"],titleterms:{"export":6,api:1,app:10,base:11,build:10,changelog:0,client:4,cmd:[6,8],command:11,content:[3,4,5,6,7,8,9,10,11,12,13,14],convert:12,creat:6,docker:12,document:1,environ:12,execut:10,fileio:14,flag:11,gener:11,help:10,imag:[5,6],importcmd:6,indic:1,inspect:10,instanc:[7,8,10],iutil:8,logger:[9,11],main:[10,11,12],master:0,messag:9,modul:[3,4,5,6,7,8,9,10,11,12,13,14],packag:[3,4,5,6,7,8,9,10,11,12,13,14],pars:12,progress:9,pull:10,python:1,recip:[4,12],run:10,shell:4,singular:[1,12],spinner:9,spython:[2,3,4,5,6,7,8,9,10,11,12,13,14],start:8,stop:8,submodul:[3,4,6,8,9,10,11,12,13,14],subpackag:[3,5,7,10],sutil:11,tabl:1,termin:14,test:[4,13],test_client:13,test_inst:13,test_util:13,util:[6,14],version:3,welcom:1}}) \ No newline at end of file +Search.setIndex({docnames:["changelog","index","source/modules","source/spython","source/spython.client","source/spython.image","source/spython.image.cmd","source/spython.instance","source/spython.instance.cmd","source/spython.logger","source/spython.main","source/spython.main.base","source/spython.main.parse","source/spython.tests","source/spython.utils"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":2,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":2,"sphinx.domains.rst":2,"sphinx.domains.std":1,"sphinx.ext.intersphinx":1,"sphinx.ext.todo":2,"sphinx.ext.viewcode":1,sphinx:56},filenames:["changelog.md","index.rst","source/modules.rst","source/spython.rst","source/spython.client.rst","source/spython.image.rst","source/spython.image.cmd.rst","source/spython.instance.rst","source/spython.instance.cmd.rst","source/spython.logger.rst","source/spython.main.rst","source/spython.main.base.rst","source/spython.main.parse.rst","source/spython.tests.rst","source/spython.utils.rst"],objects:{"":{spython:[3,0,0,"-"]},"spython.client":{get_parser:[4,1,1,""],main:[4,1,1,""],recipe:[4,0,0,"-"],set_verbosity:[4,1,1,""],shell:[4,0,0,"-"],test:[4,0,0,"-"],version:[4,1,1,""]},"spython.client.recipe":{main:[4,1,1,""]},"spython.client.shell":{ipython:[4,1,1,""],main:[4,1,1,""],prepare_client:[4,1,1,""],python:[4,1,1,""],run_bpython:[4,1,1,""]},"spython.client.test":{main:[4,1,1,""]},"spython.image":{Image:[5,2,1,""],ImageBase:[5,2,1,""],cmd:[6,0,0,"-"]},"spython.image.Image":{get_hash:[5,3,1,""]},"spython.image.ImageBase":{parse_image_name:[5,3,1,""]},"spython.image.cmd":{"export":[6,0,0,"-"],create:[6,0,0,"-"],generate_image_commands:[6,1,1,""],importcmd:[6,0,0,"-"],utils:[6,0,0,"-"]},"spython.image.cmd.create":{create:[6,1,1,""]},"spython.image.cmd.export":{"export":[6,1,1,""]},"spython.image.cmd.importcmd":{importcmd:[6,1,1,""]},"spython.image.cmd.utils":{compress:[6,1,1,""],decompress:[6,1,1,""]},"spython.instance":{Instance:[7,2,1,""],cmd:[8,0,0,"-"]},"spython.instance.Instance":{RobotNamer:[7,4,1,""],debug:[7,4,1,""],error_logs:[7,3,1,""],generate_name:[7,3,1,""],get_uri:[7,3,1,""],instance:[7,4,1,""],output_logs:[7,3,1,""],parse_image_name:[7,3,1,""],quiet:[7,4,1,""],run_command:[7,3,1,""],start:[7,3,1,""],stop:[7,3,1,""],version:[7,3,1,""]},"spython.instance.cmd":{generate_instance_commands:[8,1,1,""],iutils:[8,0,0,"-"],start:[8,0,0,"-"],stop:[8,0,0,"-"]},"spython.instance.cmd.iutils":{get:[8,1,1,""],parse_table:[8,1,1,""]},"spython.instance.cmd.start":{start:[8,1,1,""]},"spython.instance.cmd.stop":{stop:[8,1,1,""]},"spython.logger":{message:[9,0,0,"-"],progress:[9,0,0,"-"],spinner:[9,0,0,"-"]},"spython.logger.message":{SingularityMessage:[9,2,1,""],convert2boolean:[9,1,1,""],get_logging_level:[9,1,1,""],get_user_color_preference:[9,1,1,""]},"spython.logger.message.SingularityMessage":{abort:[9,3,1,""],addColor:[9,3,1,""],critical:[9,3,1,""],custom:[9,3,1,""],debug:[9,3,1,""],emit:[9,3,1,""],emitError:[9,3,1,""],emitOutput:[9,3,1,""],error:[9,3,1,""],exit:[9,3,1,""],get_logs:[9,3,1,""],info:[9,3,1,""],isEnabledFor:[9,3,1,""],is_quiet:[9,3,1,""],log:[9,3,1,""],newline:[9,3,1,""],println:[9,3,1,""],show_progress:[9,3,1,""],spinner:[9,4,1,""],table:[9,3,1,""],useColor:[9,3,1,""],verbose1:[9,3,1,""],verbose2:[9,3,1,""],verbose3:[9,3,1,""],verbose:[9,3,1,""],warning:[9,3,1,""],write:[9,3,1,""]},"spython.logger.progress":{ProgressBar:[9,2,1,""],bar:[9,1,1,""]},"spython.logger.progress.ProgressBar":{done:[9,3,1,""],format_time:[9,3,1,""],show:[9,3,1,""]},"spython.logger.spinner":{Spinner:[9,2,1,""]},"spython.logger.spinner.Spinner":{balloons_cursor:[9,3,1,""],changing_arrows:[9,3,1,""],delay:[9,4,1,""],run:[9,3,1,""],select_generator:[9,3,1,""],spinning:[9,4,1,""],spinning_cursor:[9,3,1,""],start:[9,3,1,""],stop:[9,3,1,""]},"spython.main":{apps:[10,0,0,"-"],base:[11,0,0,"-"],build:[10,0,0,"-"],execute:[10,0,0,"-"],get_client:[10,1,1,""],help:[10,0,0,"-"],inspect:[10,0,0,"-"],instances:[10,0,0,"-"],parse:[12,0,0,"-"],pull:[10,0,0,"-"],run:[10,0,0,"-"]},"spython.main.apps":{apps:[10,1,1,""]},"spython.main.base":{Client:[11,2,1,""],command:[11,0,0,"-"],flags:[11,0,0,"-"],generate:[11,0,0,"-"],logger:[11,0,0,"-"],sutils:[11,0,0,"-"]},"spython.main.base.Client":{"export":[11,3,1,""],RobotNamer:[11,4,1,""],apps:[11,3,1,""],build:[11,3,1,""],debug:[11,4,1,""],execute:[11,3,1,""],help:[11,3,1,""],image:[11,4,1,""],inspect:[11,3,1,""],instance:[11,4,1,""],instance_stopall:[11,3,1,""],instances:[11,3,1,""],load:[11,3,1,""],oci:[11,4,1,""],pull:[11,3,1,""],quiet:[11,4,1,""],run:[11,3,1,""],setenv:[11,3,1,""],shell:[11,3,1,""],version:[11,3,1,""],version_info:[11,3,1,""]},"spython.main.base.command":{generate_bind_list:[11,1,1,""],init_command:[11,1,1,""],run_command:[11,1,1,""],send_command:[11,1,1,""]},"spython.main.base.flags":{parse_verbosity:[11,1,1,""]},"spython.main.base.generate":{RobotNamer:[11,2,1,""],main:[11,1,1,""]},"spython.main.base.generate.RobotNamer":{generate:[11,3,1,""]},"spython.main.base.logger":{init_level:[11,1,1,""],println:[11,1,1,""]},"spython.main.base.sutils":{get_filename:[11,1,1,""],get_uri:[11,1,1,""],load:[11,1,1,""],setenv:[11,1,1,""]},"spython.main.build":{build:[10,1,1,""]},"spython.main.execute":{execute:[10,1,1,""],shell:[10,1,1,""]},"spython.main.help":{helpcmd:[10,1,1,""]},"spython.main.inspect":{inspect:[10,1,1,""],parse_labels:[10,1,1,""]},"spython.main.instances":{list_instances:[10,1,1,""],stopall:[10,1,1,""]},"spython.main.parse":{recipe:[12,0,0,"-"]},"spython.main.parse.recipe":{Recipe:[12,2,1,""]},"spython.main.parse.recipe.Recipe":{json:[12,3,1,""]},"spython.main.pull":{pull:[10,1,1,""]},"spython.main.run":{run:[10,1,1,""]},"spython.utils":{fileio:[14,0,0,"-"],terminal:[14,0,0,"-"]},"spython.utils.fileio":{mkdir_p:[14,1,1,""],read_file:[14,1,1,""],read_json:[14,1,1,""],write_file:[14,1,1,""],write_json:[14,1,1,""]},"spython.utils.terminal":{check_install:[14,1,1,""],format_container_name:[14,1,1,""],get_installdir:[14,1,1,""],get_singularity_version:[14,1,1,""],get_singularity_version_info:[14,1,1,""],get_userhome:[14,1,1,""],get_username:[14,1,1,""],remove_uri:[14,1,1,""],run_command:[14,1,1,""],split_uri:[14,1,1,""],stream_command:[14,1,1,""],which:[14,1,1,""]},spython:{client:[4,0,0,"-"],image:[5,0,0,"-"],instance:[7,0,0,"-"],logger:[9,0,0,"-"],main:[10,0,0,"-"],tests:[13,0,0,"-"],utils:[14,0,0,"-"],version:[3,0,0,"-"]}},objnames:{"0":["py","module","Python module"],"1":["py","function","Python function"],"2":["py","class","Python class"],"3":["py","method","Python method"],"4":["py","attribute","Python attribute"]},objtypes:{"0":"py:module","1":"py:function","2":"py:class","3":"py:method","4":"py:attribute"},terms:{"1024mib":6,"95m":9,"boolean":[0,7,9,10,11],"byte":11,"case":[7,10],"char":11,"class":[5,7,9,11,12],"default":[0,6,7,8,9,10,11,14],"export":[0,3,5,11],"function":[0,4,6,9,10,11,14],"import":[0,6,10],"int":9,"new":[6,11,14],"public":[3,12,14],"return":[0,5,7,8,9,10,11,12,14],"static":9,"true":[6,7,8,9,10,11,14],Added:0,Adding:0,For:[7,10,11,14],The:[0,1,6,8,10,11,12,14],These:6,Will:9,With:[7,8],__init__:6,_setup:0,abil:0,abl:7,abort:9,about:[10,11],accept:0,access:[10,11],account:0,action:[0,11],actual:[5,11],add:[0,7,14],addcolor:9,added:[0,6,7,9,10,11],adding:0,addit:[0,6,11],address:0,adjust:0,advis:[10,11],after:[7,8,11],alia:[7,11],all:[0,9,10,11,14],allow:[10,11],along:7,also:[11,12],ani:[10,11],api:0,app:[2,3,11],appear:11,append:14,applic:[11,14],arbitari:[10,11],aren:0,arg:[0,4,7,8,9,10,11],argpars:11,argument:[0,7,8,10,11],around:4,ascii:9,associ:[10,11],assum:[8,9],attempt:[11,14],attribut:[0,12],automat:[10,11],avail:[11,14],back:[0,11,14],backward:0,balloons_cursor:9,bar:[7,9,14],base:[0,3,4,5,7,9,10,12,14],basic:0,been:0,behaviour:0,being:9,bind:[0,7,8,10,11],bindlist:11,bootstrap:11,both:[4,11],bourn:11,bpython:4,branch:0,bug:0,build:[0,2,3,11,12],build_fold:[10,11],builder:[10,11],built:[10,11],bundl:11,call:[0,7,8,9,10,11],calledprocesserror:0,can:[0,3,4,7,11,12,14],capabl:11,captur:[7,8,10,11,14],carriage_return:9,chang:0,changelog:0,changing_arrow:9,charact:[9,14],check:[0,9,11,14],check_instal:14,cli:0,client:[0,1,2,3,6,7,8,10,11,14],close:[0,14],cmd:[0,3,5,7,11,12,14],code:[0,3,10,11,12,14],coincid:0,col_width:9,color:9,column:[8,9],com:[0,7,10,11],command:[0,3,6,7,8,10,12,14],comment:12,commit:0,commonli:6,complet:[5,7,9,10,11],compos:0,compress:6,condit:0,configur:9,consol:[0,4,7,14],contain:[0,1,7,8,10,11,14],content:[1,2],context:[10,11],continu:0,convers:0,convert2boolean:9,convert:[0,3,4,10,11],copi:[0,3,12,14],copyright:[3,12,14],correct:0,couldn:[10,11],count:9,crash:0,creat:[3,5,7,8,9,10,11,14],criteria:5,critic:[0,9],crypto:11,current:9,custom:[6,9,10,11],data:[10,11,14],debug:[0,6,7,8,9,10,11],decompress:6,defil:[10,11],defin:[0,4,6,10,11,14],delai:9,delim:11,delimit:11,deprec:[0,6,10,11],deriv:[7,11],descriptor:11,dest:0,destin:0,determin:[4,7,9,14],dict:14,dictionari:[9,10,11,12],differ:[0,10,11],dimens:8,directli:[10,11],directori:[0,10,11,14],disabl:[10,11],displai:11,distribut:[3,12,14],docker:[0,3,5,7,10,11,14],dockerfil:0,dockerpars:[4,12],dockerrecip:0,dockerwrit:4,document:0,doe:[0,11],doesn:[7,10,11],don:[7,8,10,11,14],done:[7,8,9,10,11],driver:[10,11],each:[0,7,8,14],effect:14,either:[4,11,12],emb:4,emit:9,emiterror:9,emitoutput:9,empti:[0,11,14],empty_char:9,enabl:9,encod:9,end:14,enhanc:0,entir:[10,11],entri:9,entrypoint:[0,12],env:[0,10,11],environ:[0,3,4,6,8,9,10,11],environment:9,error:[0,9,10,11],error_log:7,etc:8,eventu:0,everi:9,exampl:[11,14],except:14,exec:[0,11],execut:[0,2,3,11],exist:[5,7,11],exit:[9,11],expand:0,expect:[8,10,11],expected_s:9,expos:[6,10,11],express:[0,7,14],ext3:[6,10,11],ext4:6,ext:[6,10,11],extens:[10,11],fail:0,fals:[0,6,7,8,9,10,11,14],file:[0,3,4,5,6,10,11,12,14],fileio:[2,3],filenam:[11,14],filesystem:[6,10,11],filled_char:9,find:[7,14],first:[5,9,11],fix:[0,10],flag:[0,3,10],flexibl:0,folder:[10,11],follow:11,forc:[0,10,11],forcebl:[7,8],form:[3,12,14],format:[0,14],format_container_nam:14,format_tim:9,found:[10,11,14],free:12,from:[0,5,6,7,8,10,11,14],fromhead:0,full:[0,6,10,11,14],full_path:[10,11],fun:[10,11],ged:[10,11],gener:[0,3,7,9,10],generate_bind_list:11,generate_image_command:[6,11],generate_instance_command:8,generate_nam:7,get:[5,7,8,10,11,14],get_client:10,get_filenam:11,get_hash:5,get_installdir:14,get_log:9,get_logging_level:9,get_pars:4,get_singularity_vers:[0,7,11,14],get_singularity_version_info:[11,14],get_uri:[7,11],get_user_color_prefer:9,get_userhom:14,get_usernam:14,github:[0,7,10,11],give:[4,5,7,8,10,11],given:[0,6,11],global:11,group:[0,6,8,10,11],guidanc:0,haikun:11,handl:[0,5,7],hang:[0,7,8],hardcod:0,has:[0,9,11],hash:[0,5],have:[0,7,11,14],header:[0,8],help:[2,3,4,11],helpcmd:10,helper:6,here:0,hide:9,highli:[10,11],histori:9,hold:[6,8],home:14,honor:0,host:[10,11],how:0,howev:[10,11],http:[0,3,7,10,11,12,14],hub:[10,11],ideal:8,ignor:0,imag:[0,1,2,3,4,7,8,10,11,14],image_path:[6,10,11],imagebas:[5,7],imagecli:11,implement:[0,11],import_typ:6,importcmd:[3,5],imprt:6,includ:[0,9,10,11,12],incompat:0,increas:11,index:1,indic:[7,8],info:9,inform:[9,10,11],init:10,init_command:11,init_level:11,initi:[10,11],input:6,input_sourc:6,inspect:[0,2,3,11],inspir:11,instal:[10,11,12,14],instanc:[0,1,2,3,11],instance_stopal:11,instead:[10,11],integ:8,integr:[10,11],intend:[5,12],interact:[11,12],interest:[7,14],interpret:9,ipython:4,is_quiet:9,isenabledfor:9,isol:[10,11],issu:[0,7,10,11],issuecom:7,item:0,iter:[9,10,11],iutil:[3,7],join:9,join_newlin:9,json:[0,8,10,11,12,14],json_obj:14,jsonifi:8,just:[7,10,11],kei:9,kill:[0,7,8],know:0,kwarg:7,label:[9,10,11,12],last:0,later:7,launch:11,length:[9,11],level:[0,4,5,6,8,9,11],libexecdir:[10,11],librari:[0,14],licens:[1,3,12,14],like:[0,7,8],line:[0,7,11,14],lint:11,linux:[10,11],list:[0,6,7,8,9,10,11,14],list_inst:10,load:[0,5,7,10,11],local:11,locat:[10,11,12],log:[0,7,9,11],logger:[0,1,2,3,10],look:[8,10,11],main:[0,1,2,3,4,6,7,8,9],mainli:6,make:[10,11],manag:11,manual:0,map:[10,11],master:0,match:8,md5:5,mean:[7,10,11,14],merg:0,messag:[0,2,3,4,10,11],messagelevel:[6,8,9],metadata:11,mib:6,migrat:0,min_level:9,miss:0,mkdir:14,mkdir_p:14,mode:[10,14],modul:[1,2],more:[0,10,11],most:10,mount:[10,11],move:0,mozilla:[3,12,14],mpl:[1,3,12,14],much:11,multipl:0,multistag:0,must:[6,8,9,11],name:[0,7,8,9,10,11,14],name_by_commit:0,name_by_hash:0,namespac:[5,7],need:[7,8,10,11,14],newlin:[7,9,14],nicer:14,no_newline_regexp:[7,14],non:[0,7,8,11],none:[5,6,7,8,9,10,11,12,14],normal:11,note:[10,11],now:0,number:[8,9],nvidia:[0,10,11],object:[5,7,9,10,11,12,14],obtain:[3,12,14],oci:[0,11],one:[0,3,7,10,11,12,14],onli:[0,5,9,10,11],open:14,option:[0,4,6,7,8,9,10,11,14],orb:0,org:[3,12,14],origin:[0,10,12],other:[7,10,11,14],otherwis:[0,11],out:9,outfil:4,output:[0,4,7,8,10,11,14],output_fil:11,output_log:7,over:11,overrid:[0,10,11],overriden:11,packag:[1,2],package_url:0,param:[9,14],paramet:[0,5,6,7,8,10,11,12,14],parent:[7,11,14],pars:[0,3,5,7,8,10,11,14],parse_image_nam:[5,7],parse_label:10,parse_t:8,parse_verbos:11,parser:[0,4,12],part:14,pass:[6,7,8,10,11,14],path:[0,5,6,7,8,10,11,14],paus:0,perform:[10,11],persist:11,pid:[8,10,11],pipe:[7,11,14],point:0,poorli:11,popualt:12,port:12,prefer:[10,11],prefix:9,prepar:4,prepare_cli:4,preserv:[10,11],pretti:14,pretty_print:14,print:[0,4,7,9,10,11,12,14],print_log:7,print_pretti:14,println:[0,9,11],prioriti:5,privat:0,process:11,progess:[7,14],programat:7,progress:[2,3,7,14],progressbar:9,prompt:9,properli:[6,14],protocol:14,provid:[6,7,8,10,11],pull:[0,2,3,11],pull_fold:[10,11],pwd:[10,11],pypi:0,python:[0,4,11],quiet:[0,6,7,8,9,10,11,14],raw:[10,11],read:[10,11,14],read_fil:14,read_json:14,readlin:14,reason:[5,10,11],recip:[0,2,3,10,11],recommend:[10,11],refactor:0,registri:[5,7],regular:[0,7,14],rel:[0,10,11],releas:0,relev:0,remov:[0,8,14],remove_row:8,remove_uri:14,renam:0,replac:[0,11],report:14,repositori:0,repres:0,request:0,requir:[0,10,11],respect:[0,12],result:[10,11,14],resum:0,return_cod:9,return_json:[8,10,11],return_result:[0,10,11],revers:0,ride:11,right:0,robot:[7,10,11],robot_nam:[10,11],robotnam:[7,11],root:[10,11],row:[8,9],run:[0,2,3,7,8,9,11,14],run_bpython:4,run_command:[0,7,11,14],runscript:[0,10,11,12],runtim:[10,11],same:14,sandbox:[10,11],save:[4,5],sci:[10,11],scientif:[10,11],scif:[10,11],screen:[7,10,11],second:[5,7,8,9],section:0,see:[7,10,11],seem:[10,11],select:9,select_gener:9,self:[6,8,10,11],selftest:11,semant:14,send:[0,7,10,11,14],send_command:11,sens:[10,11],sequenc:12,serv:[4,10,11],set:[4,7,9,10,11,14],set_verbos:4,setenv:11,share:[10,11],shell:[0,2,3,10,11],shortcut:[7,11],should:[0,7,9,10,11,14],show:[9,10,11],show_progress:9,shub:14,sif:[10,11],siflist:11,sign:11,signatur:11,silent:11,simag:11,simpli:[5,7],singl:[8,10,11],singular:[0,3,5,6,7,8,10,11,14],singularity_cachedir:[10,11],singularity_opt:[6,7,8,10,11],singularity_tmpdir:[10,11],singularityhub:0,singularitymessag:9,singularitypars:[4,12],singularityrecip:0,singularitywar:[10,11],size:6,sizein:6,skip:[7,14],slash:14,sochat:[3,12,14],softwar:[10,11,14],some:[0,11],sourc:[0,3,4,5,6,7,8,9,10,11,12,14],special:14,special_charact:14,specif:[10,11],specifi:[6,7,8,9,10,11],sphinx:0,spin:9,spinner:[2,3],spinning_cursor:9,split:[0,5,7,14],split_uri:14,spython:[0,1],src:0,standard:[9,10,11],start:[0,3,7,9,14],state:0,statement:0,stderr:[9,11],stdin:6,stdout:[0,7,9,11,14],stop:[0,3,7,9,10,11],stopal:10,stream:[0,9,10,11,14],stream_command:14,string:[7,8,10,11,14],strip:0,structur:14,sub:[10,11],subclass:12,subcommand:6,subject:[3,12,14],submodul:[1,2,5,7],subpackag:[1,2],subprocess:[7,14],success:[0,11],sudo:[0,6,7,8,10,11,14],sudo_opt:[0,7,10,11,14],suffix:9,summari:11,suppli:[7,14],support:[0,6,7,8,9,10,11],suppress:[10,11],sutil:[3,10],sylab:7,symbol:9,system:[6,10,11],tabl:[8,9],table_str:8,take:[7,10,11,14],taken:0,tar:[6,11],tempfil:0,temporari:[6,11],term:[3,12,14],termin:[0,2,3,7,9,11],test:[0,2,3,10,11,12],testscript:11,text:[9,10,11],thei:7,them:[0,10,11],thi:[0,3,4,5,7,8,10,11,12,14],those:14,timeout:[0,7,8],tmp:[7,8],tmptar:6,tokenchar:11,tokenlength:11,tool:[10,11],top:8,total:9,track:0,trail:14,tree:0,tupl:[7,8],turn:10,typo:0,ubuntu:[5,7,11],uid:[10,11,14],under:[1,6,9],unset:0,updat:10,uri:[0,5,7,8,10,11,14],url:0,usag:[7,8,11,14],use:[0,6,7,10,11,12,14],usecolor:9,used:[6,9,10,11],useful:[5,10,11],user:[0,4,7,8,9,10,11,12,14],uses:[7,11,14],using:[10,11],usual:9,utf:11,util:[1,2,3,5],valu:[0,10,11],vanessa:[3,12,14],variabl:[0,6,8,9,11],vault:0,verbos:[6,8,9,10,11],verbose1:9,verbose2:9,verbose3:9,verifi:11,version:[0,1,2,4,5,7,11,12,14],version_info:[0,11],via:[6,8],volum:12,want:[7,8],warn:[0,9],whatev:6,when:[0,7,8],where:[8,10,11,14],which:[9,10,11,14],width:9,within:[10,11],withour:[10,11],without:14,won:[7,14],work:[0,1],workdir:12,wrap:9,wrapper:[4,11],writabl:[0,10,11],write:[9,10,11,12,14],write_fil:14,write_json:14,writer:[0,4,12],x1b:9,yes:11,yield:14,you:[3,10,11,12,14],your:[9,10,11]},titles:["<no title>","Welcome to Singularity Python API\u2019s documentation!","spython","spython package","spython.client package","spython.image package","spython.image.cmd package","spython.instance package","spython.instance.cmd package","spython.logger package","spython.main package","spython.main.base package","spython.main.parse package","spython.tests package","spython.utils package"],titleterms:{"export":6,api:1,app:10,base:11,build:10,client:4,cmd:[6,8],command:11,content:[3,4,5,6,7,8,9,10,11,12,13,14],convert:12,creat:6,docker:12,document:1,environ:12,execut:10,fileio:14,flag:11,gener:11,help:10,imag:[5,6],importcmd:6,indic:1,inspect:10,instanc:[7,8,10],iutil:8,logger:[9,11],main:[10,11,12],messag:9,modul:[3,4,5,6,7,8,9,10,11,12,13,14],packag:[3,4,5,6,7,8,9,10,11,12,13,14],pars:12,progress:9,pull:10,python:1,recip:[4,12],run:10,shell:4,singular:[1,12],spinner:9,spython:[2,3,4,5,6,7,8,9,10,11,12,13,14],start:8,stop:8,submodul:[3,4,6,8,9,10,11,12,13,14],subpackag:[3,5,7,10],sutil:11,tabl:1,termin:14,test:[4,13],test_client:13,test_inst:13,test_util:13,util:[6,14],version:3,welcom:1}}) \ No newline at end of file diff --git a/docs/api/source/modules.html b/docs/api/source/modules.html index 69cc42fb..3e8b443c 100644 --- a/docs/api/source/modules.html +++ b/docs/api/source/modules.html @@ -21,10 +21,10 @@ - - - - + + + + @@ -84,7 +84,6 @@ diff --git a/docs/api/source/spython.client.html b/docs/api/source/spython.client.html index bcaeed97..395ba405 100644 --- a/docs/api/source/spython.client.html +++ b/docs/api/source/spython.client.html @@ -21,10 +21,10 @@ - - - - + + + + @@ -107,7 +107,6 @@
  • Module contents
  • -
  • CHANGELOG
  • @@ -181,9 +180,9 @@

    Submodules

    spython.client.recipe module

    -
    +
    -spython.client.recipe.main(args, options, parser)[source]
    +spython.client.recipe.main(args, options, parser)[source]

    This function serves as a wrapper around the DockerParser, SingularityParser, DockerWriter, and SingularityParser converters. We can either save to file if args.outfile is defined, or print @@ -193,63 +192,63 @@

    Submodules

    spython.client.shell module

    -
    +
    -spython.client.shell.ipython(image)[source]
    +spython.client.shell.ipython(image)[source]

    give the user an ipython shell

    -
    +
    -spython.client.shell.main(args, options, parser)[source]
    +spython.client.shell.main(args, options, parser)[source]
    -
    +
    -spython.client.shell.prepare_client(image)[source]
    +spython.client.shell.prepare_client(image)[source]

    prepare a client to embed in a shell with recipe parsers and writers.

    -
    +
    -spython.client.shell.python(image)[source]
    +spython.client.shell.python(image)[source]

    give the user a python shell

    -
    +
    -spython.client.shell.run_bpython(image)[source]
    +spython.client.shell.run_bpython(image)[source]

    give the user a bpython shell

    spython.client.test module

    -
    +
    -spython.client.test.main(args, options, parser)[source]
    +spython.client.test.main(args, options, parser)[source]

    Module contents

    -
    +
    spython.client.get_parser()[source]
    -
    +
    spython.client.main()[source]
    -
    +
    -spython.client.set_verbosity(args)[source]
    +spython.client.set_verbosity(args)[source]

    determine the message level in the environment to set based on args.

    -
    +
    spython.client.version()[source]

    version prints the version, both for the user and help output

    diff --git a/docs/api/source/spython.html b/docs/api/source/spython.html index 3aadcb6a..3e034bb0 100644 --- a/docs/api/source/spython.html +++ b/docs/api/source/spython.html @@ -21,10 +21,10 @@ - - - - + + + + @@ -100,7 +100,6 @@
  • Module contents
  • -
  • CHANGELOG
  • diff --git a/docs/api/source/spython.image.cmd.html b/docs/api/source/spython.image.cmd.html index 6521976d..a828c01a 100644 --- a/docs/api/source/spython.image.cmd.html +++ b/docs/api/source/spython.image.cmd.html @@ -21,10 +21,10 @@ - - - - + + + + @@ -104,7 +104,6 @@
  • Module contents
  • -
  • CHANGELOG
  • @@ -180,9 +179,9 @@

    Submodules

    spython.image.cmd.create module

    -
    +
    -spython.image.cmd.create.create(self, image_path, size=1024, sudo=False)[source]
    +spython.image.cmd.create.create(self, image_path, size=1024, sudo=False, singularity_options=None)[source]

    create will create a a new image

    Parameters
    @@ -190,6 +189,7 @@

    Submodules

    spython.image.cmd.export module

    -
    +
    -spython.image.cmd.export.export(self, image_path, tmptar=None)[source]
    +spython.image.cmd.export.export(self, image_path, tmptar=None)[source]

    export will export an image, sudo must be used.

    Parameters
    @@ -215,9 +215,9 @@

    Submodules

    spython.image.cmd.importcmd module

    -
    +
    -spython.image.cmd.importcmd.importcmd(self, image_path, input_source)[source]
    +spython.image.cmd.importcmd.importcmd(self, image_path, input_source)[source]

    import will import (stdin) to the image

    Parameters
    @@ -233,22 +233,22 @@

    Submodules

    spython.image.cmd.utils module

    -
    +
    -spython.image.cmd.utils.compress(self, image_path)[source]
    +spython.image.cmd.utils.compress(self, image_path)[source]

    compress will (properly) compress an image

    -
    +
    -spython.image.cmd.utils.decompress(self, image_path, quiet=True)[source]
    +spython.image.cmd.utils.decompress(self, image_path, quiet=True)[source]

    decompress will (properly) decompress an image

    Module contents

    -
    +
    spython.image.cmd.generate_image_commands()[source]

    The Image client holds the Singularity image command group, mainly diff --git a/docs/api/source/spython.image.html b/docs/api/source/spython.image.html index 241726f5..ed578cb4 100644 --- a/docs/api/source/spython.image.html +++ b/docs/api/source/spython.image.html @@ -21,10 +21,10 @@ - - - - + + + + @@ -104,7 +104,6 @@

  • Module contents
  • -
  • CHANGELOG
  • @@ -191,13 +190,13 @@

    Subpackages

    Module contents

    -
    +
    -class spython.image.Image(image=None)[source]
    +class spython.image.Image(image=None)[source]

    Bases: spython.image.ImageBase

    -
    +
    -get_hash(image=None)[source]
    +get_hash(image=None)[source]

    return an md5 hash of the file based on a criteria level. This is intended to give the file a reasonable version. This only is useful for actual image files.

    @@ -210,13 +209,13 @@

    Subpackages +
    class spython.image.ImageBase[source]

    Bases: object

    -
    +
    -parse_image_name(image)[source]
    +parse_image_name(image)[source]

    simply split the uri from the image. Singularity handles parsing of registry, namespace, image.

    diff --git a/docs/api/source/spython.instance.cmd.html b/docs/api/source/spython.instance.cmd.html index ae956a24..ca3bc2cf 100644 --- a/docs/api/source/spython.instance.cmd.html +++ b/docs/api/source/spython.instance.cmd.html @@ -21,10 +21,10 @@ - - - - + + + + @@ -104,7 +104,6 @@
  • Module contents
  • -
  • CHANGELOG
  • @@ -180,16 +179,16 @@

    Submodules

    spython.instance.cmd.iutils module

    -
    +
    -spython.instance.cmd.iutils.get(self, name, return_json=False, quiet=False)[source]
    +spython.instance.cmd.iutils.get(self, name, return_json=False, quiet=False, singularity_options=None)[source]

    get is a list for a single instance. It is assumed to be running, and we need to look up the PID, etc.

    -
    +
    -spython.instance.cmd.iutils.parse_table(table_string, header, remove_rows=1)[source]
    +spython.instance.cmd.iutils.parse_table(table_string, header, remove_rows=1)[source]

    parse a table to json from a string, where a header is expected by default. Return a jsonified table.

    @@ -206,9 +205,9 @@

    Submodules

    spython.instance.cmd.start module

    -
    +
    -spython.instance.cmd.start.start(self, image=None, name=None, args=None, sudo=False, options=None, capture=False)[source]
    +spython.instance.cmd.start.start(self, image=None, name=None, args=None, sudo=False, options=None, capture=False, singularity_options=None)[source]

    start an instance. This is done by default when an instance is created.

    Parameters
    @@ -218,6 +217,7 @@

    Submodules

    spython.instance.cmd.stop module

    -
    +
    -spython.instance.cmd.stop.stop(self, name=None, sudo=False, timeout=None)[source]
    +spython.instance.cmd.stop.stop(self, name=None, sudo=False, timeout=None, singularity_options=None)[source]

    stop an instance. This is done by default when an instance is created.

    Parameters
    • name (a name for the instance)

    • sudo (if the user wants to run the command with sudo)

    • +
    • singularity_options (a list of options to provide to the singularity client)

    • timeout (forcebly kill non-stopped instance after the) – timeout specified in seconds

    • USAGE

    • singularity […] instance.stop […] <instance name>

    • @@ -249,7 +250,7 @@

      Submodules

      Module contents

      -
      +
      spython.instance.cmd.generate_instance_commands()[source]

      The Instance client holds the Singularity Instance command group diff --git a/docs/api/source/spython.instance.html b/docs/api/source/spython.instance.html index 53b89f81..a45cfcca 100644 --- a/docs/api/source/spython.instance.html +++ b/docs/api/source/spython.instance.html @@ -21,10 +21,10 @@ - - - - + + + + @@ -104,7 +104,6 @@

    • Module contents
    -
  • CHANGELOG
  • @@ -190,23 +189,23 @@

    Subpackages

    Module contents

    -
    +
    -class spython.instance.Instance(image, start=True, name=None, **kwargs)[source]
    +class spython.instance.Instance(image, start=True, name=None, **kwargs)[source]

    Bases: spython.image.ImageBase

    -
    +
    RobotNamer = <spython.main.base.generate.RobotNamer object>
    -
    +
    debug = False
    -
    +
    -error_logs(print_logs=False)
    +error_logs(print_logs=False)

    For Singularity 3.5 and later, we are able to programatically derive the name of the log. In this case, return the content to the user. See @@ -219,28 +218,28 @@

    Subpackages +
    -generate_name(name=None)[source]
    +generate_name(name=None)[source]

    generate a Robot Name for the instance to use, if the user doesn’t supply one.

    -
    +
    get_uri()[source]

    return the image uri (instance://) along with it’s name

    -
    +
    instance

    alias of Instance

    -
    +
    -output_logs(print_logs=False)
    +output_logs(print_logs=False)

    Get output logs for the user, if they exist.

    Parameters
    @@ -249,9 +248,9 @@

    Subpackages +
    -parse_image_name(image)[source]
    +parse_image_name(image)[source]

    simply split the uri from the image. Singularity handles parsing of registry, namespace, image.

    @@ -261,14 +260,14 @@

    Subpackages +
    quiet = False
    -
    +
    -run_command(sudo=False, capture=True, no_newline_regexp='Progess', quiet=False, sudo_options=None)
    +run_command(sudo=False, capture=True, no_newline_regexp='Progess', quiet=False, sudo_options=None)

    run_command uses subprocess to send a command to the terminal. If capture is True, we use the parent stdout, so the progress bar (and other commands of interest) are piped to the user. This means we @@ -287,9 +286,9 @@

    Subpackages +
    -start(image=None, name=None, args=None, sudo=False, options=None, capture=False)
    +start(image=None, name=None, args=None, sudo=False, options=None, capture=False, singularity_options=None)

    start an instance. This is done by default when an instance is created.

    Parameters
    @@ -299,6 +298,7 @@

    Subpackages +
    -stop(name=None, sudo=False, timeout=None)
    +stop(name=None, sudo=False, timeout=None, singularity_options=None)

    stop an instance. This is done by default when an instance is created.

    Parameters
    • name (a name for the instance)

    • sudo (if the user wants to run the command with sudo)

    • +
    • singularity_options (a list of options to provide to the singularity client)

    • timeout (forcebly kill non-stopped instance after the) – timeout specified in seconds

    • USAGE

    • singularity […] instance.stop […] <instance name>

    • @@ -324,9 +325,9 @@

      Subpackages +
      -classmethod version()
      +version()

      Shortcut to get_singularity_version, takes no arguments.

      diff --git a/docs/api/source/spython.logger.html b/docs/api/source/spython.logger.html index ee26bcdf..b258f75c 100644 --- a/docs/api/source/spython.logger.html +++ b/docs/api/source/spython.logger.html @@ -21,10 +21,10 @@ - - - - + + + + @@ -107,7 +107,6 @@
    • Module contents
    -
  • CHANGELOG
  • @@ -181,40 +180,40 @@

    Submodules

    spython.logger.message module

    -
    +
    -class spython.logger.message.SingularityMessage(MESSAGELEVEL=None)[source]
    +class spython.logger.message.SingularityMessage(MESSAGELEVEL=None)[source]

    Bases: object

    -
    +
    -abort(message)[source]
    +abort(message)[source]
    -
    +
    -addColor(level, text)[source]
    +addColor(level, text)[source]

    addColor to the prompt (usually prefix) if terminal supports, and specified to do so

    -
    +
    -critical(message)[source]
    +critical(message)[source]
    -
    +
    -custom(prefix, message, color='\x1b[95m')[source]
    +custom(prefix, message, color='\x1b[95m')[source]
    -
    +
    -debug(message)[source]
    +debug(message)[source]
    -
    +
    -emit(level, message, prefix=None, color=None)[source]
    +emit(level, message, prefix=None, color=None)[source]

    emit is the main function to print the message optionally with a prefix :param level: the level of the message @@ -222,92 +221,92 @@

    Submodules +
    -emitError(level)[source]
    +emitError(level)[source]

    determine if a level should print to stderr, includes all levels but INFO and QUIET

    -
    +
    -emitOutput(level)[source]
    +emitOutput(level)[source]

    determine if a level should print to stdout only includes INFO

    -
    +
    -error(message)[source]
    +error(message)[source]
    -
    +
    -exit(message, return_code=1)[source]
    +exit(message, return_code=1)[source]
    -
    +
    -get_logs(join_newline=True)[source]
    +get_logs(join_newline=True)[source]

    ‘get_logs will return the complete history, joined by newline (default) or as is.

    -
    +
    -info(message)[source]
    +info(message)[source]
    -
    +
    -isEnabledFor(messageLevel)[source]
    +isEnabledFor(messageLevel)[source]

    check if a messageLevel is enabled to emit a level

    -
    +
    is_quiet()[source]

    is_quiet returns true if the level is under 1

    -
    +
    -log(message)[source]
    +log(message)[source]
    -
    +
    newline()[source]
    -
    +
    -println(message)[source]
    +println(message)[source]
    -
    +
    -show_progress(iteration, total, length=40, min_level=0, prefix=None, carriage_return=True, suffix=None, symbol=None)[source]
    +show_progress(iteration, total, length=40, min_level=0, prefix=None, carriage_return=True, suffix=None, symbol=None)[source]

    create a terminal progress bar, default bar shows for verbose+ :param iteration: current iteration (Int) :param total: total iterations (Int) :param length: character length of bar (Int)

    -
    +
    spinner = <spython.logger.spinner.Spinner object>
    -
    +
    -table(rows, col_width=2)[source]
    +table(rows, col_width=2)[source]

    table will print a table of entries. If the rows is a dictionary, the keys are interpreted as column names. if not, a numbered list is used.

    -
    +
    useColor()[source]

    useColor will determine if color should be added @@ -315,48 +314,48 @@

    Submodules +
    -verbose(message)[source]
    +verbose(message)[source]
    -
    +
    -verbose1(message)[source]
    +verbose1(message)[source]
    -
    +
    -verbose2(message)[source]
    +verbose2(message)[source]
    -
    +
    -verbose3(message)[source]
    +verbose3(message)[source]
    -
    +
    -warning(message)[source]
    +warning(message)[source]
    -
    +
    -write(stream, message)[source]
    +write(stream, message)[source]

    write will write a message to a stream, first checking the encoding

    -
    +
    -spython.logger.message.convert2boolean(arg)[source]
    +spython.logger.message.convert2boolean(arg)[source]

    convert2boolean is used for environmental variables that must be returned as boolean

    -
    +
    spython.logger.message.get_logging_level()[source]

    configure a logging to standard out based on the user’s @@ -365,7 +364,7 @@

    Submodules +
    spython.logger.message.get_user_color_preference()[source]
    @@ -373,81 +372,81 @@

    Submodules

    spython.logger.progress module

    -
    +
    -class spython.logger.progress.ProgressBar(label='', width=32, hide=None, empty_char=' ', filled_char='-', expected_size=None, every=1)[source]
    +class spython.logger.progress.ProgressBar(label='', width=32, hide=None, empty_char=' ', filled_char='-', expected_size=None, every=1)[source]

    Bases: object

    -
    +
    done()[source]
    -
    +
    -format_time(seconds)[source]
    +format_time(seconds)[source]
    -
    +
    -show(progress, count=None)[source]
    +show(progress, count=None)[source]
    -
    +
    -spython.logger.progress.bar(it, label='', width=32, hide=None, empty_char=' ', filled_char='-', expected_size=None, every=1)[source]
    +spython.logger.progress.bar(it, label='', width=32, hide=None, empty_char=' ', filled_char='-', expected_size=None, every=1)[source]

    Progress iterator. Wrap your iterables with it.

    spython.logger.spinner module

    -
    +
    -class spython.logger.spinner.Spinner(delay=None, generator=None)[source]
    +class spython.logger.spinner.Spinner(delay=None, generator=None)[source]

    Bases: object

    -
    +
    static balloons_cursor()[source]
    -
    +
    static changing_arrows()[source]
    -
    +
    delay = 0.1
    -
    +
    run()[source]
    -
    +
    -select_generator(generator)[source]
    +select_generator(generator)[source]
    -
    +
    spinning = False
    -
    +
    static spinning_cursor()[source]
    -
    +
    start()[source]
    -
    +
    stop()[source]
    diff --git a/docs/api/source/spython.main.base.html b/docs/api/source/spython.main.base.html index c77c0c3e..6fa1b14f 100644 --- a/docs/api/source/spython.main.base.html +++ b/docs/api/source/spython.main.base.html @@ -21,10 +21,10 @@ - - - - + + + + @@ -113,7 +113,6 @@
  • Module contents
  • -
  • CHANGELOG
  • @@ -189,9 +188,9 @@

    Submodules

    spython.main.base.command module

    -
    +
    -spython.main.base.command.generate_bind_list(self, bindlist=None)[source]
    +spython.main.base.command.generate_bind_list(self, bindlist=None)[source]
    generate bind string will take a single string or list of binds, and

    return a list that can be added to an exec or run command. For example, the following map as follows:

    @@ -208,23 +207,23 @@

    Submodules +
    -spython.main.base.command.init_command(self, action, flags=None)[source]
    +spython.main.base.command.init_command(self, action, flags=None)[source]

    return the initial Singularity command with any added flags.

    Parameters
    • action (the main action to perform (e.g., build))

    • -
    • flags (one or more additional flags (e.g, volumes)) – not implemented yet.

    • +
    • flags (one or more additional singularity options)

    -
    +
    -spython.main.base.command.run_command(self, cmd, sudo=False, capture=True, quiet=None, return_result=False, sudo_options=None)[source]
    +spython.main.base.command.run_command(self, cmd, sudo=False, capture=True, quiet=None, return_result=False, sudo_options=None)[source]

    run_command is a wrapper for the global run_command, checking first for sudo and exiting on error if needed. The message is returned as a list of lines for the calling function to parse, and stdout uses @@ -243,9 +242,9 @@

    Submodules +
    -spython.main.base.command.send_command(self, cmd, sudo=False, stderr=None, stdout=None)[source]
    +spython.main.base.command.send_command(self, cmd, sudo=False, stderr=None, stdout=None)[source]

    send command is a non interactive version of run_command, meaning that we execute the command and return the return value, but don’t attempt to stream any content (text from the screen) back to the @@ -302,9 +301,9 @@

    Submodules +
    -spython.main.base.flags.parse_verbosity(self, args)[source]
    +spython.main.base.flags.parse_verbosity(self, args)[source]

    parse_verbosity will take an argument object, and return the args passed (from a dictionary) to a list

    @@ -317,13 +316,13 @@

    Submodules

    spython.main.base.generate module

    -
    +
    class spython.main.base.generate.RobotNamer[source]

    Bases: object

    -
    +
    -generate(delim='-', length=4, chars='0123456789')[source]
    +generate(delim='-', length=4, chars='0123456789')[source]
    Generate a robot name. Inspiration from Haikunator, but much more

    poorly implemented ;)

    @@ -341,7 +340,7 @@

    Submodules +
    spython.main.base.generate.main()[source]
    @@ -349,9 +348,9 @@

    Submodules

    spython.main.base.logger module

    -
    +
    -spython.main.base.logger.init_level(self, quiet=False)[source]
    +spython.main.base.logger.init_level(self, quiet=False)[source]

    set the logging level based on the environment

    Parameters
    @@ -360,9 +359,9 @@

    Submodules +
    -spython.main.base.logger.println(self, output, quiet=False)[source]
    +spython.main.base.logger.println(self, output, quiet=False)[source]

    print will print the output, given that quiet is not True. This function also serves to convert output in bytes to utf-8

    @@ -378,9 +377,9 @@

    Submodules

    spython.main.base.sutils module

    -
    +
    -spython.main.base.sutils.get_filename(self, image, ext='sif', pwd=True)[source]
    +spython.main.base.sutils.get_filename(self, image, ext='sif', pwd=True)[source]

    return an image filename based on the image uri.

    Parameters
    @@ -392,16 +391,16 @@

    Submodules +
    -spython.main.base.sutils.get_uri(self)[source]
    +spython.main.base.sutils.get_uri(self)[source]

    check if the loaded image object (self.simage) has an associated uri return if yes, None if not.

    -
    +
    -spython.main.base.sutils.load(self, image=None)[source]
    +spython.main.base.sutils.load(self, image=None)[source]

    load an image, either an actual path on the filesystem or a uri.

    Parameters
    @@ -410,9 +409,9 @@

    Submodules +
    -spython.main.base.sutils.setenv(self, variable, value)[source]
    +spython.main.base.sutils.setenv(self, variable, value)[source]

    set an environment variable for Singularity

    Parameters
    @@ -427,22 +426,23 @@

    Submodules

    Module contents

    -
    +
    class spython.main.base.Client[source]

    Bases: object

    -
    +
    RobotNamer = <spython.main.base.generate.RobotNamer object>
    -
    +
    -apps(image=None, full_path=False, root='')
    +apps(image=None, full_path=False, root='')

    return list of SCIF apps in image. The Singularity software serves a scientific filesystem integration that will install apps to /scif/apps and associated data to /scif/data. For more information -about SCIF, see https://sci-f.github.io

    +about SCIF, see https://sci-f.github.io. Note that this seems +to be deprecated in Singularity 3.x.

    Parameters

    -
    -

    spython.tests.test_client module

    -
    -
    -spython.tests.test_client.test_build_from_docker(tmp_path)[source]
    -
    - -
    -
    -spython.tests.test_client.test_docker_pull(docker_container)[source]
    -
    - -
    -
    -spython.tests.test_client.test_execute(docker_container)[source]
    -
    - -
    -
    -spython.tests.test_client.test_execute_with_return_code(docker_container)[source]
    -
    - -
    -
    -spython.tests.test_client.test_export()[source]
    -
    - -
    -
    -spython.tests.test_client.test_inspect(docker_container)[source]
    -
    - +
    +

    spython.tests.test_client module

    -
    -

    spython.tests.test_instances module

    -
    -
    -class spython.tests.test_instances.TestInstanceFuncs[source]
    -

    Bases: object

    -
    -
    -cleanup()[source]
    -
    - -
    -
    -test_instance_cmds(docker_container)[source]
    -
    - -
    - -
    -
    -spython.tests.test_instances.test_has_no_instances()[source]
    -
    - -
    -
    -spython.tests.test_instances.test_instance_class()[source]
    -
    - +
    +

    spython.tests.test_instances module

    -
    -

    spython.tests.test_utils module

    -
    -
    -spython.tests.test_utils.test_ScopedEnvVar()[source]
    -
    - -
    -
    -spython.tests.test_utils.test_check_get_singularity_version()[source]
    -

    check that the singularity version is found to be that installed

    -
    - -
    -
    -spython.tests.test_utils.test_check_get_singularity_version_info()[source]
    -

    Check that the version_info is correct

    -
    - -
    -
    -spython.tests.test_utils.test_check_install()[source]
    -

    check install is used to check if a particular software is installed. -If no command is provided, singularity is assumed to be the test case

    -
    - -
    -
    -spython.tests.test_utils.test_decode()[source]
    -
    - -
    -
    -spython.tests.test_utils.test_get_installdir()[source]
    -

    get install directory should return the base of where singularity -is installed

    -
    - -
    -
    -spython.tests.test_utils.test_remove_uri()[source]
    -
    - -
    -
    -spython.tests.test_utils.test_split_uri()[source]
    -
    - -
    -
    -spython.tests.test_utils.test_write_bad_json(tmp_path)[source]
    -
    - -
    -
    -spython.tests.test_utils.test_write_json(tmp_path)[source]
    -
    - -
    -
    -spython.tests.test_utils.test_write_read_files(tmp_path)[source]
    -

    test_write_read_files will test the functions write_file and read_file

    -
    - +
    +

    spython.tests.test_utils module

    Module contents

    diff --git a/docs/api/source/spython.utils.html b/docs/api/source/spython.utils.html index bfad7ab9..4f82d52e 100644 --- a/docs/api/source/spython.utils.html +++ b/docs/api/source/spython.utils.html @@ -21,10 +21,10 @@ - - - - + + + + @@ -35,7 +35,7 @@ - + @@ -106,7 +106,6 @@
  • Module contents
  • -
  • CHANGELOG
  • @@ -184,37 +183,37 @@

    Submoduleshttp://mozilla.org/MPL/2.0/.

    -
    +
    -spython.utils.fileio.mkdir_p(path)[source]
    +spython.utils.fileio.mkdir_p(path)[source]

    mkdir_p attempts to get the same functionality as mkdir -p :param path: the path to create.

    -
    +
    -spython.utils.fileio.read_file(filename, mode='r', readlines=True)[source]
    +spython.utils.fileio.read_file(filename, mode='r', readlines=True)[source]

    write_file will open a file, “filename” and write content, “content” and properly close the file

    -
    +
    -spython.utils.fileio.read_json(filename, mode='r')[source]
    +spython.utils.fileio.read_json(filename, mode='r')[source]

    read_json reads in a json file and returns the data structure as dict.

    -
    +
    -spython.utils.fileio.write_file(filename, content, mode='w')[source]
    +spython.utils.fileio.write_file(filename, content, mode='w')[source]

    write_file will open a file, “filename” and write content, “content” and properly close the file

    -
    +
    -spython.utils.fileio.write_json(json_obj, filename, mode='w', print_pretty=True)[source]
    +spython.utils.fileio.write_json(json_obj, filename, mode='w', print_pretty=True)[source]

    write_json will (optionally,pretty print) a json object to file :param json_obj: the dict to print to json :param filename: the output file to write to @@ -228,29 +227,29 @@

    Submoduleshttp://mozilla.org/MPL/2.0/.

    -
    +
    -spython.utils.terminal.check_install(software='singularity', quiet=True)[source]
    +spython.utils.terminal.check_install(software='singularity', quiet=True)[source]

    check_install will attempt to run the singularity command, and return True if installed. The command line utils will not run without this check.

    -
    +
    -spython.utils.terminal.format_container_name(name, special_characters=None)[source]
    +spython.utils.terminal.format_container_name(name, special_characters=None)[source]

    format_container_name will take a name supplied by the user, remove all special characters (except for those defined by “special-characters” and return the new image name.

    -
    +
    spython.utils.terminal.get_installdir()[source]

    get_installdir returns the installation directory of the application

    -
    +
    spython.utils.terminal.get_singularity_version()[source]

    get the full singularity client version as reported by @@ -258,33 +257,33 @@

    Submodules +
    spython.utils.terminal.get_singularity_version_info()[source]

    get the full singularity client version as a semantic version”

    -
    +
    spython.utils.terminal.get_userhome()[source]

    get the user home based on the effective uid

    -
    +
    spython.utils.terminal.get_username()[source]

    get the user name based on the effective uid

    -
    +
    -spython.utils.terminal.remove_uri(container)[source]
    -

    remove_uri will remove docker:// or shub:// from the uri

    +spython.utils.terminal.remove_uri(container)[source] +

    remove_uri will remove docker:// or shub:// or library:// from the uri

    -
    +
    -spython.utils.terminal.run_command(cmd, sudo=False, capture=True, no_newline_regexp='Progess', quiet=False, sudo_options=None)[source]
    +spython.utils.terminal.run_command(cmd, sudo=False, capture=True, no_newline_regexp='Progess', quiet=False, sudo_options=None)[source]

    run_command uses subprocess to send a command to the terminal. If capture is True, we use the parent stdout, so the progress bar (and other commands of interest) are piped to the user. This means we @@ -303,17 +302,17 @@

    Submodules +
    -spython.utils.terminal.split_uri(container)[source]
    +spython.utils.terminal.split_uri(container)[source]

    Split the uri of a container into the protocol and image part

    An empty protocol is returned if none found. A trailing slash is removed from the image part.

    -
    +
    -spython.utils.terminal.stream_command(cmd, no_newline_regexp='Progess', sudo=False, sudo_options=None)[source]
    +spython.utils.terminal.stream_command(cmd, no_newline_regexp='Progess', sudo=False, sudo_options=None)[source]

    stream a command (yield) back to the user, as each line is available.

    # Example usage: results = [] @@ -333,9 +332,9 @@

    Submodules +
    -spython.utils.terminal.which(software='singularity')[source]
    +spython.utils.terminal.which(software='singularity')[source]

    which returns the full path to where software is installed.

    @@ -353,7 +352,7 @@

    Submodules - + diff --git a/docs/pages/recipes.md b/docs/pages/recipes.md index 86f52c0b..d8cac8eb 100644 --- a/docs/pages/recipes.md +++ b/docs/pages/recipes.md @@ -22,6 +22,10 @@ Now we can answer what kind of things might you want to do: - convert a Singularity Recipe to a Dockerfile - read in a recipe of either type, and modify it before doing the above +**Important** Singularity Python added support for parsing [multistage builds](https://sylabs.io/guides/3.5/user-guide/definition_files.html#multi-stage-builds) for version 0.0.83 and after. By default, +any base layer that isn't named is called `spython-base` unless you have named +it otherwise. + # Command Line Client @@ -31,7 +35,7 @@ for quick visual inspection or piping into an output file. If you use the `spython` utility, you can see your options available: -``` +```bash spython recipe --help usage: spython recipe [-h] [--entrypoint ENTRYPOINT] [--json] [--force] @@ -108,7 +112,7 @@ $ spython recipe --parser singularity container.def Another customization to a recipe can be modifying the entrypoint on the fly. -``` +```bash $ spython recipe --entrypoint /bin/sh Dockerfile ... %runscript @@ -150,7 +154,7 @@ technology) you can do that. ```python from spython.main.parse.recipe import Recipe -recipe = Recipe +recipe = Recipe() ``` By default, the recipe starts empty. @@ -175,6 +179,7 @@ recipe.labels = ['Maintainer vanessasaur'] recipe.ports = ['3031'] recipe.volumes = ['/data'] recipe.workdir = '/code' +recipe.fromHeader = 'ubuntu:18:04' ``` And then verify they are added: @@ -195,6 +200,24 @@ recipe.json() ``` And then you can use a [writer](#writer) to print a custom recipe type to file. +Note that the writer is intended for multistage builds, meaning that the recipe +you provide it should be a lookup with sections. For example: + +``` +from spython.main.parse.writers import DockerWriter +writer = DockerWriter({"baselayer": recipe}) + +FROM ubuntu:18:04 AS baselayer +ADD one two +LABEL Maintainer vanessasaur +ENV PANCAKES=WITHSYRUP +RUN apt-get update +EXPOSE 3031 +WORKDIR /code +CMD ['echo', 'hello'] +ENTRYPOINT /bin/bash +HEALTHCHECK true +``` # Parsers @@ -220,42 +243,66 @@ then give it a Dockerfile to munch on. parser=DockerParser('Dockerfile') ``` -By default, it will parse the Dockerfile (or other container recipe) into a `Recipe` -class, provided at `parser.recipe`: +By default, it will parse the Dockerfile (or other container recipe) into a lookup of `Recipe` +class, each of which is a layer / stage for the build. ```python -parser.recipe -[spython-recipe][source:/home/vanessa/Documents/Dropbox/Code/sregistry/singularity-cli/Dockerfile] +> parser.recipe +{'builder': [spython-recipe][source:/home/vanessa/Desktop/Code/singularity-cli/Dockerfile], + 'runner': [spython-recipe][source:/home/vanessa/Desktop/Code/singularity-cli/Dockerfile]} ``` -You can quickly see the fields with the .json function: +In the above, we see that the Dockerfile has two staged, the first named `builder` +and the second named `runner`. You can inspect each of these recipes by indexing into +the dictionary. E.g., here is how to look at the .json output as we did previously: ```python -parser.recipe.json() -{'cmd': '/code/run_uwsgi.sh', - 'environ': ['PYTHONUNBUFFERED=1'], - 'files': [['requirements.txt', '/tmp/requirements.txt'], - ['/home/vanessa/Documents/Dropbox/Code/sregistry/singularity-cli', - '/code/']], - 'install': ['PYTHONUNBUFFERED=1', -... -``` - +parser.recipe['runner'].json() +Out[6]: +{'fromHeader': 'ubuntu:20.04 ', + 'layer_files': {'builder': [['/build_thirdparty/usr/', '/usr/'], + ['/build${PROJ_INSTALL_PREFIX}/share/proj/', + '${PROJ_INSTALL_PREFIX}/share/proj/'], + ['/build${PROJ_INSTALL_PREFIX}/include/', + '${PROJ_INSTALL_PREFIX}/include/'], + ['/build${PROJ_INSTALL_PREFIX}/bin/', '${PROJ_INSTALL_PREFIX}/bin/'], + ['/build${PROJ_INSTALL_PREFIX}/lib/', '${PROJ_INSTALL_PREFIX}/lib/'], + ['/build/usr/share/gdal/', '/usr/share/gdal/'], + ['/build/usr/include/', '/usr/include/'], + ['/build_gdal_python/usr/', '/usr/'], + ['/build_gdal_version_changing/usr/', '/usr/']]}, + 'install': ['\n', + 'date', + '\n', + '# PROJ dependencies\n', + 'apt-get update; \\', + 'DEBIAN_FRONTEND=noninteractive apt-get install -y \\', + ... + '\n', + 'ldconfig']} +``` + +Notice in the above that we have a section called `layer_files` that a writer knows +how to parse into a `%files` section from the previous layer. All of these fields are attributes of the recipe, so you could change or otherwise -interact with them: +interact with them. For example, here we are adding an entrypoint. ```python -parser.recipe.entrypoint = '/bin/sh' +parser.recipe['runner'].entrypoint = '/bin/sh' ``` -or if you don't want to, you can skip automatic parsing: +or if you don't want to, you can skip automatic parsing. Here we inspect a single, +empty recipe layer: ```python -parser = DockerParser('Dockerfile', load=False) -parser.recipe.json() +parser = DockerParser('Dockerfile', load=False) parser.recipe +{'spython-base': [spython-recipe][source:/home/vanessa/Desktop/Code/singularity-cli/Dockerfile]} + +parser.recipe['spython-base'].json() +{} ``` -And then parse it later: +WHen you are ready to parse it (to show the layers we saw previously) ```python parser.parse() @@ -268,9 +315,10 @@ SingularityParser = get_parser("Singularity") parser = SingularityParser("Singularity") ``` ```python -parser.recipe.json() -Out[16]: +parser.recipe['spython-base'].json() +Out[21]: {'cmd': 'exec /opt/conda/bin/spython "$@"', + 'fromHeader': 'continuumio/miniconda3', 'install': ['apt-get update && apt-get install -y git', '# Dependencies', 'cd /opt', @@ -279,7 +327,6 @@ Out[16]: '/opt/conda/bin/pip install setuptools', '/opt/conda/bin/python setup.py install'], 'labels': ['maintainer vsochat@stanford.edu']} - ``` # Writers @@ -352,7 +399,7 @@ writer = DockerWriter(parser.recipe) print(writer.convert()) ``` ``` -FROM continuumio/miniconda3 +FROM continuumio/miniconda3 AS spython-base LABEL maintainer vsochat@stanford.edu RUN apt-get update && apt-get install -y git RUN cd /opt diff --git a/spython/image/__init__.py b/spython/image/__init__.py index 28c25612..ac68b60e 100644 --- a/spython/image/__init__.py +++ b/spython/image/__init__.py @@ -6,7 +6,6 @@ import hashlib import os -import re from spython.logger import bot from spython.utils import split_uri diff --git a/spython/instance/cmd/logs.py b/spython/instance/cmd/logs.py index 58a0349b..2f005577 100644 --- a/spython/instance/cmd/logs.py +++ b/spython/instance/cmd/logs.py @@ -41,7 +41,7 @@ def _logs(self, print_logs=False, ext="out"): """A shared function to print log files. The only differing element is the extension (err or out) """ - from spython.utils import check_install, run_command + from spython.utils import check_install check_install() diff --git a/spython/main/base/__init__.py b/spython/main/base/__init__.py index e6ee3907..679ded74 100644 --- a/spython/main/base/__init__.py +++ b/spython/main/base/__init__.py @@ -18,11 +18,6 @@ from .logger import println, init_level from .generate import RobotNamer -import json -import sys -import os -import re - class Client: def __str__(self): diff --git a/spython/main/parse/parsers/base.py b/spython/main/parse/parsers/base.py index f727d745..b0cabb82 100644 --- a/spython/main/parse/parsers/base.py +++ b/spython/main/parse/parsers/base.py @@ -5,7 +5,9 @@ # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. import abc +from copy import deepcopy import os +import re from spython.logger import bot from spython.utils import read_file @@ -35,7 +37,14 @@ def __init__(self, filename, load=True): self.filename = filename self._run_checks() self.lines = [] - self.recipe = Recipe(self.filename) + + # Arguments can be used internally, active layer name and number + self.args = {} + self.active_layer = "spython-base" + self.active_layer_num = 1 + + # Support multistage builds + self.recipe = {"spython-base": Recipe(self.filename)} if self.filename: @@ -100,3 +109,52 @@ def _split_line(self, line): """ return [x.strip() for x in line.split(" ", 1)] + + # Multistage + + def _multistage(self, fromHeader): + """Given a from header, determine if we have a multistage build, and + update the recipe parser active in case that we do. If we are dealing + with the first layer and it's named, we also update the default + name "spython-base" to be what the recipe intended. + + Parameters + ========== + fromHeader: the fromHeader parsed from self.from, possibly with AS + """ + # Derive if there is a named layer + match = re.search("AS (?P.+)", fromHeader, flags=re.I) + if match: + layer = match.groups("layer")[0].strip() + + # If it's the first layer named incorrectly, we need to rename + if len(self.recipe) == 1 and list(self.recipe)[0] == "spython-base": + self.recipe[layer] = deepcopy(self.recipe[self.active_layer]) + del self.recipe[self.active_layer] + else: + self.active_layer_num += 1 + self.recipe[layer] = Recipe(self.filename, self.active_layer_num) + self.active_layer = layer + bot.debug( + "Active layer #%s updated to %s" + % (self.active_layer_num, self.active_layer) + ) + + def _replace_from_dict(self, string, args): + """Given a lookup of arguments, args, replace any that are found in + the given string. This is intended to be used to substitute ARGs + provided in a Dockerfile into other sections, e.g., FROM $BASE + + Parameters + ========== + string: an input string to look for replacements + args: a dictionary to make lookups from + + Returns + ======= + string: the string with replacements made + """ + for key, value in args.items(): + if re.search("\$(" + key + "|\{[^}]*\})", string): + string = re.sub("\$(" + key + "|\{[^}]*\})", value, string) + return string diff --git a/spython/main/parse/parsers/docker.py b/spython/main/parse/parsers/docker.py index 0026bee4..38c2f473 100644 --- a/spython/main/parse/parsers/docker.py +++ b/spython/main/parse/parsers/docker.py @@ -80,21 +80,28 @@ def _setup(self, action, line): # From Parser def _from(self, line): - """ get the FROM container image name from a FROM line! - - Parameters - ========== - line: the line from the recipe file to parse for FROM - recipe: the recipe object to populate. + """ get the FROM container image name from a FROM line. If we have + already seen a FROM statement, this is indicative of adding + another image (multistage build). + + Parameters + ========== + line: the line from the recipe file to parse for FROM + recipe: the recipe object to populate. """ fromHeader = self._setup("FROM", line) - # Singularity does not support AS level - self.recipe.fromHeader = re.sub("AS .+", "", fromHeader[0], flags=re.I) + # Do we have a multistge build to update the active layer? + self._multistage(fromHeader[0]) - if "scratch" in self.recipe.fromHeader: + # Now extract the from header, make args replacements + self.recipe[self.active_layer].fromHeader = self._replace_from_dict( + re.sub("AS .+", "", fromHeader[0], flags=re.I), self.args + ) + + if "scratch" in self.recipe[self.active_layer].fromHeader: bot.warning("scratch is no longer available on Docker Hub.") - bot.debug("FROM %s" % self.recipe.fromHeader) + bot.debug("FROM %s" % self.recipe[self.active_layer].fromHeader) # Run and Test Parser @@ -107,7 +114,7 @@ def _run(self, line): """ line = self._setup("RUN", line) - self.recipe.install += line + self.recipe[self.active_layer].install += line def _test(self, line): """ A healthcheck is generally a test command @@ -117,7 +124,7 @@ def _test(self, line): line: the line from the recipe file to parse for FROM """ - self.recipe.test = self._setup("HEALTHCHECK", line) + self.recipe[self.active_layer].test = self._setup("HEALTHCHECK", line) # Arg Parser @@ -132,8 +139,27 @@ def _arg(self, line): """ line = self._setup("ARG", line) - bot.warning("ARG is not supported for Singularity! To get %s" % line[0]) - bot.warning("in the container, on host export SINGULARITY_%s" % line[0]) + + # Args are treated like envars, so we add them to install + environ = self.parse_env([x for x in line if "=" in x]) + self.recipe[self.active_layer].install += environ + + # Try to extract arguments from the line + for arg in line: + + # An undefined arg cannot be used + if "=" not in arg: + bot.warning( + "ARG is not supported for Singularity, and must be defined with " + "a default to be parsed. Skipping %s" % arg + ) + continue + + arg, value = arg.split("=", 1) + arg = arg.strip() + value = value.strip() + bot.debug("Updating ARG %s to %s" % (arg, value)) + self.args[arg] = value # Env Parser @@ -152,10 +178,10 @@ def _env(self, line): environ = self.parse_env(line) # Add to global environment, run during install - self.recipe.install += environ + self.recipe[self.active_layer].install += environ # Also define for global environment - self.recipe.environ += environ + self.recipe[self.active_layer].environ += environ def parse_env(self, envlist): """parse_env will parse a single line (with prefix like ENV removed) to @@ -216,10 +242,25 @@ def _copy(self, lines): lines = self._setup("COPY", lines) for line in lines: + + # Take into account multistage builds + layer = None + if line.startswith("--from"): + layer = line.strip("--from").split(" ")[0].lstrip("=") + if layer not in self.recipe: + bot.warning( + "COPY requested from layer %s, but layer not previously defined." + % layer + ) + continue + + # Remove the --from from the line + line = " ".join([l for l in line.split(" ")[1:] if l]) + values = line.split(" ") topath = values.pop() for frompath in values: - self._add_files(frompath, topath) + self._add_files(frompath, topath, layer) def _add(self, lines): """Add can also handle https, and compressed files. @@ -254,7 +295,7 @@ def _add(self, lines): # File Handling - def _add_files(self, source, dest): + def _add_files(self, source, dest, layer=None): """add files is the underlying function called to add files to the list, whether originally called from the functions to parse archives, or https. We make sure that any local references are changed to @@ -271,11 +312,18 @@ def _add_files(self, source, dest): bot.warning("Singularity doesn't support expansion, * found in %s" % source) # Warning if file/folder (src) doesn't exist - if not os.path.exists(source): + if not os.path.exists(source) and layer is None: bot.warning("%s doesn't exist, ensure exists for build" % source) # The pair is added to the files as a list - self.recipe.files.append([source, dest]) + if not layer: + self.recipe[self.active_layer].files.append([source, dest]) + + # Unless the file is to be copied from a particular layer + else: + if layer not in self.recipe[self.active_layer].layer_files: + self.recipe[self.active_layer].layer_files[layer] = [] + self.recipe[self.active_layer].layer_files[layer].append([source, dest]) def _parse_http(self, url, dest): """will get the filename of an http address, and return a statement @@ -290,7 +338,7 @@ def _parse_http(self, url, dest): file_name = os.path.basename(url) download_path = "%s/%s" % (dest, file_name) command = "curl %s -o %s" % (url, download_path) - self.recipe.install.append(command) + self.recipe[self.active_layer].install.append(command) def _parse_archive(self, targz, dest): """parse_targz will add a line to the install script to extract a @@ -304,7 +352,7 @@ def _parse_archive(self, targz, dest): """ # Add command to extract it - self.recipe.install.append("tar -zvf %s %s" % (targz, dest)) + self.recipe[self.active_layer].install.append("tar -zvf %s %s" % (targz, dest)) # Ensure added to container files return self._add_files(targz, dest) @@ -321,7 +369,7 @@ def _comment(self, line): line: the line from the recipe file to parse to INSTALL """ - self.recipe.install.append(line) + self.recipe[self.active_layer].install.append(line) def _default(self, line): """the default action assumes a line that is either a command (a @@ -333,7 +381,7 @@ def _default(self, line): """ if line.strip().startswith("#"): return self._comment(line) - self.recipe.install.append(line) + self.recipe[self.active_layer].install.append(line) # Ports and Volumes @@ -349,7 +397,7 @@ def _volume(self, line): """ volumes = self._setup("VOLUME", line) if volumes: - self.recipe.volumes += volumes + self.recipe[self.active_layer].volumes += volumes return self._comment("# %s" % line) def _expose(self, line): @@ -362,7 +410,7 @@ def _expose(self, line): """ ports = self._setup("EXPOSE", line) if ports: - self.recipe.ports += ports + self.recipe[self.active_layer].ports += ports return self._comment("# %s" % line) # Working Directory @@ -378,8 +426,8 @@ def _workdir(self, line): # Save the last working directory to add to the runscript workdir = self._setup("WORKDIR", line) workdir_cd = "cd %s" % ("".join(workdir)) - self.recipe.install.append(workdir_cd) - self.recipe.workdir = workdir[0] + self.recipe[self.active_layer].install.append(workdir_cd) + self.recipe[self.active_layer].workdir = workdir[0] # Entrypoint and Command @@ -395,7 +443,7 @@ def _cmd(self, line): """ cmd = self._setup("CMD", line)[0] - self.recipe.cmd = self._load_list(cmd) + self.recipe[self.active_layer].cmd = self._load_list(cmd) def _load_list(self, line): """load an entrypoint or command, meaning it can be wrapped in a list @@ -419,7 +467,7 @@ def _entry(self, line): """ entrypoint = self._setup("ENTRYPOINT", line)[0] - self.recipe.entrypoint = self._load_list(entrypoint) + self.recipe[self.active_layer].entrypoint = self._load_list(entrypoint) # Labels @@ -432,7 +480,7 @@ def _label(self, line): """ label = self._setup("LABEL", line) - self.recipe.labels += [label] + self.recipe[self.active_layer].labels += [label] # Main Parsing Functions diff --git a/spython/main/parse/parsers/singularity.py b/spython/main/parse/parsers/singularity.py index 270e3df8..3ecbfece 100644 --- a/spython/main/parse/parsers/singularity.py +++ b/spython/main/parse/parsers/singularity.py @@ -38,21 +38,7 @@ def parse(self): cd first in a line is parsed as WORKDIR """ - # If the recipe isn't loaded, load it - if not hasattr(self, "config"): - self.load_recipe() - - # Parse each section - for section, lines in self.config.items(): - bot.debug(section) - - # Get the correct parsing function - parser = self._get_mapping(section) - - # Parse it, if appropriate - if parser: - parser(lines) - + self.load_recipe() return self.recipe # Setup for each Parser @@ -91,8 +77,8 @@ def _from(self, line): line: the line from the recipe file to parse for FROM """ - self.recipe.fromHeader = line - bot.debug("FROM %s" % self.recipe.fromHeader) + self.recipe[self.active_layer].fromHeader = line + bot.debug("FROM %s" % self.recipe[self.active_layer].fromHeader) # Run and Test Parser @@ -105,7 +91,7 @@ def _test(self, lines): """ self._write_script("/tests.sh", lines) - self.recipe.test = "/bin/bash /tests.sh" + self.recipe[self.active_layer].test = "/bin/bash /tests.sh" # Env Parser @@ -120,11 +106,11 @@ def _env(self, lines): """ environ = [re.sub("^export", "", x).strip() for x in lines if "=" in x] - self.recipe.environ += environ + self.recipe[self.active_layer].environ += environ # Files for container - def _files(self, lines): + def _files(self, lines, layer=None): """parse_files will simply add the list of files to the correct object Parameters @@ -132,7 +118,12 @@ def _files(self, lines): lines: pairs of files, one pair per line """ - self.recipe.files += lines + if not layer: + self.recipe[self.active_layer].files += lines + else: + if layer not in self.recipe[self.active_layer].layer_files: + self.recipe[self.active_layer].layer_files[layer] = [] + self.recipe[self.active_layer].layer_files[layer] += lines # Comments and Help @@ -147,7 +138,8 @@ def _comments(self, lines): """ for line in lines: comment = self._comment(line) - self.recipe.comments.append(comment) + if comment not in self.recipe[self.active_layer].comments: + self.recipe[self.active_layer].comments.append(comment) def _comment(self, line): """Simply add the line to the install as a comment. Add an extra # to be @@ -184,7 +176,7 @@ def _run(self, lines): self._write_script("/entrypoint.sh", lines) runscript = "/bin/bash /entrypoint.sh" - self.recipe.cmd = runscript + self.recipe[self.active_layer].cmd = runscript # Labels @@ -196,7 +188,7 @@ def _labels(self, lines): lines: the lines from the recipe with key,value pairs """ - self.recipe.labels += lines + self.recipe[self.active_layer].labels += lines def _post(self, lines): """the main core of commands, to be added to the install section @@ -206,7 +198,7 @@ def _post(self, lines): lines: the lines from the recipe with install commands """ - self.recipe.install += lines + self.recipe[self.active_layer].install += lines # Main Parsing Functions @@ -252,17 +244,16 @@ def _load_from(self, line): """ # Remove any comments line = line.split("#", 1)[0] - line = re.sub("(F|f)(R|r)(O|o)(M|m):", "", line).strip() - self.config["from"] = line + line = re.sub("from:", "", line.lower()).strip() + self.recipe[self.active_layer].fromHeader = line - def _load_bootstrap(self, line): - """load bootstrap checks that the bootstrap is Docker, otherwise we - exit on fail (there is no other option to convert to Dockerfile! + def _check_bootstrap(self, line): + """checks that the bootstrap is Docker, otherwise we exit on fail. """ - if "docker" not in line.lower(): - raise NotImplementedError("docker not detected as Bootstrap!") + if not re.search("docker", line, re.IGNORECASE): + raise NotImplementedError("Only docker is supported.") - def _load_section(self, lines, section): + def _load_section(self, lines, section, layer=None): """read in a section to a list, and stop when we hit the next section """ members = [] @@ -273,6 +264,10 @@ def _load_section(self, lines, section): break next_line = lines[0] + # We have a start of another bootstrap + if re.search("bootstrap:", next_line, re.IGNORECASE): + break + # The end of a section if next_line.strip().startswith("%"): break @@ -284,9 +279,19 @@ def _load_section(self, lines, section): members.append(new_member) # Add the list to the config - if members: - if section is not None: - self.config[section] += members + if members and section is not None: + + # Get the correct parsing function + parser = self._get_mapping(section) + + # Parse it, if appropriate + if not parser: + bot.warning("%s is an unrecognized section, skipping." % section) + else: + if section == "files": + parser(members, layer) + else: + parser(members) def load_recipe(self): """load_recipe will return a loaded in singularity recipe. The idea @@ -300,12 +305,8 @@ def load_recipe(self): # Comments between sections, add to top of file lines = self.lines[:] - comments = [] - - # Start with a fresh config! - self.config = dict() - - section = None + fromHeader = None + stage = None while lines: @@ -314,55 +315,58 @@ def load_recipe(self): stripped = line.strip() # Bootstrap Line - if re.search("(b|B)(o|O){2}(t|T)(s|S)(t|T)(r|R)(a|A)(p|P)", line): - self._load_bootstrap(stripped) + if re.search("bootstrap", line, re.IGNORECASE): + self._check_bootstrap(stripped) + section = None + comments = [] # From Line - if re.search("(f|F)(r|R)(O|o)(m|M)", stripped): - self._load_from(stripped) + elif re.search("from:", stripped, re.IGNORECASE): + fromHeader = stripped + if stage is None: + self._load_from(fromHeader) + + # Identify stage + elif re.search("stage:", stripped, re.IGNORECASE): + stage = re.sub("stage:", "", stripped.lower()).strip() + self._multistage("as %s" % stage) + self._load_from(fromHeader) # Comment - if stripped.startswith("#"): + elif stripped.startswith("#") and stripped not in comments: comments.append(stripped) - continue # Section elif stripped.startswith("%"): - section = self._add_section(stripped) - bot.debug("Adding section title %s" % section) + section, layer = self._get_section(stripped) + bot.debug("Found section %s" % section) # If we have a section, and are adding it elif section is not None: lines = [line] + lines - self._load_section(lines=lines, section=section) + self._load_section(lines=lines, section=section, layer=layer) - self.config["comments"] = comments + self._comments(comments) - def _add_section(self, line, section=None): - """parse a line for a section, and return the parsed section (if not - None) + def _get_section(self, line): + """parse a line for a section, and return the name of the section Parameters ========== line: the line to parse - section: the current (or previous) section - - Resulting data structure is: - config['post'] (in lowercase) - """ # Remove any comments line = line.split("#", 1)[0].strip() # Is there a section name? - parts = line.split(" ") + parts = [l.strip() for l in line.split(" ") if l] section = re.sub(r"[%]|(\s+)", "", parts[0]).lower() - if section not in self.config: - self.config[section] = [] - bot.debug("Adding section %s" % section) - - return section + # Is there a named layer? + layer = None + if len(parts) == 3 and parts[1].lower() == "from": + layer = parts[2] + return section, layer def _write_script(self, path, lines, chmod=True): """write a script with some lines content to path in the image. This @@ -376,7 +380,9 @@ def _write_script(self, path, lines, chmod=True): """ for line in lines: - self.recipe.install.append('echo "%s" >> %s' % (line, path)) + self.recipe[self.active_layer].install.append( + 'echo "%s" >> %s' % (line, path) + ) if chmod: - self.recipe.install.append("chmod u+x %s" % path) + self.recipe[self.active_layer].install.append("chmod u+x %s" % path) diff --git a/spython/main/parse/recipe.py b/spython/main/parse/recipe.py index 22d58378..0fcb476d 100644 --- a/spython/main/parse/recipe.py +++ b/spython/main/parse/recipe.py @@ -17,22 +17,26 @@ class Recipe(object): ========== recipe: the original recipe file, parsed by the subclass either DockerParser or SingularityParser + layer: the count of the layer, for human readability """ - def __init__(self, recipe=None): + def __init__(self, recipe=None, layer=1): self.cmd = None self.comments = [] self.entrypoint = None self.environ = [] self.files = [] + self.layer_files = {} self.install = [] self.labels = [] self.ports = [] self.test = None self.volumes = [] self.workdir = None + self.layer = layer + self.fromHeader = None self.source = recipe @@ -54,7 +58,8 @@ def json(self): Returns: a dictionary of attributes including cmd, comments, entrypoint, environ, files, install, labels, ports, - test, volumes, and workdir. + test, volumes, and workdir, organized by layer for + multistage builds. """ attributes = [ "cmd", @@ -62,6 +67,8 @@ def json(self): "entrypoint", "environ", "files", + "fromHeader", + "layer_files", "install", "labels", "ports", diff --git a/spython/main/parse/writers/docker.py b/spython/main/parse/writers/docker.py index ce7701ee..de0429a2 100644 --- a/spython/main/parse/writers/docker.py +++ b/spython/main/parse/writers/docker.py @@ -66,19 +66,22 @@ def validate(self): if self.recipe is None: bot.exit("Please provide a Recipe() to the writer first.") - if self.recipe.fromHeader is None: + def validate_stage(self, parser): + """Given a recipe parser for a stage, ensure that the recipe is valid + """ + if parser.fromHeader is None: bot.exit("Dockerfile requires a fromHeader.") # Parse the provided name uri_regexes = [_reduced_uri, _default_uri, _docker_uri] for r in uri_regexes: - match = r.match(self.recipe.fromHeader) + match = r.match(parser.fromHeader) if match: break if not match: - bot.exit("FROM header %s not valid." % self.recipe.fromHeader) + bot.exit("FROM header %s not valid." % parser.fromHeader) def convert(self, runscript="/bin/bash", force=False): """convert is called by the parent class to convert the recipe object @@ -86,34 +89,41 @@ def convert(self, runscript="/bin/bash", force=False): """ self.validate() - recipe = ["FROM %s" % self.recipe.fromHeader] + recipe = [] + for stage, parser in self.recipe.items(): + self.validate_stage(parser) + + recipe += ["FROM %s AS %s" % (parser.fromHeader, stage)] - # Comments go up front! - recipe += self.recipe.comments + # First add files, labels, environment + recipe += write_files("ADD", parser.files) + recipe += write_lines("LABEL", parser.labels) + recipe += write_lines("ENV", parser.environ) - # First add files, labels, environment - recipe += write_files("ADD", self.recipe.files) - recipe += write_lines("LABEL", self.recipe.labels) - recipe += write_lines("ENV", self.recipe.environ) + # Handle custom files from other layers + for layer, files in parser.layer_files.items(): + for pair in files: + recipe += ["COPY --from=%s %s" % (layer, pair)] - # Install routine is added as RUN commands - recipe += write_lines("RUN", self.recipe.install) + # Install routine is added as RUN commands + # TODO: this needs some work + recipe += write_lines("RUN", parser.install) - # Expose ports - recipe += write_lines("EXPOSE", self.recipe.ports) + # Expose ports + recipe += write_lines("EXPOSE", parser.ports) - if self.recipe.workdir is not None: - recipe.append("WORKDIR %s" % self.recipe.workdir) + if parser.workdir is not None: + recipe.append("WORKDIR %s" % parser.workdir) - # write the command, and entrypoint as is - if self.recipe.cmd is not None: - recipe.append("CMD %s" % self.recipe.cmd) + # write the command, and entrypoint as is + if parser.cmd is not None: + recipe.append("CMD %s" % parser.cmd) - if self.recipe.entrypoint is not None: - recipe.append("ENTRYPOINT %s" % self.recipe.entrypoint) + if parser.entrypoint is not None: + recipe.append("ENTRYPOINT %s" % parser.entrypoint) - if self.recipe.test is not None: - recipe += write_lines("HEALTHCHECK", self.recipe.test) + if parser.test is not None: + recipe += write_lines("HEALTHCHECK", parser.test) # Clean up extra white spaces recipe = "\n".join(recipe).replace("\n\n", "\n") diff --git a/spython/main/parse/writers/singularity.py b/spython/main/parse/writers/singularity.py index d75b6fa9..ae206e75 100644 --- a/spython/main/parse/writers/singularity.py +++ b/spython/main/parse/writers/singularity.py @@ -34,9 +34,6 @@ def validate(self): if self.recipe is None: bot.exit("Please provide a Recipe() to the writer first.") - if self.recipe.fromHeader is None: - bot.exit("Singularity recipe requires a from header.") - def convert(self, runscript="/bin/bash", force=False): """docker2singularity will return a Singularity build recipe based on a the loaded recipe object. It doesn't take any arguments as the @@ -45,31 +42,56 @@ def convert(self, runscript="/bin/bash", force=False): """ self.validate() - recipe = ["Bootstrap: docker"] - recipe += ["From: %s" % self.recipe.fromHeader] + # Write single recipe that includes all layer + recipe = [] + + # Number of layers + num_layers = len(self.recipe) + count = 0 + + # Write each layer to new file + for stage, parser in self.recipe.items(): + + # Set the first and active stage + self.stage = stage + + # From header is required + if parser.fromHeader is None: + bot.exit("Singularity recipe requires a from header.") - # Sections with key value pairs - recipe += self._create_section("files") - recipe += self._create_section("labels") - recipe += self._create_section("install", "post") - recipe += self._create_section("environ", "environment") + recipe += ["\n\n\nBootstrap: docker"] + recipe += ["From: %s" % parser.fromHeader] + recipe += ["Stage: %s\n\n\n" % stage] - # Take preference for user, entrypoint, command, then default - runscript = self._create_runscript(runscript, force) + # TODO: stopped here - bug with files being found + # Add global files, and then layer files + recipe += self._create_section("files") + for layer, files in parser.layer_files.items(): + recipe += create_keyval_section(files, "files", layer) - # If a working directory was used, add it as a cd - if self.recipe.workdir is not None: - runscript = ["cd " + self.recipe.workdir] + [runscript] + # Sections with key value pairs + recipe += self._create_section("labels") + recipe += self._create_section("install", "post") + recipe += self._create_section("environ", "environment") - # Finish the recipe, also add as startscript - recipe += finish_section(runscript, "runscript") - recipe += finish_section(runscript, "startscript") + # If we are at the last layer, write the runscript + if count == num_layers - 1: + runscript = self._create_runscript(runscript, force) - if self.recipe.test is not None: - recipe += finish_section(self.recipe.test, "test") + # If a working directory was used, add it as a cd + if parser.workdir is not None: + runscript = ["cd " + parser.workdir] + [runscript] + + # Finish the recipe, also add as startscript + recipe += finish_section(runscript, "runscript") + recipe += finish_section(runscript, "startscript") + + if parser.test is not None: + recipe += finish_section(parser.test, "test") + count += 1 # Clean up extra white spaces - recipe = "\n".join(recipe).replace("\n\n", "\n") + recipe = "\n".join(recipe).replace("\n\n", "\n").strip("\n") return recipe.rstrip() def _create_runscript(self, default="/bin/bash", force=False): @@ -89,19 +111,21 @@ def _create_runscript(self, default="/bin/bash", force=False): # Only look at Docker if not enforcing default if not force: - if self.recipe.entrypoint is not None: + if self.recipe[self.stage].entrypoint is not None: # The provided entrypoint can be a string or a list - if isinstance(self.recipe.entrypoint, list): - entrypoint = " ".join(self.recipe.entrypoint) + if isinstance(self.recipe[self.stage].entrypoint, list): + entrypoint = " ".join(self.recipe[self.stage].entrypoint) else: - entrypoint = "".join(self.recipe.entrypoint) + entrypoint = "".join(self.recipe[self.stage].entrypoint) - if self.recipe.cmd is not None: - if isinstance(self.recipe.cmd, list): - entrypoint = entrypoint + " " + " ".join(self.recipe.cmd) + if self.recipe[self.stage].cmd is not None: + if isinstance(self.recipe[self.stage].cmd, list): + entrypoint = ( + entrypoint + " " + " ".join(self.recipe[self.stage].cmd) + ) else: - entrypoint = entrypoint + " " + "".join(self.recipe.cmd) + entrypoint = entrypoint + " " + "".join(self.recipe[self.stage].cmd) # Entrypoint should use exec if not entrypoint.startswith("exec"): @@ -112,7 +136,7 @@ def _create_runscript(self, default="/bin/bash", force=False): entrypoint = '%s "$@"' % entrypoint return entrypoint - def _create_section(self, attribute, name=None): + def _create_section(self, attribute, name=None, stage=None): """create a section based on key, value recipe pairs, This is used for files or label @@ -133,7 +157,7 @@ def _create_section(self, attribute, name=None): # Only continue if we have the section and it's not empty try: - section = getattr(self.recipe, attribute) + section = getattr(self.recipe[self.stage], attribute) except AttributeError: bot.debug("Recipe does not have section for %s" % attribute) return section @@ -144,7 +168,7 @@ def _create_section(self, attribute, name=None): # Files if attribute in ["files", "labels"]: - return create_keyval_section(section, name) + return create_keyval_section(section, name, stage) # An environment section needs exports if attribute in ["environ"]: @@ -180,7 +204,7 @@ def finish_section(section, name): return header + lines -def create_keyval_section(pairs, name): +def create_keyval_section(pairs, name, layer): """create a section based on key, value recipe pairs, This is used for files or label @@ -188,9 +212,12 @@ def create_keyval_section(pairs, name): ========== section: the list of values to return as a parsed list of lines name: the name of the section to write (e.g., files) - + layer: if a layer name is provided, name section """ - section = ["%" + name] + if layer: + section = ["%" + name + " from %s" % layer] + else: + section = ["%" + name] for pair in pairs: section.append(" ".join(pair).strip().strip("\\")) return section diff --git a/spython/oci/__init__.py b/spython/oci/__init__.py index b33812c8..b464ddce 100644 --- a/spython/oci/__init__.py +++ b/spython/oci/__init__.py @@ -6,7 +6,6 @@ from spython.image import ImageBase from spython.logger import bot -import os class OciImage(ImageBase): diff --git a/spython/tests/test_client.py b/spython/tests/test_client.py index 844960f1..61d0ee96 100644 --- a/spython/tests/test_client.py +++ b/spython/tests/test_client.py @@ -7,8 +7,11 @@ # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. from spython.main import Client +from spython.utils import write_file import shutil import os +import pytest +from subprocess import CalledProcessError def test_build_from_docker(tmp_path): @@ -50,6 +53,37 @@ def test_execute_with_return_code(docker_container): assert result["return_code"] == 0 +@pytest.mark.parametrize("return_code", [True, False]) +def test_execute_with_called_process_error( + capsys, docker_container, return_code, tmp_path +): + tmp_file = os.path.join(tmp_path, "CalledProcessError.sh") + # "This is stdout" to stdout, "This is stderr" to stderr + script = f"""#!/bin/bash +echo "This is stdout" +>&2 echo "This is stderr" +{"exit 1" if return_code else ""} +""" + write_file(tmp_file, script) + if return_code: + with pytest.raises(CalledProcessError): + for line in Client.execute( + docker_container[1], f"/bin/sh {tmp_file}", stream=True + ): + print(line, "") + else: + for line in Client.execute( + docker_container[1], f"/bin/sh {tmp_file}", stream=True + ): + print(line, "") + captured = capsys.readouterr() + assert "stdout" in captured.out + if return_code: + assert "stderr" in captured.err + else: + assert "stderr" not in captured.err + + def test_inspect(docker_container): result = Client.inspect(docker_container[1]) assert "attributes" in result or "data" in result diff --git a/spython/tests/test_conversion.py b/spython/tests/test_conversion.py index e84cf276..58da0b91 100644 --- a/spython/tests/test_conversion.py +++ b/spython/tests/test_conversion.py @@ -11,8 +11,9 @@ def read_file(file): - with open(file) as f: - return [line.rstrip("\n") for line in f] + with open(file) as fd: + content = fd.read().strip("\n") + return content def test_other_recipe_exists(test_data): @@ -42,8 +43,7 @@ def test_docker2singularity(test_data, tmp_path): for dockerfile, recipe in test_data["d2s"]: parser = DockerParser(dockerfile) writer = SingularityWriter(parser.recipe) - - assert writer.convert().split("\n") == read_file(recipe) + assert writer.convert().strip("\n") == read_file(recipe) def test_singularity2docker(test_data, tmp_path): @@ -55,5 +55,4 @@ def test_singularity2docker(test_data, tmp_path): for recipe, dockerfile in test_data["s2d"]: parser = SingularityParser(recipe) writer = DockerWriter(parser.recipe) - - assert writer.convert().split("\n") == read_file(dockerfile) + assert writer.convert() == read_file(dockerfile) diff --git a/spython/tests/test_parsers.py b/spython/tests/test_parsers.py index e9e5cc2c..15e4c7ce 100644 --- a/spython/tests/test_parsers.py +++ b/spython/tests/test_parsers.py @@ -28,31 +28,35 @@ def test_docker_parser(test_data): parser = DockerParser(dockerfile) assert str(parser) == "[spython-parser][docker]" + assert "spython-base" in parser.recipe + recipe = parser.recipe["spython-base"] # Test all fields from recipe - assert parser.recipe.fromHeader == "python:3.5.1" - assert parser.recipe.cmd == "/code/run_uwsgi.sh" - assert parser.recipe.entrypoint is None - assert parser.recipe.workdir == "/code" - assert parser.recipe.volumes == [] - assert parser.recipe.ports == ["3031"] - assert parser.recipe.files[0] == ["requirements.txt", "/tmp/requirements.txt"] - assert parser.recipe.environ == ["PYTHONUNBUFFERED=1"] - assert parser.recipe.source == dockerfile + assert recipe.fromHeader == "python:3.5.1" + assert recipe.cmd == "/code/run_uwsgi.sh" + assert recipe.entrypoint is None + assert recipe.workdir == "/code" + assert recipe.volumes == [] + assert recipe.ports == ["3031"] + assert recipe.files[0] == ["requirements.txt", "/tmp/requirements.txt"] + assert recipe.environ == ["PYTHONUNBUFFERED=1"] + assert recipe.source == dockerfile def test_singularity_parser(test_data): - recipe = os.path.join(test_data["root"], "Singularity") - parser = SingularityParser(recipe) + recipefile = os.path.join(test_data["root"], "Singularity") + parser = SingularityParser(recipefile) assert str(parser) == "[spython-parser][singularity]" + assert "spython-base" in parser.recipe + recipe = parser.recipe["spython-base"] # Test all fields from recipe - assert parser.recipe.fromHeader == "continuumio/miniconda3" - assert parser.recipe.cmd == 'exec /opt/conda/bin/spython "$@"' - assert parser.recipe.entrypoint is None - assert parser.recipe.workdir is None - assert parser.recipe.volumes == [] - assert parser.recipe.files == [] - assert parser.recipe.environ == [] - assert parser.recipe.source == recipe + assert recipe.fromHeader == "continuumio/miniconda3" + assert recipe.cmd == 'exec /opt/conda/bin/spython "$@"' + assert recipe.entrypoint is None + assert recipe.workdir is None + assert recipe.volumes == [] + assert recipe.files == [] + assert recipe.environ == [] + assert recipe.source == recipefile diff --git a/spython/tests/testdata/docker2singularity/add.def b/spython/tests/testdata/docker2singularity/add.def index 89a52c49..a73c6319 100644 --- a/spython/tests/testdata/docker2singularity/add.def +++ b/spython/tests/testdata/docker2singularity/add.def @@ -1,8 +1,10 @@ Bootstrap: docker From: busybox:latest +Stage: spython-base + %files . /opt %runscript exec /bin/bash "$@" %startscript -exec /bin/bash "$@" \ No newline at end of file +exec /bin/bash "$@" diff --git a/spython/tests/testdata/docker2singularity/cmd.def b/spython/tests/testdata/docker2singularity/cmd.def index d40c42a1..604f3f45 100644 --- a/spython/tests/testdata/docker2singularity/cmd.def +++ b/spython/tests/testdata/docker2singularity/cmd.def @@ -1,6 +1,8 @@ Bootstrap: docker From: busybox:latest +Stage: spython-base + %runscript exec /bin/bash echo hello "$@" %startscript -exec /bin/bash echo hello "$@" \ No newline at end of file +exec /bin/bash echo hello "$@" diff --git a/spython/tests/testdata/docker2singularity/comments.def b/spython/tests/testdata/docker2singularity/comments.def index 6cedd2e1..5f49edef 100644 --- a/spython/tests/testdata/docker2singularity/comments.def +++ b/spython/tests/testdata/docker2singularity/comments.def @@ -1,5 +1,7 @@ Bootstrap: docker From: busybox:latest +Stage: spython-base + %post # This is a really important line @@ -9,4 +11,4 @@ cp /bin/echo /opt/echo %runscript exec /bin/bash "$@" %startscript -exec /bin/bash "$@" \ No newline at end of file +exec /bin/bash "$@" diff --git a/spython/tests/testdata/docker2singularity/copy.def b/spython/tests/testdata/docker2singularity/copy.def index 89a52c49..a73c6319 100644 --- a/spython/tests/testdata/docker2singularity/copy.def +++ b/spython/tests/testdata/docker2singularity/copy.def @@ -1,8 +1,10 @@ Bootstrap: docker From: busybox:latest +Stage: spython-base + %files . /opt %runscript exec /bin/bash "$@" %startscript -exec /bin/bash "$@" \ No newline at end of file +exec /bin/bash "$@" diff --git a/spython/tests/testdata/docker2singularity/entrypoint-cmd.def b/spython/tests/testdata/docker2singularity/entrypoint-cmd.def index d5a1a18d..3029268e 100644 --- a/spython/tests/testdata/docker2singularity/entrypoint-cmd.def +++ b/spython/tests/testdata/docker2singularity/entrypoint-cmd.def @@ -1,5 +1,7 @@ Bootstrap: docker From: busybox:latest +Stage: spython-base + %runscript exec python /code/script.py "$@" %startscript diff --git a/spython/tests/testdata/docker2singularity/entrypoint.def b/spython/tests/testdata/docker2singularity/entrypoint.def index da3fba3a..bfe8965d 100644 --- a/spython/tests/testdata/docker2singularity/entrypoint.def +++ b/spython/tests/testdata/docker2singularity/entrypoint.def @@ -1,6 +1,8 @@ Bootstrap: docker From: busybox:latest +Stage: spython-base + %runscript exec /bin/bash run_uwsgi.sh "$@" %startscript -exec /bin/bash run_uwsgi.sh "$@" \ No newline at end of file +exec /bin/bash run_uwsgi.sh "$@" diff --git a/spython/tests/testdata/docker2singularity/expose.def b/spython/tests/testdata/docker2singularity/expose.def index 74502b27..bed8064b 100644 --- a/spython/tests/testdata/docker2singularity/expose.def +++ b/spython/tests/testdata/docker2singularity/expose.def @@ -1,9 +1,11 @@ Bootstrap: docker From: busybox:latest +Stage: spython-base + %post # EXPOSE 3031 # EXPOSE 9000 %runscript exec /bin/bash "$@" %startscript -exec /bin/bash "$@" \ No newline at end of file +exec /bin/bash "$@" diff --git a/spython/tests/testdata/docker2singularity/from.def b/spython/tests/testdata/docker2singularity/from.def index b71937ec..79940ae1 100644 --- a/spython/tests/testdata/docker2singularity/from.def +++ b/spython/tests/testdata/docker2singularity/from.def @@ -1,6 +1,8 @@ Bootstrap: docker From: busybox:latest +Stage: spython-base + %runscript exec /bin/bash "$@" %startscript -exec /bin/bash "$@" \ No newline at end of file +exec /bin/bash "$@" diff --git a/spython/tests/testdata/docker2singularity/healthcheck.def b/spython/tests/testdata/docker2singularity/healthcheck.def index b2c0bd2a..9891d7f8 100644 --- a/spython/tests/testdata/docker2singularity/healthcheck.def +++ b/spython/tests/testdata/docker2singularity/healthcheck.def @@ -1,8 +1,10 @@ Bootstrap: docker From: busybox:latest +Stage: spython-base + %runscript exec /bin/bash "$@" %startscript exec /bin/bash "$@" %test -true \ No newline at end of file +true diff --git a/spython/tests/testdata/docker2singularity/label.def b/spython/tests/testdata/docker2singularity/label.def index 6c8bb8fd..2728277a 100644 --- a/spython/tests/testdata/docker2singularity/label.def +++ b/spython/tests/testdata/docker2singularity/label.def @@ -1,8 +1,10 @@ Bootstrap: docker From: busybox:latest +Stage: spython-base + %labels maintainer dinosaur %runscript exec /bin/bash "$@" %startscript -exec /bin/bash "$@" \ No newline at end of file +exec /bin/bash "$@" diff --git a/spython/tests/testdata/docker2singularity/multiple-lines.def b/spython/tests/testdata/docker2singularity/multiple-lines.def index 02d6f86c..0af95244 100644 --- a/spython/tests/testdata/docker2singularity/multiple-lines.def +++ b/spython/tests/testdata/docker2singularity/multiple-lines.def @@ -1,5 +1,7 @@ Bootstrap: docker From: busybox:latest +Stage: spython-base + %post apt-get update && \ @@ -10,4 +12,4 @@ squashfs-tools %runscript exec /bin/bash "$@" %startscript -exec /bin/bash "$@" \ No newline at end of file +exec /bin/bash "$@" diff --git a/spython/tests/testdata/docker2singularity/multistage.def b/spython/tests/testdata/docker2singularity/multistage.def new file mode 100644 index 00000000..068ed5f9 --- /dev/null +++ b/spython/tests/testdata/docker2singularity/multistage.def @@ -0,0 +1,20 @@ +Bootstrap: docker +From: golang:1.12.3-alpine3.9 +Stage: devel + +%post +export PATH="/go/bin:/usr/local/go/bin:$PATH" +export HOME="/root" +cd /root +touch hello + +Bootstrap: docker +From: alpine:3.9 +Stage: final + +%files from devel +/root/hello /bin/hello +%runscript +exec /bin/bash "$@" +%startscript +exec /bin/bash "$@" diff --git a/spython/tests/testdata/docker2singularity/multistage.docker b/spython/tests/testdata/docker2singularity/multistage.docker new file mode 100644 index 00000000..18b34ba1 --- /dev/null +++ b/spython/tests/testdata/docker2singularity/multistage.docker @@ -0,0 +1,7 @@ +FROM golang:1.12.3-alpine3.9 AS devel +RUN export PATH="/go/bin:/usr/local/go/bin:$PATH" +RUN export HOME="/root" +RUN cd /root +RUN touch hello +FROM alpine:3.9 AS final +COPY --from=devel /root/hello /bin/hello diff --git a/spython/tests/testdata/docker2singularity/user.def b/spython/tests/testdata/docker2singularity/user.def index c46871bc..b8695de4 100644 --- a/spython/tests/testdata/docker2singularity/user.def +++ b/spython/tests/testdata/docker2singularity/user.def @@ -1,5 +1,7 @@ Bootstrap: docker From: busybox:latest +Stage: spython-base + %post echo "cloud" su - rainman # USER rainman @@ -8,4 +10,4 @@ su - root # USER root %runscript exec /bin/bash "$@" %startscript -exec /bin/bash "$@" \ No newline at end of file +exec /bin/bash "$@" diff --git a/spython/tests/testdata/docker2singularity/workdir.def b/spython/tests/testdata/docker2singularity/workdir.def index 7e1276f8..27b59414 100644 --- a/spython/tests/testdata/docker2singularity/workdir.def +++ b/spython/tests/testdata/docker2singularity/workdir.def @@ -1,5 +1,7 @@ Bootstrap: docker From: busybox:latest +Stage: spython-base + %post cd /code %runscript @@ -7,4 +9,4 @@ cd /code exec /bin/bash "$@" %startscript cd /code -exec /bin/bash "$@" \ No newline at end of file +exec /bin/bash "$@" diff --git a/spython/tests/testdata/singularity2docker/files.docker b/spython/tests/testdata/singularity2docker/files.docker index 920b21f9..425db97c 100644 --- a/spython/tests/testdata/singularity2docker/files.docker +++ b/spython/tests/testdata/singularity2docker/files.docker @@ -1,3 +1,3 @@ -FROM busybox:latest +FROM busybox:latest AS spython-base ADD file.txt /opt/file.txt -ADD /path/to/thing /opt/thing \ No newline at end of file +ADD /path/to/thing /opt/thing diff --git a/spython/tests/testdata/singularity2docker/from.docker b/spython/tests/testdata/singularity2docker/from.docker index f51439e3..8d8f9559 100644 --- a/spython/tests/testdata/singularity2docker/from.docker +++ b/spython/tests/testdata/singularity2docker/from.docker @@ -1 +1 @@ -FROM busybox:latest \ No newline at end of file +FROM busybox:latest AS spython-base diff --git a/spython/tests/testdata/singularity2docker/labels.docker b/spython/tests/testdata/singularity2docker/labels.docker index 3d126e35..158c4957 100644 --- a/spython/tests/testdata/singularity2docker/labels.docker +++ b/spython/tests/testdata/singularity2docker/labels.docker @@ -1,3 +1,3 @@ -FROM busybox:latest +FROM busybox:latest AS spython-base LABEL Maintainer dinosaur -LABEL Version 1.0.0 \ No newline at end of file +LABEL Version 1.0.0 diff --git a/spython/tests/testdata/singularity2docker/multiple-lines.docker b/spython/tests/testdata/singularity2docker/multiple-lines.docker index 75ccaa68..916b9e61 100644 --- a/spython/tests/testdata/singularity2docker/multiple-lines.docker +++ b/spython/tests/testdata/singularity2docker/multiple-lines.docker @@ -1,6 +1,6 @@ -FROM busybox:latest +FROM busybox:latest AS spython-base RUN apt-get update && \ apt-get install -y git \ wget \ curl \ -squashfs-tools \ No newline at end of file +squashfs-tools diff --git a/spython/tests/testdata/singularity2docker/multistage.def b/spython/tests/testdata/singularity2docker/multistage.def new file mode 100644 index 00000000..c85d5111 --- /dev/null +++ b/spython/tests/testdata/singularity2docker/multistage.def @@ -0,0 +1,19 @@ +Bootstrap: docker +From: golang:1.12.3-alpine3.9 +Stage: devel + +%post + # prep environment + export PATH="/go/bin:/usr/local/go/bin:$PATH" + export HOME="/root" + cd /root + touch hello + +# Install binary into final image +Bootstrap: docker +From: alpine:3.9 +Stage: final + +# install binary from stage one +%files from devel + /root/hello /bin/hello diff --git a/spython/tests/testdata/singularity2docker/multistage.docker b/spython/tests/testdata/singularity2docker/multistage.docker new file mode 100644 index 00000000..18b34ba1 --- /dev/null +++ b/spython/tests/testdata/singularity2docker/multistage.docker @@ -0,0 +1,7 @@ +FROM golang:1.12.3-alpine3.9 AS devel +RUN export PATH="/go/bin:/usr/local/go/bin:$PATH" +RUN export HOME="/root" +RUN cd /root +RUN touch hello +FROM alpine:3.9 AS final +COPY --from=devel /root/hello /bin/hello diff --git a/spython/tests/testdata/singularity2docker/post.docker b/spython/tests/testdata/singularity2docker/post.docker index 6872d43e..95ae6453 100644 --- a/spython/tests/testdata/singularity2docker/post.docker +++ b/spython/tests/testdata/singularity2docker/post.docker @@ -1,4 +1,4 @@ -FROM busybox:latest +FROM busybox:latest AS spython-base RUN apt-get update RUN apt-get install -y git \ -wget \ No newline at end of file +wget diff --git a/spython/tests/testdata/singularity2docker/runscript.docker b/spython/tests/testdata/singularity2docker/runscript.docker index 94fd139c..ac85d450 100644 --- a/spython/tests/testdata/singularity2docker/runscript.docker +++ b/spython/tests/testdata/singularity2docker/runscript.docker @@ -1,2 +1,2 @@ -FROM busybox:latest -CMD exec /bin/bash echo hello "$@" \ No newline at end of file +FROM busybox:latest AS spython-base +CMD exec /bin/bash echo hello "$@" diff --git a/spython/tests/testdata/singularity2docker/test.docker b/spython/tests/testdata/singularity2docker/test.docker index cbd12cc9..2e4b6955 100644 --- a/spython/tests/testdata/singularity2docker/test.docker +++ b/spython/tests/testdata/singularity2docker/test.docker @@ -1,4 +1,4 @@ -FROM busybox:latest +FROM busybox:latest AS spython-base RUN echo "true" >> /tests.sh RUN chmod u+x /tests.sh -HEALTHCHECK /bin/bash /tests.sh \ No newline at end of file +HEALTHCHECK /bin/bash /tests.sh diff --git a/spython/utils/terminal.py b/spython/utils/terminal.py index 9331ee87..2856a680 100644 --- a/spython/utils/terminal.py +++ b/spython/utils/terminal.py @@ -134,13 +134,16 @@ def stream_command(cmd, no_newline_regexp="Progess", sudo=False, sudo_options=No """ cmd = _process_sudo_cmd(cmd, sudo, sudo_options) - process = subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True) + process = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True + ) for line in iter(process.stdout.readline, ""): if not re.search(no_newline_regexp, line): yield line process.stdout.close() return_code = process.wait() if return_code: + print(process.stderr.read(), file=sys.stderr) raise subprocess.CalledProcessError(return_code, cmd) diff --git a/spython/version.py b/spython/version.py index 49611ad8..4d9d0dbb 100644 --- a/spython/version.py +++ b/spython/version.py @@ -5,7 +5,7 @@ # with this file, You can obtain one at http://mozilla.org/MPL/2.0/. -__version__ = "0.0.80" +__version__ = "0.0.84" AUTHOR = "Vanessa Sochat" AUTHOR_EMAIL = "vsochat@stanford.edu" NAME = "spython"