=b.SCRIPT.id?r.text():b.DISPLAY:"text"===e&&r.size===b.DISPLAY.size?r=b.TEXT:"script"===e?r=b.SCRIPT:"scriptscript"===e&&(r=b.SCRIPTSCRIPT),r},Qr=function(e,t){var r,n=Jr(e.size,t.style),a=n.fracNum(),i=n.fracDen();r=t.havingStyle(a);var o=bt(e.numer,r,t);if(e.continued){var s=8.5/t.fontMetrics().ptPerEm,l=3.5/t.fontMetrics().ptPerEm;o.height=o.height0?3*c:7*c,d=t.fontMetrics().denom1):(m>0?(u=t.fontMetrics().num2,p=c):(u=t.fontMetrics().num3,p=3*c),d=t.fontMetrics().denom2),h){var w=t.fontMetrics().axisHeight;u-o.depth-(w+.5*m)0&&(t="."===(t=e)?null:t),t};nt({type:"genfrac",names:["\\genfrac"],props:{numArgs:6,allowedInArgument:!0,argTypes:["math","math","size","text","math","math"]},handler:function(e,t){var r,n=e.parser,a=t[4],i=t[5],o=it(t[0]),s="atom"===o.type&&"open"===o.family?rn(o.text):null,l=it(t[1]),h="atom"===l.type&&"close"===l.family?rn(l.text):null,m=Ft(t[2],"size"),c=null;r=!!m.isBlank||(c=m.value).number>0;var u="auto",p=t[3];if("ordgroup"===p.type){if(p.body.length>0){var d=Ft(p.body[0],"textord");u=tn[Number(d.text)]}}else p=Ft(p,"textord"),u=tn[Number(p.text)];return{type:"genfrac",mode:n.mode,numer:a,denom:i,continued:!1,hasBarLine:r,barSize:c,leftDelim:s,rightDelim:h,size:u}},htmlBuilder:Qr,mathmlBuilder:en}),nt({type:"infix",names:["\\above"],props:{numArgs:1,argTypes:["size"],infix:!0},handler:function(e,t){var r=e.parser,n=(e.funcName,e.token);return{type:"infix",mode:r.mode,replaceWith:"\\\\abovefrac",size:Ft(t[0],"size").value,token:n}}}),nt({type:"genfrac",names:["\\\\abovefrac"],props:{numArgs:3,argTypes:["math","size","math"]},handler:function(e,t){var r=e.parser,n=(e.funcName,t[0]),a=function(e){if(!e)throw new Error("Expected non-null, but got "+String(e));return e}(Ft(t[1],"infix").size),i=t[2],o=a.number>0;return{type:"genfrac",mode:r.mode,numer:n,denom:i,continued:!1,hasBarLine:o,barSize:a,leftDelim:null,rightDelim:null,size:"auto"}},htmlBuilder:Qr,mathmlBuilder:en});var nn=function(e,t){var r,n,a=t.style;"supsub"===e.type?(r=e.sup?bt(e.sup,t.havingStyle(a.sup()),t):bt(e.sub,t.havingStyle(a.sub()),t),n=Ft(e.base,"horizBrace")):n=Ft(e,"horizBrace");var i,o=bt(n.base,t.havingBaseStyle(b.DISPLAY)),s=Pt(n,t);if(n.isOver?(i=je.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:o},{type:"kern",size:.1},{type:"elem",elem:s}]},t)).children[0].children[0].children[1].classes.push("svg-align"):(i=je.makeVList({positionType:"bottom",positionData:o.depth+.1+s.height,children:[{type:"elem",elem:s},{type:"kern",size:.1},{type:"elem",elem:o}]},t)).children[0].children[0].children[0].classes.push("svg-align"),r){var l=je.makeSpan(["mord",n.isOver?"mover":"munder"],[i],t);i=n.isOver?je.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:l},{type:"kern",size:.2},{type:"elem",elem:r}]},t):je.makeVList({positionType:"bottom",positionData:l.depth+.2+r.height+r.depth,children:[{type:"elem",elem:r},{type:"kern",size:.2},{type:"elem",elem:l}]},t)}return je.makeSpan(["mord",n.isOver?"mover":"munder"],[i],t)};nt({type:"horizBrace",names:["\\overbrace","\\underbrace"],props:{numArgs:1},handler:function(e,t){var r=e.parser,n=e.funcName;return{type:"horizBrace",mode:r.mode,label:n,isOver:/^\\over/.test(n),base:t[0]}},htmlBuilder:nn,mathmlBuilder:function(e,t){var r=Dt(e.label);return new Mt.MathNode(e.isOver?"mover":"munder",[Nt(e.base,t),r])}}),nt({type:"href",names:["\\href"],props:{numArgs:2,argTypes:["url","original"],allowedInText:!0},handler:function(e,t){var r=e.parser,n=t[1],a=Ft(t[0],"url").url;return r.settings.isTrusted({command:"\\href",url:a})?{type:"href",mode:r.mode,href:a,body:ot(n)}:r.formatUnsupportedCmd("\\href")},htmlBuilder:function(e,t){var r=ut(e.body,t,!1);return je.makeAnchor(e.href,[],r,t)},mathmlBuilder:function(e,t){var r=qt(e.body,t);return r instanceof kt||(r=new kt("mrow",[r])),r.setAttribute("href",e.href),r}}),nt({type:"href",names:["\\url"],props:{numArgs:1,argTypes:["url"],allowedInText:!0},handler:function(e,t){var r=e.parser,n=Ft(t[0],"url").url;if(!r.settings.isTrusted({command:"\\url",url:n}))return r.formatUnsupportedCmd("\\url");for(var a=[],i=0;i0&&(n=Le(e.totalheight,t)-r,n=Number(n.toFixed(2)));var a=0;e.width.number>0&&(a=Le(e.width,t));var i={height:r+n+"em"};a>0&&(i.width=a+"em"),n>0&&(i.verticalAlign=-n+"em");var o=new C(e.src,e.alt,i);return o.height=r,o.depth=n,o},mathmlBuilder:function(e,t){var r=new Mt.MathNode("mglyph",[]);r.setAttribute("alt",e.alt);var n=Le(e.height,t),a=0;if(e.totalheight.number>0&&(a=(a=Le(e.totalheight,t)-n).toFixed(2),r.setAttribute("valign","-"+a+"em")),r.setAttribute("height",n+a+"em"),e.width.number>0){var i=Le(e.width,t);r.setAttribute("width",i+"em")}return r.setAttribute("src",e.src),r}}),nt({type:"kern",names:["\\kern","\\mkern","\\hskip","\\mskip"],props:{numArgs:1,argTypes:["size"],primitive:!0,allowedInText:!0},handler:function(e,t){var r=e.parser,n=e.funcName,a=Ft(t[0],"size");if(r.settings.strict){var i="m"===n[1],o="mu"===a.value.unit;i?(o||r.settings.reportNonstrict("mathVsTextUnits","LaTeX's "+n+" supports only mu units, not "+a.value.unit+" units"),"math"!==r.mode&&r.settings.reportNonstrict("mathVsTextUnits","LaTeX's "+n+" works only in math mode")):o&&r.settings.reportNonstrict("mathVsTextUnits","LaTeX's "+n+" doesn't support mu units")}return{type:"kern",mode:r.mode,dimension:a.value}},htmlBuilder:function(e,t){return je.makeGlue(e.dimension,t)},mathmlBuilder:function(e,t){var r=Le(e.dimension,t);return new Mt.SpaceNode(r)}}),nt({type:"lap",names:["\\mathllap","\\mathrlap","\\mathclap"],props:{numArgs:1,allowedInText:!0},handler:function(e,t){var r=e.parser,n=e.funcName,a=t[0];return{type:"lap",mode:r.mode,alignment:n.slice(5),body:a}},htmlBuilder:function(e,t){var r;"clap"===e.alignment?(r=je.makeSpan([],[bt(e.body,t)]),r=je.makeSpan(["inner"],[r],t)):r=je.makeSpan(["inner"],[bt(e.body,t)]);var n=je.makeSpan(["fix"],[]),a=je.makeSpan([e.alignment],[r,n],t),i=je.makeSpan(["strut"]);return i.style.height=a.height+a.depth+"em",i.style.verticalAlign=-a.depth+"em",a.children.unshift(i),a=je.makeSpan(["thinbox"],[a],t),je.makeSpan(["mord","vbox"],[a],t)},mathmlBuilder:function(e,t){var r=new Mt.MathNode("mpadded",[Nt(e.body,t)]);if("rlap"!==e.alignment){var n="llap"===e.alignment?"-1":"-0.5";r.setAttribute("lspace",n+"width")}return r.setAttribute("width","0px"),r}}),nt({type:"styling",names:["\\(","$"],props:{numArgs:0,allowedInText:!0,allowedInMath:!1},handler:function(e,t){var r=e.funcName,n=e.parser,a=n.mode;n.switchMode("math");var i="\\("===r?"\\)":"$",o=n.parseExpression(!1,i);return n.expect(i),n.switchMode(a),{type:"styling",mode:n.mode,style:"text",body:o}}}),nt({type:"text",names:["\\)","\\]"],props:{numArgs:0,allowedInText:!0,allowedInMath:!1},handler:function(e,t){throw new n("Mismatched "+e.funcName)}});var on=function(e,t){switch(t.style.size){case b.DISPLAY.size:return e.display;case b.TEXT.size:return e.text;case b.SCRIPT.size:return e.script;case b.SCRIPTSCRIPT.size:return e.scriptscript;default:return e.text}};nt({type:"mathchoice",names:["\\mathchoice"],props:{numArgs:4,primitive:!0},handler:function(e,t){return{type:"mathchoice",mode:e.parser.mode,display:ot(t[0]),text:ot(t[1]),script:ot(t[2]),scriptscript:ot(t[3])}},htmlBuilder:function(e,t){var r=on(e,t),n=ut(r,t,!1);return je.makeFragment(n)},mathmlBuilder:function(e,t){var r=on(e,t);return qt(r,t)}});var sn=function(e,t,r,n,a,i,o){var s,l,h;if(e=je.makeSpan([],[e]),t){var m=bt(t,n.havingStyle(a.sup()),n);l={elem:m,kern:Math.max(n.fontMetrics().bigOpSpacing1,n.fontMetrics().bigOpSpacing3-m.depth)}}if(r){var c=bt(r,n.havingStyle(a.sub()),n);s={elem:c,kern:Math.max(n.fontMetrics().bigOpSpacing2,n.fontMetrics().bigOpSpacing4-c.height)}}if(l&&s){var u=n.fontMetrics().bigOpSpacing5+s.elem.height+s.elem.depth+s.kern+e.depth+o;h=je.makeVList({positionType:"bottom",positionData:u,children:[{type:"kern",size:n.fontMetrics().bigOpSpacing5},{type:"elem",elem:s.elem,marginLeft:-i+"em"},{type:"kern",size:s.kern},{type:"elem",elem:e},{type:"kern",size:l.kern},{type:"elem",elem:l.elem,marginLeft:i+"em"},{type:"kern",size:n.fontMetrics().bigOpSpacing5}]},n)}else if(s){var p=e.height-o;h=je.makeVList({positionType:"top",positionData:p,children:[{type:"kern",size:n.fontMetrics().bigOpSpacing5},{type:"elem",elem:s.elem,marginLeft:-i+"em"},{type:"kern",size:s.kern},{type:"elem",elem:e}]},n)}else{if(!l)return e;var d=e.depth+o;h=je.makeVList({positionType:"bottom",positionData:d,children:[{type:"elem",elem:e},{type:"kern",size:l.kern},{type:"elem",elem:l.elem,marginLeft:i+"em"},{type:"kern",size:n.fontMetrics().bigOpSpacing5}]},n)}return je.makeSpan(["mop","op-limits"],[h],n)},ln=["\\smallint"],hn=function(e,t){var r,n,a,i=!1;"supsub"===e.type?(r=e.sup,n=e.sub,a=Ft(e.base,"op"),i=!0):a=Ft(e,"op");var o,s=t.style,h=!1;if(s.size===b.DISPLAY.size&&a.symbol&&!l.contains(ln,a.name)&&(h=!0),a.symbol){var m=h?"Size2-Regular":"Size1-Regular",c="";if("\\oiint"!==a.name&&"\\oiiint"!==a.name||(c=a.name.substr(1),a.name="oiint"===c?"\\iint":"\\iiint"),o=je.makeSymbol(a.name,m,"math",t,["mop","op-symbol",h?"large-op":"small-op"]),c.length>0){var u=o.italic,p=je.staticSvg(c+"Size"+(h?"2":"1"),t);o=je.makeVList({positionType:"individualShift",children:[{type:"elem",elem:o,shift:0},{type:"elem",elem:p,shift:h?.08:0}]},t),a.name="\\"+c,o.classes.unshift("mop"),o.italic=u}}else if(a.body){var d=ut(a.body,t,!0);1===d.length&&d[0]instanceof O?(o=d[0]).classes[0]="mop":o=je.makeSpan(["mop"],d,t)}else{for(var f=[],g=1;g0){for(var s=a.body.map((function(e){var t=e.text;return"string"==typeof t?{type:"textord",mode:e.mode,text:t}:e})),l=ut(s,t.withFont("mathrm"),!0),h=0;h=0?s.setAttribute("height","+"+a+"em"):(s.setAttribute("height",a+"em"),s.setAttribute("depth","+"+-a+"em")),s.setAttribute("voffset",a+"em"),s}});var fn=["\\tiny","\\sixptsize","\\scriptsize","\\footnotesize","\\small","\\normalsize","\\large","\\Large","\\LARGE","\\huge","\\Huge"];nt({type:"sizing",names:fn,props:{numArgs:0,allowedInText:!0},handler:function(e,t){var r=e.breakOnTokenText,n=e.funcName,a=e.parser,i=a.parseExpression(!1,r);return{type:"sizing",mode:a.mode,size:fn.indexOf(n)+1,body:i}},htmlBuilder:function(e,t){var r=t.havingSize(e.size);return dn(e.body,r,t)},mathmlBuilder:function(e,t){var r=t.havingSize(e.size),n=Bt(e.body,r),a=new Mt.MathNode("mstyle",n);return a.setAttribute("mathsize",r.sizeMultiplier+"em"),a}}),nt({type:"smash",names:["\\smash"],props:{numArgs:1,numOptionalArgs:1,allowedInText:!0},handler:function(e,t,r){var n=e.parser,a=!1,i=!1,o=r[0]&&Ft(r[0],"ordgroup");if(o)for(var s="",l=0;lr.height+r.depth+i&&(i=(i+c-r.height-r.depth)/2);var u=l.height-r.height-i-h;r.style.paddingLeft=m+"em";var p=je.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:r,wrapperClasses:["svg-align"]},{type:"kern",size:-(r.height+u)},{type:"elem",elem:l},{type:"kern",size:h}]},t);if(e.index){var d=t.havingStyle(b.SCRIPTSCRIPT),f=bt(e.index,d,t),g=.6*(p.height-p.depth),v=je.makeVList({positionType:"shift",positionData:-g,children:[{type:"elem",elem:f}]},t),y=je.makeSpan(["root"],[v]);return je.makeSpan(["mord","sqrt"],[y,p],t)}return je.makeSpan(["mord","sqrt"],[p],t)},mathmlBuilder:function(e,t){var r=e.body,n=e.index;return n?new Mt.MathNode("mroot",[Nt(r,t),Nt(n,t)]):new Mt.MathNode("msqrt",[Nt(r,t)])}});var gn={display:b.DISPLAY,text:b.TEXT,script:b.SCRIPT,scriptscript:b.SCRIPTSCRIPT};nt({type:"styling",names:["\\displaystyle","\\textstyle","\\scriptstyle","\\scriptscriptstyle"],props:{numArgs:0,allowedInText:!0,primitive:!0},handler:function(e,t){var r=e.breakOnTokenText,n=e.funcName,a=e.parser,i=a.parseExpression(!0,r),o=n.slice(1,n.length-5);return{type:"styling",mode:a.mode,style:o,body:i}},htmlBuilder:function(e,t){var r=gn[e.style],n=t.havingStyle(r).withFont("");return dn(e.body,n,t)},mathmlBuilder:function(e,t){var r=gn[e.style],n=t.havingStyle(r),a=Bt(e.body,n),i=new Mt.MathNode("mstyle",a),o={display:["0","true"],text:["0","false"],script:["1","false"],scriptscript:["2","false"]}[e.style];return i.setAttribute("scriptlevel",o[0]),i.setAttribute("displaystyle",o[1]),i}});var vn=function(e,t){var r=e.base;return r?"op"===r.type?r.limits&&(t.style.size===b.DISPLAY.size||r.alwaysHandleSupSub)?hn:null:"operatorname"===r.type?r.alwaysHandleSupSub&&(t.style.size===b.DISPLAY.size||r.limits)?pn:null:"accent"===r.type?l.isCharacterBox(r.base)?Ut:null:"horizBrace"===r.type&&!e.sub===r.isOver?nn:null:null};at({type:"supsub",htmlBuilder:function(e,t){var r=vn(e,t);if(r)return r(e,t);var n,a,i,o=e.base,s=e.sup,h=e.sub,m=bt(o,t),c=t.fontMetrics(),u=0,p=0,d=o&&l.isCharacterBox(o);if(s){var f=t.havingStyle(t.style.sup());n=bt(s,f,t),d||(u=m.height-f.fontMetrics().supDrop*f.sizeMultiplier/t.sizeMultiplier)}if(h){var g=t.havingStyle(t.style.sub());a=bt(h,g,t),d||(p=m.depth+g.fontMetrics().subDrop*g.sizeMultiplier/t.sizeMultiplier)}i=t.style===b.DISPLAY?c.sup1:t.style.cramped?c.sup3:c.sup2;var v,y=t.sizeMultiplier,x=.5/c.ptPerEm/y+"em",w=null;if(a){var k=e.base&&"op"===e.base.type&&e.base.name&&("\\oiint"===e.base.name||"\\oiiint"===e.base.name);(m instanceof O||k)&&(w=-m.italic+"em")}if(n&&a){u=Math.max(u,i,n.depth+.25*c.xHeight),p=Math.max(p,c.sub2);var S=4*c.defaultRuleThickness;if(u-n.depth-(a.height-p)0&&(u+=M,p-=M)}var z=[{type:"elem",elem:a,shift:p,marginRight:x,marginLeft:w},{type:"elem",elem:n,shift:-u,marginRight:x}];v=je.makeVList({positionType:"individualShift",children:z},t)}else if(a){p=Math.max(p,c.sub1,a.height-.8*c.xHeight);var A=[{type:"elem",elem:a,marginLeft:w,marginRight:x}];v=je.makeVList({positionType:"shift",positionData:p,children:A},t)}else{if(!n)throw new Error("supsub must have either sup or sub.");u=Math.max(u,i,n.depth+.25*c.xHeight),v=je.makeVList({positionType:"shift",positionData:-u,children:[{type:"elem",elem:n,marginRight:x}]},t)}var T=gt(m,"right")||"mord";return je.makeSpan([T],[m,je.makeSpan(["msupsub"],[v])],t)},mathmlBuilder:function(e,t){var r,n=!1;e.base&&"horizBrace"===e.base.type&&!!e.sup===e.base.isOver&&(n=!0,r=e.base.isOver),!e.base||"op"!==e.base.type&&"operatorname"!==e.base.type||(e.base.parentIsSupSub=!0);var a,i=[Nt(e.base,t)];if(e.sub&&i.push(Nt(e.sub,t)),e.sup&&i.push(Nt(e.sup,t)),n)a=r?"mover":"munder";else if(e.sub)if(e.sup){var o=e.base;a=o&&"op"===o.type&&o.limits&&t.style===b.DISPLAY||o&&"operatorname"===o.type&&o.alwaysHandleSupSub&&(t.style===b.DISPLAY||o.limits)?"munderover":"msubsup"}else{var s=e.base;a=s&&"op"===s.type&&s.limits&&(t.style===b.DISPLAY||s.alwaysHandleSupSub)||s&&"operatorname"===s.type&&s.alwaysHandleSupSub&&(s.limits||t.style===b.DISPLAY)?"munder":"msub"}else{var l=e.base;a=l&&"op"===l.type&&l.limits&&(t.style===b.DISPLAY||l.alwaysHandleSupSub)||l&&"operatorname"===l.type&&l.alwaysHandleSupSub&&(l.limits||t.style===b.DISPLAY)?"mover":"msup"}return new Mt.MathNode(a,i)}}),at({type:"atom",htmlBuilder:function(e,t){return je.mathsym(e.text,e.mode,t,["m"+e.family])},mathmlBuilder:function(e,t){var r=new Mt.MathNode("mo",[zt(e.text,e.mode)]);if("bin"===e.family){var n=Tt(e,t);"bold-italic"===n&&r.setAttribute("mathvariant",n)}else"punct"===e.family?r.setAttribute("separator","true"):"open"!==e.family&&"close"!==e.family||r.setAttribute("stretchy","false");return r}});var bn={mi:"italic",mn:"normal",mtext:"normal"};at({type:"mathord",htmlBuilder:function(e,t){return je.makeOrd(e,t,"mathord")},mathmlBuilder:function(e,t){var r=new Mt.MathNode("mi",[zt(e.text,e.mode,t)]),n=Tt(e,t)||"italic";return n!==bn[r.type]&&r.setAttribute("mathvariant",n),r}}),at({type:"textord",htmlBuilder:function(e,t){return je.makeOrd(e,t,"textord")},mathmlBuilder:function(e,t){var r,n=zt(e.text,e.mode,t),a=Tt(e,t)||"normal";return r="text"===e.mode?new Mt.MathNode("mtext",[n]):/[0-9]/.test(e.text)?new Mt.MathNode("mn",[n]):"\\prime"===e.text?new Mt.MathNode("mo",[n]):new Mt.MathNode("mi",[n]),a!==bn[r.type]&&r.setAttribute("mathvariant",a),r}});var yn={"\\nobreak":"nobreak","\\allowbreak":"allowbreak"},xn={" ":{},"\\ ":{},"~":{className:"nobreak"},"\\space":{},"\\nobreakspace":{className:"nobreak"}};at({type:"spacing",htmlBuilder:function(e,t){if(xn.hasOwnProperty(e.text)){var r=xn[e.text].className||"";if("text"===e.mode){var a=je.makeOrd(e,t,"textord");return a.classes.push(r),a}return je.makeSpan(["mspace",r],[je.mathsym(e.text,e.mode,t)],t)}if(yn.hasOwnProperty(e.text))return je.makeSpan(["mspace",yn[e.text]],[],t);throw new n('Unknown type of space "'+e.text+'"')},mathmlBuilder:function(e,t){if(!xn.hasOwnProperty(e.text)){if(yn.hasOwnProperty(e.text))return new Mt.MathNode("mspace");throw new n('Unknown type of space "'+e.text+'"')}return new Mt.MathNode("mtext",[new Mt.TextNode("\xa0")])}});var wn=function(){var e=new Mt.MathNode("mtd",[]);return e.setAttribute("width","50%"),e};at({type:"tag",mathmlBuilder:function(e,t){var r=new Mt.MathNode("mtable",[new Mt.MathNode("mtr",[wn(),new Mt.MathNode("mtd",[qt(e.body,t)]),wn(),new Mt.MathNode("mtd",[qt(e.tag,t)])])]);return r.setAttribute("width","100%"),r}});var kn={"\\text":void 0,"\\textrm":"textrm","\\textsf":"textsf","\\texttt":"texttt","\\textnormal":"textrm"},Sn={"\\textbf":"textbf","\\textmd":"textmd"},Mn={"\\textit":"textit","\\textup":"textup"},zn=function(e,t){var r=e.font;return r?kn[r]?t.withTextFontFamily(kn[r]):Sn[r]?t.withTextFontWeight(Sn[r]):t.withTextFontShape(Mn[r]):t};nt({type:"text",names:["\\text","\\textrm","\\textsf","\\texttt","\\textnormal","\\textbf","\\textmd","\\textit","\\textup"],props:{numArgs:1,argTypes:["text"],allowedInArgument:!0,allowedInText:!0},handler:function(e,t){var r=e.parser,n=e.funcName,a=t[0];return{type:"text",mode:r.mode,body:ot(a),font:n}},htmlBuilder:function(e,t){var r=zn(e,t),n=ut(e.body,r,!0);return je.makeSpan(["mord","text"],n,r)},mathmlBuilder:function(e,t){var r=zn(e,t);return qt(e.body,r)}}),nt({type:"underline",names:["\\underline"],props:{numArgs:1,allowedInText:!0},handler:function(e,t){return{type:"underline",mode:e.parser.mode,body:t[0]}},htmlBuilder:function(e,t){var r=bt(e.body,t),n=je.makeLineSpan("underline-line",t),a=t.fontMetrics().defaultRuleThickness,i=je.makeVList({positionType:"top",positionData:r.height,children:[{type:"kern",size:a},{type:"elem",elem:n},{type:"kern",size:3*a},{type:"elem",elem:r}]},t);return je.makeSpan(["mord","underline"],[i],t)},mathmlBuilder:function(e,t){var r=new Mt.MathNode("mo",[new Mt.TextNode("\u203e")]);r.setAttribute("stretchy","true");var n=new Mt.MathNode("munder",[Nt(e.body,t),r]);return n.setAttribute("accentunder","true"),n}}),nt({type:"vcenter",names:["\\vcenter"],props:{numArgs:1,argTypes:["original"],allowedInText:!1},handler:function(e,t){return{type:"vcenter",mode:e.parser.mode,body:t[0]}},htmlBuilder:function(e,t){var r=bt(e.body,t),n=t.fontMetrics().axisHeight,a=.5*(r.height-n-(r.depth+n));return je.makeVList({positionType:"shift",positionData:a,children:[{type:"elem",elem:r}]},t)},mathmlBuilder:function(e,t){return new Mt.MathNode("mpadded",[Nt(e.body,t)],["vcenter"])}}),nt({type:"verb",names:["\\verb"],props:{numArgs:0,allowedInText:!0},handler:function(e,t,r){throw new n("\\verb ended by end of line instead of matching delimiter")},htmlBuilder:function(e,t){for(var r=An(e),n=[],a=t.havingStyle(t.style.text()),i=0;i0&&(this.undefStack[this.undefStack.length-1][e]=t)}else{var a=this.undefStack[this.undefStack.length-1];a&&!a.hasOwnProperty(e)&&(a[e]=this.current[e])}this.current[e]=t},e}(),Rn={},En=Rn;function Hn(e,t){Rn[e]=t}Hn("\\noexpand",(function(e){var t=e.popToken();return e.isExpandable(t.text)&&(t.noexpand=!0,t.treatAsRelax=!0),{tokens:[t],numArgs:0}})),Hn("\\expandafter",(function(e){var t=e.popToken();return e.expandOnce(!0),{tokens:[t],numArgs:0}})),Hn("\\@firstoftwo",(function(e){return{tokens:e.consumeArgs(2)[0],numArgs:0}})),Hn("\\@secondoftwo",(function(e){return{tokens:e.consumeArgs(2)[1],numArgs:0}})),Hn("\\@ifnextchar",(function(e){var t=e.consumeArgs(3);e.consumeSpaces();var r=e.future();return 1===t[0].length&&t[0][0].text===r.text?{tokens:t[1],numArgs:0}:{tokens:t[2],numArgs:0}})),Hn("\\@ifstar","\\@ifnextchar *{\\@firstoftwo{#1}}"),Hn("\\TextOrMath",(function(e){var t=e.consumeArgs(2);return"text"===e.mode?{tokens:t[0],numArgs:0}:{tokens:t[1],numArgs:0}}));var Ln={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,a:10,A:10,b:11,B:11,c:12,C:12,d:13,D:13,e:14,E:14,f:15,F:15};Hn("\\char",(function(e){var t,r=e.popToken(),a="";if("'"===r.text)t=8,r=e.popToken();else if('"'===r.text)t=16,r=e.popToken();else if("`"===r.text)if("\\"===(r=e.popToken()).text[0])a=r.text.charCodeAt(1);else{if("EOF"===r.text)throw new n("\\char` missing argument");a=r.text.charCodeAt(0)}else t=10;if(t){if(null==(a=Ln[r.text])||a>=t)throw new n("Invalid base-"+t+" digit "+r.text);for(var i;null!=(i=Ln[e.future().text])&&i":"\\dotsb","-":"\\dotsb","*":"\\dotsb",":":"\\dotsb","\\DOTSB":"\\dotsb","\\coprod":"\\dotsb","\\bigvee":"\\dotsb","\\bigwedge":"\\dotsb","\\biguplus":"\\dotsb","\\bigcap":"\\dotsb","\\bigcup":"\\dotsb","\\prod":"\\dotsb","\\sum":"\\dotsb","\\bigotimes":"\\dotsb","\\bigoplus":"\\dotsb","\\bigodot":"\\dotsb","\\bigsqcup":"\\dotsb","\\And":"\\dotsb","\\longrightarrow":"\\dotsb","\\Longrightarrow":"\\dotsb","\\longleftarrow":"\\dotsb","\\Longleftarrow":"\\dotsb","\\longleftrightarrow":"\\dotsb","\\Longleftrightarrow":"\\dotsb","\\mapsto":"\\dotsb","\\longmapsto":"\\dotsb","\\hookrightarrow":"\\dotsb","\\doteq":"\\dotsb","\\mathbin":"\\dotsb","\\mathrel":"\\dotsb","\\relbar":"\\dotsb","\\Relbar":"\\dotsb","\\xrightarrow":"\\dotsb","\\xleftarrow":"\\dotsb","\\DOTSI":"\\dotsi","\\int":"\\dotsi","\\oint":"\\dotsi","\\iint":"\\dotsi","\\iiint":"\\dotsi","\\iiiint":"\\dotsi","\\idotsint":"\\dotsi","\\DOTSX":"\\dotsx"};Hn("\\dots",(function(e){var t="\\dotso",r=e.expandAfterFuture().text;return r in Pn?t=Pn[r]:("\\not"===r.substr(0,4)||r in X.math&&l.contains(["bin","rel"],X.math[r].group))&&(t="\\dotsb"),t}));var Fn={")":!0,"]":!0,"\\rbrack":!0,"\\}":!0,"\\rbrace":!0,"\\rangle":!0,"\\rceil":!0,"\\rfloor":!0,"\\rgroup":!0,"\\rmoustache":!0,"\\right":!0,"\\bigr":!0,"\\biggr":!0,"\\Bigr":!0,"\\Biggr":!0,$:!0,";":!0,".":!0,",":!0};Hn("\\dotso",(function(e){return e.future().text in Fn?"\\ldots\\,":"\\ldots"})),Hn("\\dotsc",(function(e){var t=e.future().text;return t in Fn&&","!==t?"\\ldots\\,":"\\ldots"})),Hn("\\cdots",(function(e){return e.future().text in Fn?"\\@cdots\\,":"\\@cdots"})),Hn("\\dotsb","\\cdots"),Hn("\\dotsm","\\cdots"),Hn("\\dotsi","\\!\\cdots"),Hn("\\dotsx","\\ldots\\,"),Hn("\\DOTSI","\\relax"),Hn("\\DOTSB","\\relax"),Hn("\\DOTSX","\\relax"),Hn("\\tmspace","\\TextOrMath{\\kern#1#3}{\\mskip#1#2}\\relax"),Hn("\\,","\\tmspace+{3mu}{.1667em}"),Hn("\\thinspace","\\,"),Hn("\\>","\\mskip{4mu}"),Hn("\\:","\\tmspace+{4mu}{.2222em}"),Hn("\\medspace","\\:"),Hn("\\;","\\tmspace+{5mu}{.2777em}"),Hn("\\thickspace","\\;"),Hn("\\!","\\tmspace-{3mu}{.1667em}"),Hn("\\negthinspace","\\!"),Hn("\\negmedspace","\\tmspace-{4mu}{.2222em}"),Hn("\\negthickspace","\\tmspace-{5mu}{.277em}"),Hn("\\enspace","\\kern.5em "),Hn("\\enskip","\\hskip.5em\\relax"),Hn("\\quad","\\hskip1em\\relax"),Hn("\\qquad","\\hskip2em\\relax"),Hn("\\tag","\\@ifstar\\tag@literal\\tag@paren"),Hn("\\tag@paren","\\tag@literal{({#1})}"),Hn("\\tag@literal",(function(e){if(e.macros.get("\\df@tag"))throw new n("Multiple \\tag");return"\\gdef\\df@tag{\\text{#1}}"})),Hn("\\bmod","\\mathchoice{\\mskip1mu}{\\mskip1mu}{\\mskip5mu}{\\mskip5mu}\\mathbin{\\rm mod}\\mathchoice{\\mskip1mu}{\\mskip1mu}{\\mskip5mu}{\\mskip5mu}"),Hn("\\pod","\\allowbreak\\mathchoice{\\mkern18mu}{\\mkern8mu}{\\mkern8mu}{\\mkern8mu}(#1)"),Hn("\\pmod","\\pod{{\\rm mod}\\mkern6mu#1}"),Hn("\\mod","\\allowbreak\\mathchoice{\\mkern18mu}{\\mkern12mu}{\\mkern12mu}{\\mkern12mu}{\\rm mod}\\,\\,#1"),Hn("\\pmb","\\html@mathml{\\@binrel{#1}{\\mathrlap{#1}\\kern0.5px#1}}{\\mathbf{#1}}"),Hn("\\newline","\\\\\\relax"),Hn("\\TeX","\\textrm{\\html@mathml{T\\kern-.1667em\\raisebox{-.5ex}{E}\\kern-.125emX}{TeX}}");var Vn=D["Main-Regular"]["T".charCodeAt(0)][1]-.7*D["Main-Regular"]["A".charCodeAt(0)][1]+"em";Hn("\\LaTeX","\\textrm{\\html@mathml{L\\kern-.36em\\raisebox{"+Vn+"}{\\scriptstyle A}\\kern-.15em\\TeX}{LaTeX}}"),Hn("\\KaTeX","\\textrm{\\html@mathml{K\\kern-.17em\\raisebox{"+Vn+"}{\\scriptstyle A}\\kern-.15em\\TeX}{KaTeX}}"),Hn("\\hspace","\\@ifstar\\@hspacer\\@hspace"),Hn("\\@hspace","\\hskip #1\\relax"),Hn("\\@hspacer","\\rule{0pt}{0pt}\\hskip #1\\relax"),Hn("\\ordinarycolon",":"),Hn("\\vcentcolon","\\mathrel{\\mathop\\ordinarycolon}"),Hn("\\dblcolon",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-.9mu}\\vcentcolon}}{\\mathop{\\char"2237}}'),Hn("\\coloneqq",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}=}}{\\mathop{\\char"2254}}'),Hn("\\Coloneqq",'\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}=}}{\\mathop{\\char"2237\\char"3d}}'),Hn("\\coloneq",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}\\mathrel{-}}}{\\mathop{\\char"3a\\char"2212}}'),Hn("\\Coloneq",'\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}\\mathrel{-}}}{\\mathop{\\char"2237\\char"2212}}'),Hn("\\eqqcolon",'\\html@mathml{\\mathrel{=\\mathrel{\\mkern-1.2mu}\\vcentcolon}}{\\mathop{\\char"2255}}'),Hn("\\Eqqcolon",'\\html@mathml{\\mathrel{=\\mathrel{\\mkern-1.2mu}\\dblcolon}}{\\mathop{\\char"3d\\char"2237}}'),Hn("\\eqcolon",'\\html@mathml{\\mathrel{\\mathrel{-}\\mathrel{\\mkern-1.2mu}\\vcentcolon}}{\\mathop{\\char"2239}}'),Hn("\\Eqcolon",'\\html@mathml{\\mathrel{\\mathrel{-}\\mathrel{\\mkern-1.2mu}\\dblcolon}}{\\mathop{\\char"2212\\char"2237}}'),Hn("\\colonapprox",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}\\approx}}{\\mathop{\\char"3a\\char"2248}}'),Hn("\\Colonapprox",'\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}\\approx}}{\\mathop{\\char"2237\\char"2248}}'),Hn("\\colonsim",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}\\sim}}{\\mathop{\\char"3a\\char"223c}}'),Hn("\\Colonsim",'\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}\\sim}}{\\mathop{\\char"2237\\char"223c}}'),Hn("\u2237","\\dblcolon"),Hn("\u2239","\\eqcolon"),Hn("\u2254","\\coloneqq"),Hn("\u2255","\\eqqcolon"),Hn("\u2a74","\\Coloneqq"),Hn("\\ratio","\\vcentcolon"),Hn("\\coloncolon","\\dblcolon"),Hn("\\colonequals","\\coloneqq"),Hn("\\coloncolonequals","\\Coloneqq"),Hn("\\equalscolon","\\eqqcolon"),Hn("\\equalscoloncolon","\\Eqqcolon"),Hn("\\colonminus","\\coloneq"),Hn("\\coloncolonminus","\\Coloneq"),Hn("\\minuscolon","\\eqcolon"),Hn("\\minuscoloncolon","\\Eqcolon"),Hn("\\coloncolonapprox","\\Colonapprox"),Hn("\\coloncolonsim","\\Colonsim"),Hn("\\simcolon","\\mathrel{\\sim\\mathrel{\\mkern-1.2mu}\\vcentcolon}"),Hn("\\simcoloncolon","\\mathrel{\\sim\\mathrel{\\mkern-1.2mu}\\dblcolon}"),Hn("\\approxcolon","\\mathrel{\\approx\\mathrel{\\mkern-1.2mu}\\vcentcolon}"),Hn("\\approxcoloncolon","\\mathrel{\\approx\\mathrel{\\mkern-1.2mu}\\dblcolon}"),Hn("\\notni","\\html@mathml{\\not\\ni}{\\mathrel{\\char`\u220c}}"),Hn("\\limsup","\\DOTSB\\operatorname*{lim\\,sup}"),Hn("\\liminf","\\DOTSB\\operatorname*{lim\\,inf}"),Hn("\\injlim","\\DOTSB\\operatorname*{inj\\,lim}"),Hn("\\projlim","\\DOTSB\\operatorname*{proj\\,lim}"),Hn("\\varlimsup","\\DOTSB\\operatorname*{\\overline{lim}}"),Hn("\\varliminf","\\DOTSB\\operatorname*{\\underline{lim}}"),Hn("\\varinjlim","\\DOTSB\\operatorname*{\\underrightarrow{lim}}"),Hn("\\varprojlim","\\DOTSB\\operatorname*{\\underleftarrow{lim}}"),Hn("\\gvertneqq","\\html@mathml{\\@gvertneqq}{\u2269}"),Hn("\\lvertneqq","\\html@mathml{\\@lvertneqq}{\u2268}"),Hn("\\ngeqq","\\html@mathml{\\@ngeqq}{\u2271}"),Hn("\\ngeqslant","\\html@mathml{\\@ngeqslant}{\u2271}"),Hn("\\nleqq","\\html@mathml{\\@nleqq}{\u2270}"),Hn("\\nleqslant","\\html@mathml{\\@nleqslant}{\u2270}"),Hn("\\nshortmid","\\html@mathml{\\@nshortmid}{\u2224}"),Hn("\\nshortparallel","\\html@mathml{\\@nshortparallel}{\u2226}"),Hn("\\nsubseteqq","\\html@mathml{\\@nsubseteqq}{\u2288}"),Hn("\\nsupseteqq","\\html@mathml{\\@nsupseteqq}{\u2289}"),Hn("\\varsubsetneq","\\html@mathml{\\@varsubsetneq}{\u228a}"),Hn("\\varsubsetneqq","\\html@mathml{\\@varsubsetneqq}{\u2acb}"),Hn("\\varsupsetneq","\\html@mathml{\\@varsupsetneq}{\u228b}"),Hn("\\varsupsetneqq","\\html@mathml{\\@varsupsetneqq}{\u2acc}"),Hn("\\imath","\\html@mathml{\\@imath}{\u0131}"),Hn("\\jmath","\\html@mathml{\\@jmath}{\u0237}"),Hn("\\llbracket","\\html@mathml{\\mathopen{[\\mkern-3.2mu[}}{\\mathopen{\\char`\u27e6}}"),Hn("\\rrbracket","\\html@mathml{\\mathclose{]\\mkern-3.2mu]}}{\\mathclose{\\char`\u27e7}}"),Hn("\u27e6","\\llbracket"),Hn("\u27e7","\\rrbracket"),Hn("\\lBrace","\\html@mathml{\\mathopen{\\{\\mkern-3.2mu[}}{\\mathopen{\\char`\u2983}}"),Hn("\\rBrace","\\html@mathml{\\mathclose{]\\mkern-3.2mu\\}}}{\\mathclose{\\char`\u2984}}"),Hn("\u2983","\\lBrace"),Hn("\u2984","\\rBrace"),Hn("\\minuso","\\mathbin{\\html@mathml{{\\mathrlap{\\mathchoice{\\kern{0.145em}}{\\kern{0.145em}}{\\kern{0.1015em}}{\\kern{0.0725em}}\\circ}{-}}}{\\char`\u29b5}}"),Hn("\u29b5","\\minuso"),Hn("\\darr","\\downarrow"),Hn("\\dArr","\\Downarrow"),Hn("\\Darr","\\Downarrow"),Hn("\\lang","\\langle"),Hn("\\rang","\\rangle"),Hn("\\uarr","\\uparrow"),Hn("\\uArr","\\Uparrow"),Hn("\\Uarr","\\Uparrow"),Hn("\\N","\\mathbb{N}"),Hn("\\R","\\mathbb{R}"),Hn("\\Z","\\mathbb{Z}"),Hn("\\alef","\\aleph"),Hn("\\alefsym","\\aleph"),Hn("\\Alpha","\\mathrm{A}"),Hn("\\Beta","\\mathrm{B}"),Hn("\\bull","\\bullet"),Hn("\\Chi","\\mathrm{X}"),Hn("\\clubs","\\clubsuit"),Hn("\\cnums","\\mathbb{C}"),Hn("\\Complex","\\mathbb{C}"),Hn("\\Dagger","\\ddagger"),Hn("\\diamonds","\\diamondsuit"),Hn("\\empty","\\emptyset"),Hn("\\Epsilon","\\mathrm{E}"),Hn("\\Eta","\\mathrm{H}"),Hn("\\exist","\\exists"),Hn("\\harr","\\leftrightarrow"),Hn("\\hArr","\\Leftrightarrow"),Hn("\\Harr","\\Leftrightarrow"),Hn("\\hearts","\\heartsuit"),Hn("\\image","\\Im"),Hn("\\infin","\\infty"),Hn("\\Iota","\\mathrm{I}"),Hn("\\isin","\\in"),Hn("\\Kappa","\\mathrm{K}"),Hn("\\larr","\\leftarrow"),Hn("\\lArr","\\Leftarrow"),Hn("\\Larr","\\Leftarrow"),Hn("\\lrarr","\\leftrightarrow"),Hn("\\lrArr","\\Leftrightarrow"),Hn("\\Lrarr","\\Leftrightarrow"),Hn("\\Mu","\\mathrm{M}"),Hn("\\natnums","\\mathbb{N}"),Hn("\\Nu","\\mathrm{N}"),Hn("\\Omicron","\\mathrm{O}"),Hn("\\plusmn","\\pm"),Hn("\\rarr","\\rightarrow"),Hn("\\rArr","\\Rightarrow"),Hn("\\Rarr","\\Rightarrow"),Hn("\\real","\\Re"),Hn("\\reals","\\mathbb{R}"),Hn("\\Reals","\\mathbb{R}"),Hn("\\Rho","\\mathrm{P}"),Hn("\\sdot","\\cdot"),Hn("\\sect","\\S"),Hn("\\spades","\\spadesuit"),Hn("\\sub","\\subset"),Hn("\\sube","\\subseteq"),Hn("\\supe","\\supseteq"),Hn("\\Tau","\\mathrm{T}"),Hn("\\thetasym","\\vartheta"),Hn("\\weierp","\\wp"),Hn("\\Zeta","\\mathrm{Z}"),Hn("\\argmin","\\DOTSB\\operatorname*{arg\\,min}"),Hn("\\argmax","\\DOTSB\\operatorname*{arg\\,max}"),Hn("\\plim","\\DOTSB\\mathop{\\operatorname{plim}}\\limits"),Hn("\\bra","\\mathinner{\\langle{#1}|}"),Hn("\\ket","\\mathinner{|{#1}\\rangle}"),Hn("\\braket","\\mathinner{\\langle{#1}\\rangle}"),Hn("\\Bra","\\left\\langle#1\\right|"),Hn("\\Ket","\\left|#1\\right\\rangle"),Hn("\\angln","{\\angl n}"),Hn("\\blue","\\textcolor{##6495ed}{#1}"),Hn("\\orange","\\textcolor{##ffa500}{#1}"),Hn("\\pink","\\textcolor{##ff00af}{#1}"),Hn("\\red","\\textcolor{##df0030}{#1}"),Hn("\\green","\\textcolor{##28ae7b}{#1}"),Hn("\\gray","\\textcolor{gray}{#1}"),Hn("\\purple","\\textcolor{##9d38bd}{#1}"),Hn("\\blueA","\\textcolor{##ccfaff}{#1}"),Hn("\\blueB","\\textcolor{##80f6ff}{#1}"),Hn("\\blueC","\\textcolor{##63d9ea}{#1}"),Hn("\\blueD","\\textcolor{##11accd}{#1}"),Hn("\\blueE","\\textcolor{##0c7f99}{#1}"),Hn("\\tealA","\\textcolor{##94fff5}{#1}"),Hn("\\tealB","\\textcolor{##26edd5}{#1}"),Hn("\\tealC","\\textcolor{##01d1c1}{#1}"),Hn("\\tealD","\\textcolor{##01a995}{#1}"),Hn("\\tealE","\\textcolor{##208170}{#1}"),Hn("\\greenA","\\textcolor{##b6ffb0}{#1}"),Hn("\\greenB","\\textcolor{##8af281}{#1}"),Hn("\\greenC","\\textcolor{##74cf70}{#1}"),Hn("\\greenD","\\textcolor{##1fab54}{#1}"),Hn("\\greenE","\\textcolor{##0d923f}{#1}"),Hn("\\goldA","\\textcolor{##ffd0a9}{#1}"),Hn("\\goldB","\\textcolor{##ffbb71}{#1}"),Hn("\\goldC","\\textcolor{##ff9c39}{#1}"),Hn("\\goldD","\\textcolor{##e07d10}{#1}"),Hn("\\goldE","\\textcolor{##a75a05}{#1}"),Hn("\\redA","\\textcolor{##fca9a9}{#1}"),Hn("\\redB","\\textcolor{##ff8482}{#1}"),Hn("\\redC","\\textcolor{##f9685d}{#1}"),Hn("\\redD","\\textcolor{##e84d39}{#1}"),Hn("\\redE","\\textcolor{##bc2612}{#1}"),Hn("\\maroonA","\\textcolor{##ffbde0}{#1}"),Hn("\\maroonB","\\textcolor{##ff92c6}{#1}"),Hn("\\maroonC","\\textcolor{##ed5fa6}{#1}"),Hn("\\maroonD","\\textcolor{##ca337c}{#1}"),Hn("\\maroonE","\\textcolor{##9e034e}{#1}"),Hn("\\purpleA","\\textcolor{##ddd7ff}{#1}"),Hn("\\purpleB","\\textcolor{##c6b9fc}{#1}"),Hn("\\purpleC","\\textcolor{##aa87ff}{#1}"),Hn("\\purpleD","\\textcolor{##7854ab}{#1}"),Hn("\\purpleE","\\textcolor{##543b78}{#1}"),Hn("\\mintA","\\textcolor{##f5f9e8}{#1}"),Hn("\\mintB","\\textcolor{##edf2df}{#1}"),Hn("\\mintC","\\textcolor{##e0e5cc}{#1}"),Hn("\\grayA","\\textcolor{##f6f7f7}{#1}"),Hn("\\grayB","\\textcolor{##f0f1f2}{#1}"),Hn("\\grayC","\\textcolor{##e3e5e6}{#1}"),Hn("\\grayD","\\textcolor{##d6d8da}{#1}"),Hn("\\grayE","\\textcolor{##babec2}{#1}"),Hn("\\grayF","\\textcolor{##888d93}{#1}"),Hn("\\grayG","\\textcolor{##626569}{#1}"),Hn("\\grayH","\\textcolor{##3b3e40}{#1}"),Hn("\\grayI","\\textcolor{##21242c}{#1}"),Hn("\\kaBlue","\\textcolor{##314453}{#1}"),Hn("\\kaGreen","\\textcolor{##71B307}{#1}");var Gn={"\\relax":!0,"^":!0,_:!0,"\\limits":!0,"\\nolimits":!0},Un=function(){function e(e,t,r){this.settings=void 0,this.expansionCount=void 0,this.lexer=void 0,this.macros=void 0,this.stack=void 0,this.mode=void 0,this.settings=t,this.expansionCount=0,this.feed(e),this.macros=new On(En,t.macros),this.mode=r,this.stack=[]}var t=e.prototype;return t.feed=function(e){this.lexer=new In(e,this.settings)},t.switchMode=function(e){this.mode=e},t.beginGroup=function(){this.macros.beginGroup()},t.endGroup=function(){this.macros.endGroup()},t.future=function(){return 0===this.stack.length&&this.pushToken(this.lexer.lex()),this.stack[this.stack.length-1]},t.popToken=function(){return this.future(),this.stack.pop()},t.pushToken=function(e){this.stack.push(e)},t.pushTokens=function(e){var t;(t=this.stack).push.apply(t,e)},t.scanArgument=function(e){var t,r,n;if(e){if(this.consumeSpaces(),"["!==this.future().text)return null;t=this.popToken();var a=this.consumeArg(["]"]);n=a.tokens,r=a.end}else{var i=this.consumeArg();n=i.tokens,t=i.start,r=i.end}return this.pushToken(new qn("EOF",r.loc)),this.pushTokens(n),t.range(r,"")},t.consumeSpaces=function(){for(;;){if(" "!==this.future().text)break;this.stack.pop()}},t.consumeArg=function(e){var t=[],r=e&&e.length>0;r||this.consumeSpaces();var a,i=this.future(),o=0,s=0;do{if(a=this.popToken(),t.push(a),"{"===a.text)++o;else if("}"===a.text){if(-1===--o)throw new n("Extra }",a)}else if("EOF"===a.text)throw new n("Unexpected end of input in a macro argument, expected '"+(e&&r?e[s]:"}")+"'",a);if(e&&r)if((0===o||1===o&&"{"===e[s])&&a.text===e[s]){if(++s===e.length){t.splice(-s,s);break}}else s=0}while(0!==o||r);return"{"===i.text&&"}"===t[t.length-1].text&&(t.pop(),t.shift()),t.reverse(),{tokens:t,start:i,end:a}},t.consumeArgs=function(e,t){if(t){if(t.length!==e+1)throw new n("The length of delimiters doesn't match the number of args!");for(var r=t[0],a=0;athis.settings.maxExpand)throw new n("Too many expansions: infinite loop or need to increase maxExpand setting");var i=a.tokens,o=this.consumeArgs(a.numArgs,a.delimiters);if(a.numArgs)for(var s=(i=i.slice()).length-1;s>=0;--s){var l=i[s];if("#"===l.text){if(0===s)throw new n("Incomplete placeholder at end of macro body",l);if("#"===(l=i[--s]).text)i.splice(s+1,1);else{if(!/^[1-9]$/.test(l.text))throw new n("Not a valid argument number",l);var h;(h=i).splice.apply(h,[s,2].concat(o[+l.text-1]))}}}return this.pushTokens(i),i},t.expandAfterFuture=function(){return this.expandOnce(),this.future()},t.expandNextToken=function(){for(;;){var e=this.expandOnce();if(e instanceof qn){if("\\relax"!==e.text&&!e.treatAsRelax)return this.stack.pop();this.stack.pop()}}throw new Error},t.expandMacro=function(e){return this.macros.has(e)?this.expandTokens([new qn(e)]):void 0},t.expandTokens=function(e){var t=[],r=this.stack.length;for(this.pushTokens(e);this.stack.length>r;){var n=this.expandOnce(!0);n instanceof qn&&(n.treatAsRelax&&(n.noexpand=!1,n.treatAsRelax=!1),t.push(this.stack.pop()))}return t},t.expandMacroAsText=function(e){var t=this.expandMacro(e);return t?t.map((function(e){return e.text})).join(""):t},t._getExpansion=function(e){var t=this.macros.get(e);if(null==t)return t;var r="function"==typeof t?t(this):t;if("string"==typeof r){var n=0;if(-1!==r.indexOf("#"))for(var a=r.replace(/##/g,"");-1!==a.indexOf("#"+(n+1));)++n;for(var i=new In(r,this.settings),o=[],s=i.lex();"EOF"!==s.text;)o.push(s),s=i.lex();return o.reverse(),{tokens:o,numArgs:n}}return r},t.isDefined=function(e){return this.macros.has(e)||Tn.hasOwnProperty(e)||X.math.hasOwnProperty(e)||X.text.hasOwnProperty(e)||Gn.hasOwnProperty(e)},t.isExpandable=function(e){var t=this.macros.get(e);return null!=t?"string"==typeof t||"function"==typeof t||!t.unexpandable:Tn.hasOwnProperty(e)&&!Tn[e].primitive},e}(),Yn={"\u0301":{text:"\\'",math:"\\acute"},"\u0300":{text:"\\`",math:"\\grave"},"\u0308":{text:'\\"',math:"\\ddot"},"\u0303":{text:"\\~",math:"\\tilde"},"\u0304":{text:"\\=",math:"\\bar"},"\u0306":{text:"\\u",math:"\\breve"},"\u030c":{text:"\\v",math:"\\check"},"\u0302":{text:"\\^",math:"\\hat"},"\u0307":{text:"\\.",math:"\\dot"},"\u030a":{text:"\\r",math:"\\mathring"},"\u030b":{text:"\\H"}},Wn={"\xe1":"a\u0301","\xe0":"a\u0300","\xe4":"a\u0308","\u01df":"a\u0308\u0304","\xe3":"a\u0303","\u0101":"a\u0304","\u0103":"a\u0306","\u1eaf":"a\u0306\u0301","\u1eb1":"a\u0306\u0300","\u1eb5":"a\u0306\u0303","\u01ce":"a\u030c","\xe2":"a\u0302","\u1ea5":"a\u0302\u0301","\u1ea7":"a\u0302\u0300","\u1eab":"a\u0302\u0303","\u0227":"a\u0307","\u01e1":"a\u0307\u0304","\xe5":"a\u030a","\u01fb":"a\u030a\u0301","\u1e03":"b\u0307","\u0107":"c\u0301","\u010d":"c\u030c","\u0109":"c\u0302","\u010b":"c\u0307","\u010f":"d\u030c","\u1e0b":"d\u0307","\xe9":"e\u0301","\xe8":"e\u0300","\xeb":"e\u0308","\u1ebd":"e\u0303","\u0113":"e\u0304","\u1e17":"e\u0304\u0301","\u1e15":"e\u0304\u0300","\u0115":"e\u0306","\u011b":"e\u030c","\xea":"e\u0302","\u1ebf":"e\u0302\u0301","\u1ec1":"e\u0302\u0300","\u1ec5":"e\u0302\u0303","\u0117":"e\u0307","\u1e1f":"f\u0307","\u01f5":"g\u0301","\u1e21":"g\u0304","\u011f":"g\u0306","\u01e7":"g\u030c","\u011d":"g\u0302","\u0121":"g\u0307","\u1e27":"h\u0308","\u021f":"h\u030c","\u0125":"h\u0302","\u1e23":"h\u0307","\xed":"i\u0301","\xec":"i\u0300","\xef":"i\u0308","\u1e2f":"i\u0308\u0301","\u0129":"i\u0303","\u012b":"i\u0304","\u012d":"i\u0306","\u01d0":"i\u030c","\xee":"i\u0302","\u01f0":"j\u030c","\u0135":"j\u0302","\u1e31":"k\u0301","\u01e9":"k\u030c","\u013a":"l\u0301","\u013e":"l\u030c","\u1e3f":"m\u0301","\u1e41":"m\u0307","\u0144":"n\u0301","\u01f9":"n\u0300","\xf1":"n\u0303","\u0148":"n\u030c","\u1e45":"n\u0307","\xf3":"o\u0301","\xf2":"o\u0300","\xf6":"o\u0308","\u022b":"o\u0308\u0304","\xf5":"o\u0303","\u1e4d":"o\u0303\u0301","\u1e4f":"o\u0303\u0308","\u022d":"o\u0303\u0304","\u014d":"o\u0304","\u1e53":"o\u0304\u0301","\u1e51":"o\u0304\u0300","\u014f":"o\u0306","\u01d2":"o\u030c","\xf4":"o\u0302","\u1ed1":"o\u0302\u0301","\u1ed3":"o\u0302\u0300","\u1ed7":"o\u0302\u0303","\u022f":"o\u0307","\u0231":"o\u0307\u0304","\u0151":"o\u030b","\u1e55":"p\u0301","\u1e57":"p\u0307","\u0155":"r\u0301","\u0159":"r\u030c","\u1e59":"r\u0307","\u015b":"s\u0301","\u1e65":"s\u0301\u0307","\u0161":"s\u030c","\u1e67":"s\u030c\u0307","\u015d":"s\u0302","\u1e61":"s\u0307","\u1e97":"t\u0308","\u0165":"t\u030c","\u1e6b":"t\u0307","\xfa":"u\u0301","\xf9":"u\u0300","\xfc":"u\u0308","\u01d8":"u\u0308\u0301","\u01dc":"u\u0308\u0300","\u01d6":"u\u0308\u0304","\u01da":"u\u0308\u030c","\u0169":"u\u0303","\u1e79":"u\u0303\u0301","\u016b":"u\u0304","\u1e7b":"u\u0304\u0308","\u016d":"u\u0306","\u01d4":"u\u030c","\xfb":"u\u0302","\u016f":"u\u030a","\u0171":"u\u030b","\u1e7d":"v\u0303","\u1e83":"w\u0301","\u1e81":"w\u0300","\u1e85":"w\u0308","\u0175":"w\u0302","\u1e87":"w\u0307","\u1e98":"w\u030a","\u1e8d":"x\u0308","\u1e8b":"x\u0307","\xfd":"y\u0301","\u1ef3":"y\u0300","\xff":"y\u0308","\u1ef9":"y\u0303","\u0233":"y\u0304","\u0177":"y\u0302","\u1e8f":"y\u0307","\u1e99":"y\u030a","\u017a":"z\u0301","\u017e":"z\u030c","\u1e91":"z\u0302","\u017c":"z\u0307","\xc1":"A\u0301","\xc0":"A\u0300","\xc4":"A\u0308","\u01de":"A\u0308\u0304","\xc3":"A\u0303","\u0100":"A\u0304","\u0102":"A\u0306","\u1eae":"A\u0306\u0301","\u1eb0":"A\u0306\u0300","\u1eb4":"A\u0306\u0303","\u01cd":"A\u030c","\xc2":"A\u0302","\u1ea4":"A\u0302\u0301","\u1ea6":"A\u0302\u0300","\u1eaa":"A\u0302\u0303","\u0226":"A\u0307","\u01e0":"A\u0307\u0304","\xc5":"A\u030a","\u01fa":"A\u030a\u0301","\u1e02":"B\u0307","\u0106":"C\u0301","\u010c":"C\u030c","\u0108":"C\u0302","\u010a":"C\u0307","\u010e":"D\u030c","\u1e0a":"D\u0307","\xc9":"E\u0301","\xc8":"E\u0300","\xcb":"E\u0308","\u1ebc":"E\u0303","\u0112":"E\u0304","\u1e16":"E\u0304\u0301","\u1e14":"E\u0304\u0300","\u0114":"E\u0306","\u011a":"E\u030c","\xca":"E\u0302","\u1ebe":"E\u0302\u0301","\u1ec0":"E\u0302\u0300","\u1ec4":"E\u0302\u0303","\u0116":"E\u0307","\u1e1e":"F\u0307","\u01f4":"G\u0301","\u1e20":"G\u0304","\u011e":"G\u0306","\u01e6":"G\u030c","\u011c":"G\u0302","\u0120":"G\u0307","\u1e26":"H\u0308","\u021e":"H\u030c","\u0124":"H\u0302","\u1e22":"H\u0307","\xcd":"I\u0301","\xcc":"I\u0300","\xcf":"I\u0308","\u1e2e":"I\u0308\u0301","\u0128":"I\u0303","\u012a":"I\u0304","\u012c":"I\u0306","\u01cf":"I\u030c","\xce":"I\u0302","\u0130":"I\u0307","\u0134":"J\u0302","\u1e30":"K\u0301","\u01e8":"K\u030c","\u0139":"L\u0301","\u013d":"L\u030c","\u1e3e":"M\u0301","\u1e40":"M\u0307","\u0143":"N\u0301","\u01f8":"N\u0300","\xd1":"N\u0303","\u0147":"N\u030c","\u1e44":"N\u0307","\xd3":"O\u0301","\xd2":"O\u0300","\xd6":"O\u0308","\u022a":"O\u0308\u0304","\xd5":"O\u0303","\u1e4c":"O\u0303\u0301","\u1e4e":"O\u0303\u0308","\u022c":"O\u0303\u0304","\u014c":"O\u0304","\u1e52":"O\u0304\u0301","\u1e50":"O\u0304\u0300","\u014e":"O\u0306","\u01d1":"O\u030c","\xd4":"O\u0302","\u1ed0":"O\u0302\u0301","\u1ed2":"O\u0302\u0300","\u1ed6":"O\u0302\u0303","\u022e":"O\u0307","\u0230":"O\u0307\u0304","\u0150":"O\u030b","\u1e54":"P\u0301","\u1e56":"P\u0307","\u0154":"R\u0301","\u0158":"R\u030c","\u1e58":"R\u0307","\u015a":"S\u0301","\u1e64":"S\u0301\u0307","\u0160":"S\u030c","\u1e66":"S\u030c\u0307","\u015c":"S\u0302","\u1e60":"S\u0307","\u0164":"T\u030c","\u1e6a":"T\u0307","\xda":"U\u0301","\xd9":"U\u0300","\xdc":"U\u0308","\u01d7":"U\u0308\u0301","\u01db":"U\u0308\u0300","\u01d5":"U\u0308\u0304","\u01d9":"U\u0308\u030c","\u0168":"U\u0303","\u1e78":"U\u0303\u0301","\u016a":"U\u0304","\u1e7a":"U\u0304\u0308","\u016c":"U\u0306","\u01d3":"U\u030c","\xdb":"U\u0302","\u016e":"U\u030a","\u0170":"U\u030b","\u1e7c":"V\u0303","\u1e82":"W\u0301","\u1e80":"W\u0300","\u1e84":"W\u0308","\u0174":"W\u0302","\u1e86":"W\u0307","\u1e8c":"X\u0308","\u1e8a":"X\u0307","\xdd":"Y\u0301","\u1ef2":"Y\u0300","\u0178":"Y\u0308","\u1ef8":"Y\u0303","\u0232":"Y\u0304","\u0176":"Y\u0302","\u1e8e":"Y\u0307","\u0179":"Z\u0301","\u017d":"Z\u030c","\u1e90":"Z\u0302","\u017b":"Z\u0307","\u03ac":"\u03b1\u0301","\u1f70":"\u03b1\u0300","\u1fb1":"\u03b1\u0304","\u1fb0":"\u03b1\u0306","\u03ad":"\u03b5\u0301","\u1f72":"\u03b5\u0300","\u03ae":"\u03b7\u0301","\u1f74":"\u03b7\u0300","\u03af":"\u03b9\u0301","\u1f76":"\u03b9\u0300","\u03ca":"\u03b9\u0308","\u0390":"\u03b9\u0308\u0301","\u1fd2":"\u03b9\u0308\u0300","\u1fd1":"\u03b9\u0304","\u1fd0":"\u03b9\u0306","\u03cc":"\u03bf\u0301","\u1f78":"\u03bf\u0300","\u03cd":"\u03c5\u0301","\u1f7a":"\u03c5\u0300","\u03cb":"\u03c5\u0308","\u03b0":"\u03c5\u0308\u0301","\u1fe2":"\u03c5\u0308\u0300","\u1fe1":"\u03c5\u0304","\u1fe0":"\u03c5\u0306","\u03ce":"\u03c9\u0301","\u1f7c":"\u03c9\u0300","\u038e":"\u03a5\u0301","\u1fea":"\u03a5\u0300","\u03ab":"\u03a5\u0308","\u1fe9":"\u03a5\u0304","\u1fe8":"\u03a5\u0306","\u038f":"\u03a9\u0301","\u1ffa":"\u03a9\u0300"},Xn=function(){function e(e,t){this.mode=void 0,this.gullet=void 0,this.settings=void 0,this.leftrightDepth=void 0,this.nextToken=void 0,this.mode="math",this.gullet=new Un(e,t,this.mode),this.settings=t,this.leftrightDepth=0}var t=e.prototype;return t.expect=function(e,t){if(void 0===t&&(t=!0),this.fetch().text!==e)throw new n("Expected '"+e+"', got '"+this.fetch().text+"'",this.fetch());t&&this.consume()},t.consume=function(){this.nextToken=null},t.fetch=function(){return null==this.nextToken&&(this.nextToken=this.gullet.expandNextToken()),this.nextToken},t.switchMode=function(e){this.mode=e,this.gullet.switchMode(e)},t.parse=function(){this.settings.globalGroup||this.gullet.beginGroup(),this.settings.colorIsTextColor&&this.gullet.macros.set("\\color","\\textcolor");var e=this.parseExpression(!1);return this.expect("EOF"),this.settings.globalGroup||this.gullet.endGroup(),e},t.parseExpression=function(t,r){for(var n=[];;){"math"===this.mode&&this.consumeSpaces();var a=this.fetch();if(-1!==e.endOfExpression.indexOf(a.text))break;if(r&&a.text===r)break;if(t&&Tn[a.text]&&Tn[a.text].infix)break;var i=this.parseAtom(r);if(!i)break;"internal"!==i.type&&n.push(i)}return"text"===this.mode&&this.formLigatures(n),this.handleInfixNodes(n)},t.handleInfixNodes=function(e){for(var t,r=-1,a=0;a=0&&this.settings.reportNonstrict("unicodeTextInMathMode",'Latin-1/Unicode text character "'+t[0]+'" used in math mode',e);var s,l=X[this.mode][t].group,h=Bn.range(e);if(U.hasOwnProperty(l)){var m=l;s={type:"atom",mode:this.mode,family:m,loc:h,text:t}}else s={type:l,mode:this.mode,loc:h,text:t};i=s}else{if(!(t.charCodeAt(0)>=128))return null;this.settings.strict&&(w(t.charCodeAt(0))?"math"===this.mode&&this.settings.reportNonstrict("unicodeTextInMathMode",'Unicode text character "'+t[0]+'" used in math mode',e):this.settings.reportNonstrict("unknownSymbol",'Unrecognized Unicode character "'+t[0]+'" ('+t.charCodeAt(0)+")",e)),i={type:"textord",mode:"text",loc:Bn.range(e),text:t}}if(this.consume(),o)for(var c=0;c 0
- || $(event.target).filter(".fold-unfold").size() > 0;
+ var trigger = $(event.target).has(".fold-unfold").length > 0
+ || $(event.target).filter(".fold-unfold").length > 0;
if (trigger) {
$(">*:not(h2)", this).toggle(400);
$(">h2>span.fold-unfold", this).toggleClass("glyphicon-collapse-down glyphicon-collapse-up");
diff --git a/bin/boilerplate/.travis.yml b/bin/boilerplate/.travis.yml
index 4f23be8c0..f6885e9ae 100644
--- a/bin/boilerplate/.travis.yml
+++ b/bin/boilerplate/.travis.yml
@@ -1,23 +1,57 @@
-dist: xenial # Ubuntu 16.04 (required for python 3.7)
-language: python
-python: 3.7
+# Travis CI is only used to check the lesson and is not involved in its deployment
+dist: bionic
+language: ruby
+rvm:
+ - 2.7.1
+
branches:
only:
- gh-pages
- /.*/
+
+cache:
+ apt: true
+ bundler: true
+ directories:
+ - /home/travis/.rvm/
+ - $R_LIBS_USER
+ - $HOME/.cache/pip
+
+env:
+ global:
+ - NOKOGIRI_USE_SYSTEM_LIBRARIES=true # speeds up installation of html-proofer
+ - R_LIBS_USER=~/R/Library
+ - R_LIBS_SITE=/usr/local/lib/R/site-library:/usr/lib/R/site-library
+ - R_VERSION=4.0.2
+
before_install:
- - sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E084DAB9
- - echo "deb https://cran.rstudio.com/bin/linux/ubuntu trusty/" | sudo tee -a /etc/apt/sources.list
- - sudo apt-get update -y
- - sudo apt-get install -y r-base
- - sudo Rscript -e "install.packages('knitr', repos = 'https://', dependencies = TRUE)"
- - sudo Rscript -e "install.packages('stringr', repos = 'https://cran.rstudio.com', dependencies = TRUE)"
- - sudo Rscript -e "install.packages('checkpoint', repos = 'https://cran.rstudio.com', dependencies = TRUE)"
- - sudo Rscript -e "install.packages('ggplot2', repos = 'https://cran.rstudio.com', dependencies = TRUE)"
- - rvm default
- - gem install json kramdown jekyll
-install:
- - pip install pyyaml
+ ## Install R + pandoc + dependencies
+ - sudo add-apt-repository -y "ppa:marutter/rrutter4.0"
+ - sudo add-apt-repository -y "ppa:c2d4u.team/c2d4u4.0+"
+ - sudo add-apt-repository -y "ppa:ubuntugis/ppa"
+ - sudo add-apt-repository -y "ppa:cran/travis"
+ - travis_apt_get_update
+ - sudo apt-get install -y --no-install-recommends build-essential gcc g++ libblas-dev liblapack-dev libncurses5-dev libreadline-dev libjpeg-dev libpcre3-dev libpng-dev zlib1g-dev libbz2-dev liblzma-dev libicu-dev cdbs qpdf texinfo libssh2-1-dev gfortran jq python3.5 python3-pip r-base
+ - export PATH=${TRAVIS_HOME}/R-bin/bin:$PATH
+ - export LD_LIBRARY_PATH=${TRAVIS_HOME}/R-bin/lib:$LD_LIBRARY_PATH
+ - sudo mkdir -p /usr/local/lib/R/site-library $R_LIBS_USER
+ - sudo chmod 2777 /usr/local/lib/R /usr/local/lib/R/site-library $R_LIBS_USER
+ - echo 'options(repos = c(CRAN = "https://packagemanager.rstudio.com/all/__linux__/bionic/latest"))' > ~/.Rprofile.site
+ - export R_PROFILE=~/.Rprofile.site
+ - curl -fLo /tmp/texlive.tar.gz https://github.com/jimhester/ubuntu-bin/releases/download/latest/texlive.tar.gz
+ - tar xzf /tmp/texlive.tar.gz -C ~
+ - export PATH=${TRAVIS_HOME}/texlive/bin/x86_64-linux:$PATH
+ - tlmgr update --self
+ - curl -fLo /tmp/pandoc-2.2-1-amd64.deb https://github.com/jgm/pandoc/releases/download/2.2/pandoc-2.2-1-amd64.deb
+ - sudo dpkg -i /tmp/pandoc-2.2-1-amd64.deb
+ - sudo apt-get install -f
+ - rm /tmp/pandoc-2.2-1-amd64.deb
+ - Rscript -e "install.packages(setdiff(c('renv', 'rprojroot'), installed.packages()), loc = Sys.getenv('R_LIBS_USER')); update.packages(lib.loc = Sys.getenv('R_LIBS_USER'), ask = FALSE, checkBuilt = TRUE)"
+ - Rscript -e 'sessionInfo()'
+ ## Install python and dependencies
+ - python3 -m pip install --upgrade pip setuptools wheel
+ - python3 -m pip install pyyaml
+
script:
- make lesson-check-all
- make --always-make site
diff --git a/bin/boilerplate/CONTRIBUTING.md b/bin/boilerplate/CONTRIBUTING.md
index 7925ceff4..8c095d86a 100644
--- a/bin/boilerplate/CONTRIBUTING.md
+++ b/bin/boilerplate/CONTRIBUTING.md
@@ -70,7 +70,7 @@ There are many ways to contribute,
from writing new exercises and improving existing ones
to updating or filling in the documentation
and submitting [bug reports][issues]
-about things that don't work, aren't clear, or are missing.
+about things that do not work, are not clear, or are missing.
If you are looking for ideas, please see the 'Issues' tab for
a list of issues associated with this repository,
or you may also look at the issues for [Data Carpentry][dc-issues],
@@ -79,7 +79,7 @@ or you may also look at the issues for [Data Carpentry][dc-issues],
Comments on issues and reviews of pull requests are just as welcome:
we are smarter together than we are on our own.
Reviews from novices and newcomers are particularly valuable:
-it's easy for people who have been using these lessons for a while
+it is easy for people who have been using these lessons for a while
to forget how impenetrable some of this material can be,
so fresh eyes are always welcome.
@@ -94,7 +94,7 @@ and (b) explain what you would take out to make room for it.
The first encourages contributors to be honest about requirements;
the second, to think hard about priorities.
-We are also not looking for exercises or other material that only run on one platform.
+We are also not looking for exercises or other material that will only run on one platform.
Our workshops typically contain a mixture of Windows, macOS, and Linux users;
in order to be usable,
our lessons must run equally well on all three.
@@ -104,7 +104,7 @@ our lessons must run equally well on all three.
If you choose to contribute via GitHub, you may want to look at
[How to Contribute to an Open Source Project on GitHub][how-contribute].
To manage changes, we follow [GitHub flow][github-flow].
-Each lesson has two maintainers who review issues and pull requests or encourage others to do so.
+Each lesson has at least two maintainers who review issues and pull requests or encourage others to do so.
The maintainers are community volunteers and have final say over what gets merged into the lesson.
To use the web interface for contributing to a lesson:
@@ -128,20 +128,20 @@ repository for reference while revising.
## Other Resources
-General discussion of [Software Carpentry][swc-site] and [Data Carpentry][dc-site]
+General discussion of [Software Carpentry][swc-site], [Data Carpentry][dc-site], and [Library Carpentry][lc-site]
happens on the [discussion mailing list][discuss-list],
which everyone is welcome to join.
You can also [reach us by email][email].
-[email]: mailto:admin@software-carpentry.org
+[email]: mailto:team@carpentries.org
[dc-issues]: https://github.com/issues?q=user%3Adatacarpentry
[dc-lessons]: http://datacarpentry.org/lessons/
[dc-site]: http://datacarpentry.org/
-[discuss-list]: http://lists.software-carpentry.org/listinfo/discuss
+[discuss-list]: https://carpentries.topicbox.com/groups/discuss
[github]: https://github.com
[github-flow]: https://guides.github.com/introduction/flow/
[github-join]: https://github.com/join
-[how-contribute]: https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github
+[how-contribute]: https://app.egghead.io/playlists/how-to-contribute-to-an-open-source-project-on-github
[issues]: https://guides.github.com/features/issues/
[swc-issues]: https://github.com/issues?q=user%3Aswcarpentry
[swc-lessons]: https://software-carpentry.org/lessons/
diff --git a/bin/boilerplate/README.md b/bin/boilerplate/README.md
index ed47f43c8..592e7bd86 100644
--- a/bin/boilerplate/README.md
+++ b/bin/boilerplate/README.md
@@ -17,7 +17,7 @@ Please see the current list of [issues][FIXME] for ideas for contributing to thi
repository. For making your contribution, we use the GitHub flow, which is
nicely explained in the chapter [Contributing to a Project](http://git-scm.com/book/en/v2/GitHub-Contributing-to-a-Project) in Pro Git
by Scott Chacon.
-Look for the tag ![good_first_issue](https://img.shields.io/badge/-good%20first%20issue-gold.svg). This indicates that the mantainers will welcome a pull request fixing this issue.
+Look for the tag ![good_first_issue](https://img.shields.io/badge/-good%20first%20issue-gold.svg). This indicates that the maintainers will welcome a pull request fixing this issue.
## Maintainer(s)
diff --git a/bin/boilerplate/_config.yml b/bin/boilerplate/_config.yml
index 3c3f4f2d8..242699cf1 100644
--- a/bin/boilerplate/_config.yml
+++ b/bin/boilerplate/_config.yml
@@ -7,13 +7,15 @@
# dc: Data Carpentry
# lc: Library Carpentry
# cp: Carpentries (to use for instructor traning for instance)
+# incubator: Carpentries Incubator
carpentry: "swc"
# Overall title for pages.
title: "Lesson Title"
# Life cycle stage of the lesson
-# possible values: "pre-alpha", "alpha", "beta", "stable"
+# See this page for more details: https://cdh.carpentries.org/the-lesson-life-cycle.html
+# Possible values: "pre-alpha", "alpha", "beta", "stable"
life_cycle: "pre-alpha"
#------------------------------------------------------------
@@ -32,11 +34,11 @@ repository: /
email: "team@carpentries.org"
# Sites.
-amy_site: "https://amy.software-carpentry.org/workshops"
+amy_site: "https://amy.carpentries.org/"
carpentries_github: "https://github.com/carpentries"
carpentries_pages: "https://carpentries.github.io"
carpentries_site: "https://carpentries.org/"
-dc_site: "http://datacarpentry.org"
+dc_site: "https://datacarpentry.org"
example_repo: "https://github.com/carpentries/lesson-example"
example_site: "https://carpentries.github.io/lesson-example"
lc_site: "https://librarycarpentry.org/"
@@ -50,16 +52,13 @@ workshop_site: "https://carpentries.github.io/workshop-template"
cc_by_human: "https://creativecommons.org/licenses/by/4.0/"
# Surveys.
-swc_pre_survey: "https://www.surveymonkey.com/r/swc_pre_workshop_v1?workshop_id="
-swc_post_survey: "https://www.surveymonkey.com/r/swc_post_workshop_v1?workshop_id="
-training_post_survey: "https://www.surveymonkey.com/r/post-instructor-training"
-dc_pre_survey: "https://www.surveymonkey.com/r/dcpreworkshopassessment?workshop_id="
-dc_post_survey: "https://www.surveymonkey.com/r/dcpostworkshopassessment?workshop_id="
-lc_pre_survey: "https://www.surveymonkey.com/r/lcpreworkshopsurvey?workshop_id="
-lc_post_survey: "https://www.surveymonkey.com/r/lcpostworkshopsurvey?workshop_id="
+pre_survey: "https://carpentries.typeform.com/to/wi32rS?slug="
+post_survey: "https://carpentries.typeform.com/to/UgVdRQ?slug="
instructor_pre_survey: "https://www.surveymonkey.com/r/instructor_training_pre_survey?workshop_id="
instructor_post_survey: "https://www.surveymonkey.com/r/instructor_training_post_survey?workshop_id="
+# Set to 'true' for instructor training websites only.
+instructor_training: false
# Start time in minutes (0 to be clock-independent, 540 to show a start at 09:00 am).
start_time: 0
@@ -96,6 +95,9 @@ exclude:
- Makefile
- bin/
- .Rproj.user/
+ - .vendor/
+ - vendor/
+ - .docker-vendor/
# Turn on built-in syntax highlighting.
highlighter: rouge
diff --git a/bin/boilerplate/_extras/figures.md b/bin/boilerplate/_extras/figures.md
index ee5b65082..0012c88ea 100644
--- a/bin/boilerplate/_extras/figures.md
+++ b/bin/boilerplate/_extras/figures.md
@@ -3,11 +3,17 @@ title: Figures
---
{% include base_path.html %}
+{% include manual_episode_order.html %}
-{% comment %}
-Create anchor for each one of the episodes.
-{% endcomment %}
-{% for episode in site.episodes %}
+
+{% comment %} Create anchor for each one of the episodes. {% endcomment %}
+
+{% for lesson_episode in lesson_episodes %}
+ {% if site.episode_order %}
+ {% assign episode = site.episodes | where: "slug", lesson_episode | first %}
+ {% else %}
+ {% assign episode = lesson_episode %}
+ {% endif %}
{% endfor %}
diff --git a/bin/chunk-options.R b/bin/chunk-options.R
index 6bd4aefae..8e0d62af7 100644
--- a/bin/chunk-options.R
+++ b/bin/chunk-options.R
@@ -37,24 +37,34 @@ opts_chunk$set(tidy = FALSE, results = "markup", comment = NA,
# are properly formatted when the site is built.
hook_in <- function(x, options) {
+ lg <- tolower(options$engine)
+ style <- paste0(".language-", lg)
+
stringr::str_c("\n\n~~~\n",
- paste0(x, collapse="\n"),
- "\n~~~\n{: .language-r}\n\n")
+ paste0(x, collapse="\n"),
+ "\n~~~\n{: ", style, "}\n\n")
}
hook_out <- function(x, options) {
x <- gsub("\n$", "", x)
stringr::str_c("\n\n~~~\n",
- paste0(x, collapse="\n"),
- "\n~~~\n{: .output}\n\n")
+ paste0(x, collapse="\n"),
+ "\n~~~\n{: .output}\n\n")
}
hook_error <- function(x, options) {
x <- gsub("\n$", "", x)
stringr::str_c("\n\n~~~\n",
- paste0(x, collapse="\n"),
- "\n~~~\n{: .error}\n\n")
+ paste0(x, collapse="\n"),
+ "\n~~~\n{: .error}\n\n")
+}
+
+hook_warning <- function(x, options) {
+ x <- gsub("\n$", "", x)
+ stringr::str_c("\n\n~~~\n",
+ paste0(x, collapse = "\n"),
+ "\n~~~\n{: .warning}\n\n")
}
-knit_hooks$set(source = hook_in, output = hook_out, warning = hook_error,
- error = hook_error, message = hook_out)
+knit_hooks$set(source = hook_in, output = hook_out, warning = hook_warning,
+ error = hook_error, message = hook_out)
diff --git a/bin/dependencies.R b/bin/dependencies.R
new file mode 100644
index 000000000..4eeeb2152
--- /dev/null
+++ b/bin/dependencies.R
@@ -0,0 +1,107 @@
+install_required_packages <- function(lib = NULL, repos = getOption("repos", default = c(CRAN = "https://cran.rstudio.com/"))) {
+
+ if (is.null(lib)) {
+ lib <- .libPaths()[[1]]
+ }
+
+ message("lib paths: ", paste(lib, collapse = ", "))
+ # Note: RMarkdown is needed for renv to detect packages in Rmd documents.
+ required_pkgs <- c("rprojroot", "desc", "remotes", "renv", "BiocManager", "rmarkdown")
+ installed_pkgs <- rownames(installed.packages(lib.loc = lib))
+ missing_pkgs <- setdiff(required_pkgs, installed_pkgs)
+
+ # The default installation of R will have "@CRAN@" as the default repository,
+ # which directs contrib.url() to either force the user to choose a mirror if
+ # interactive or fail if not. Since we are not interactve, we need to force
+ # the mirror here.
+ if ("@CRAN@" %in% repos) {
+ repos <- c(CRAN = "https://cran.rstudio.com/")
+ }
+
+ if (length(missing_pkgs) != 0) {
+ install.packages(missing_pkgs, lib = lib, repos = repos)
+ }
+}
+
+find_root <- function() {
+
+ cfg <- rprojroot::has_file_pattern("^_config.y*ml$")
+ root <- rprojroot::find_root(cfg)
+
+ root
+}
+
+# set the BiocManager repositories and return a function that resets the default
+# repositories.
+#
+# @example
+# bioc_repos_example <- function() {
+# message("User repos")
+# as.data.frame(getOption("repos"))
+# reset_repos <- use_bioc_repos()
+# on.exit(reset_repos())
+# message("Bioc repos")
+# as.data.frame(getOption("repos"))
+# }
+# bioc_repos_example()
+# as.data.frame(getOption("repos")
+use_bioc_repos <- function() {
+ repos <- getOption("repos")
+ suppressMessages(options(repos = BiocManager::repositories()))
+ function() {
+ options(repos = repos)
+ }
+}
+
+identify_dependencies <- function() {
+
+ root <- find_root()
+
+ reset_repos <- use_bioc_repos()
+ on.exit(reset_repos(), add = TRUE)
+ eps <- file.path(root, "_episodes_rmd")
+ bin <- file.path(root, "bin")
+
+ required_pkgs <- unique(c(
+ ## Packages for episodes
+ renv::dependencies(eps, progress = FALSE, error = "ignored")$Package,
+ ## Packages for tools
+ renv::dependencies(bin, progress = FALSE, error = "ignored")$Package
+ ))
+
+ required_pkgs
+}
+
+create_description <- function(required_pkgs) {
+ d <- desc::description$new("!new")
+ d$set_deps(data.frame(type = "Imports", package = required_pkgs, version = "*"))
+ d$write("DESCRIPTION")
+ # We have to write the description twice to get the hidden dependencies
+ # because renv only considers explicit dependencies.
+ #
+ # This is needed because some of the hidden dependencis will require system
+ # libraries to be configured.
+ suppressMessages(repo <- BiocManager::repositories())
+ deps <- remotes::dev_package_deps(dependencies = TRUE, repos = repo)
+ deps <- deps$package[deps$diff < 0]
+ if (length(deps)) {
+ # only create new DESCRIPTION file if there are dependencies to install
+ d$set_deps(data.frame(type = "Imports", package = deps, version = "*"))
+ d$write("DESCRIPTION")
+ }
+}
+
+install_dependencies <- function(required_pkgs, ...) {
+
+ reset_repos <- use_bioc_repos()
+ on.exit(reset_repos(), add = TRUE)
+
+ create_description(required_pkgs)
+ on.exit(file.remove("DESCRIPTION"), add = TRUE)
+ remotes::install_deps(dependencies = TRUE, ...)
+
+ if (require("knitr") && packageVersion("knitr") < '1.9.19') {
+ stop("knitr must be version 1.9.20 or higher")
+ }
+
+}
diff --git a/bin/generate_md_episodes.R b/bin/generate_md_episodes.R
index 7f37a7b1c..fc55d8241 100644
--- a/bin/generate_md_episodes.R
+++ b/bin/generate_md_episodes.R
@@ -1,34 +1,11 @@
generate_md_episodes <- function() {
- library("methods")
-
- if (!require("remotes", quietly = TRUE)) {
- install.packages("remotes", repos = c(CRAN = "https://cloud.r-project.org/"))
- }
-
- if (!require("requirements", quietly = TRUE)) {
- remotes::install_github("hadley/requirements")
- }
-
- required_pkgs <- unique(c(
- ## Packages for episodes
- requirements:::req_dir("_episodes_rmd"),
- ## Pacakges for tools
- requirements:::req_dir("bin")
- ))
-
- missing_pkgs <- setdiff(required_pkgs, rownames(installed.packages()))
-
- if (length(missing_pkgs)) {
- message("Installing missing required packages: ",
- paste(missing_pkgs, collapse=", "))
- install.packages(missing_pkgs)
- }
-
- if (require("knitr") && packageVersion("knitr") < '1.9.19')
- stop("knitr must be version 1.9.20 or higher")
-
- ## get the Rmd file to process from the command line, and generate the path for their respective outputs
+ # avoid ansi color characters from being printed in the output
+ op <- options()
+ on.exit(options(op), add = TRUE)
+ options(crayon.enabled = FALSE)
+ ## get the Rmd file to process from the command line, and generate the path
+ ## for their respective outputs
args <- commandArgs(trailingOnly = TRUE)
if (!identical(length(args), 2L)) {
stop("input and output file must be passed to the script")
@@ -40,20 +17,29 @@ generate_md_episodes <- function() {
## knit the Rmd into markdown
knitr::knit(src_rmd, output = dest_md)
- # Read the generated md files and add comments advising not to edit them
- vapply(dest_md, function(y) {
- con <- file(y)
- mdfile <- readLines(con)
- if (mdfile[1] != "---")
- stop("Input file does not have a valid header")
- mdfile <- append(mdfile, "# Please do not edit this file directly; it is auto generated.", after = 1)
- mdfile <- append(mdfile, paste("# Instead, please edit",
- basename(y), "in _episodes_rmd/"), after = 2)
- writeLines(mdfile, con)
- close(con)
- return(paste("Warning added to YAML header of", y))
- },
- character(1))
+ # Read the generated md files and add comments advising not to edit them
+ add_no_edit_comment <- function(y) {
+ con <- file(y)
+ mdfile <- readLines(con)
+ if (mdfile[1] != "---")
+ stop("Input file does not have a valid header")
+ mdfile <- append(
+ mdfile,
+ "# Please do not edit this file directly; it is auto generated.",
+ after = 1
+ )
+ mdfile <- append(
+ mdfile,
+ paste("# Instead, please edit", basename(y), "in _episodes_rmd/"),
+ after = 2
+ )
+ writeLines(mdfile, con)
+ close(con)
+ return(paste("Warning added to YAML header of", y))
+ }
+
+ vapply(dest_md, add_no_edit_comment, character(1))
+
}
generate_md_episodes()
diff --git a/bin/install_r_deps.sh b/bin/install_r_deps.sh
new file mode 100755
index 000000000..0280f2412
--- /dev/null
+++ b/bin/install_r_deps.sh
@@ -0,0 +1 @@
+Rscript -e "source(file.path('bin', 'dependencies.R')); install_required_packages(); install_dependencies(identify_dependencies())"
diff --git a/bin/lesson_check.py b/bin/lesson_check.py
old mode 100755
new mode 100644
index 2597da5bf..86e424950
--- a/bin/lesson_check.py
+++ b/bin/lesson_check.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python3
-
"""
Check lesson files and their contents.
"""
@@ -8,10 +6,13 @@
import os
import glob
import re
+import sys
from argparse import ArgumentParser
-from util import (Reporter, read_markdown, load_yaml, check_unwanted_files,
- require)
+# This uses the `__all__` list in `util.py` to determine what objects to import
+# see https://docs.python.org/3/tutorial/modules.html#importing-from-a-package
+from util import *
+from reporter import Reporter
__version__ = '0.3'
@@ -29,19 +30,19 @@
# specially. This list must include all the Markdown files listed in the
# 'bin/initialize' script.
REQUIRED_FILES = {
- '%/CODE_OF_CONDUCT.md': True,
- '%/CONTRIBUTING.md': False,
- '%/LICENSE.md': True,
- '%/README.md': False,
- '%/_extras/discuss.md': True,
- '%/_extras/guide.md': True,
- '%/index.md': True,
- '%/reference.md': True,
- '%/setup.md': True,
+ 'CODE_OF_CONDUCT.md': True,
+ 'CONTRIBUTING.md': False,
+ 'LICENSE.md': True,
+ 'README.md': False,
+ os.path.join('_extras', 'discuss.md'): True,
+ os.path.join('_extras', 'guide.md'): True,
+ 'index.md': True,
+ 'reference.md': True,
+ 'setup.md': True,
}
# Episode filename pattern.
-P_EPISODE_FILENAME = re.compile(r'/_episodes/(\d\d)-[-\w]+.md$')
+P_EPISODE_FILENAME = re.compile(r'(\d\d)-[-\w]+.md$')
# Pattern to match lines ending with whitespace.
P_TRAILING_WHITESPACE = re.compile(r'\s+$')
@@ -58,9 +59,24 @@
# Pattern to match {% include ... %} statements
P_INTERNAL_INCLUDE_LINK = re.compile(r'^{% include ([^ ]*) %}$')
+# Pattern to match image-only and link-only lines
+P_LINK_IMAGE_LINE = re.compile(r'''
+ [> #]* # any number of '>', '#', and spaces
+ \W{,3} # up to 3 non-word characters
+ !? # ! or nothing
+ \[[^]]+\] # [any text]
+ [([] # ( or [
+ [^])]+ # 1+ characters that are neither ] nor )
+ [])] # ] or )
+ (?:{:[^}]+})? # {:any text} or nothing
+ \W{,3} # up to 3 non-word characters
+ [ ]* # any number of spaces
+ \\?$ # \ or nothing + end of line''', re.VERBOSE)
+
# What kinds of blockquotes are allowed?
KNOWN_BLOCKQUOTES = {
'callout',
+ 'caution',
'challenge',
'checklist',
'discussion',
@@ -69,22 +85,17 @@
'prereq',
'quotation',
'solution',
- 'testimonial'
+ 'testimonial',
+ 'warning'
}
# What kinds of code fragments are allowed?
+# Below we allow all 'language-*' code blocks
KNOWN_CODEBLOCKS = {
'error',
'output',
'source',
- 'language-bash',
- 'html',
- 'language-make',
- 'language-matlab',
- 'language-python',
- 'language-r',
- 'language-shell',
- 'language-sql'
+ 'warning'
}
# What fields are required in teaching episode metadata?
@@ -108,14 +119,28 @@
# Please keep this in sync with .editorconfig!
MAX_LINE_LEN = 100
+# Contents of _config.yml
+CONFIG = {}
def main():
"""Main driver."""
args = parse_args()
args.reporter = Reporter()
- check_config(args.reporter, args.source_dir)
+
+ global CONFIG
+ config_file = os.path.join(args.source_dir, '_config.yml')
+ CONFIG = load_yaml(config_file)
+ CONFIG["config_file"] = config_file
+
+ life_cycle = CONFIG.get('life_cycle', None)
+ # pre-alpha lessons should report without error
+ if life_cycle == "pre-alpha":
+ args.permissive = True
+
+ check_config(args.reporter)
check_source_rmd(args.reporter, args.source_dir, args.parser)
+
args.references = read_references(args.reporter, args.reference_path)
docs = read_all_markdown(args.source_dir, args.parser)
@@ -126,8 +151,16 @@ def main():
checker.check()
args.reporter.report()
- if args.reporter.messages and not args.permissive:
- exit(1)
+ if args.reporter.messages:
+ if args.permissive:
+ print("Problems detected but ignored (permissive mode).")
+ else:
+ print("Problems detected.")
+ sys.exit(1)
+ else:
+ print("No problems found.")
+
+ return
def parse_args():
@@ -164,33 +197,35 @@ def parse_args():
args, extras = parser.parse_known_args()
require(args.parser is not None,
- 'Path to Markdown parser not provided')
+ 'Path to Markdown parser not provided',
+ True)
require(not extras,
'Unexpected trailing command-line arguments "{0}"'.format(extras))
return args
-
-def check_config(reporter, source_dir):
+def check_config(reporter):
"""Check configuration file."""
- config_file = os.path.join(source_dir, '_config.yml')
- config = load_yaml(config_file)
- reporter.check_field(config_file, 'configuration',
- config, 'kind', 'lesson')
- reporter.check_field(config_file, 'configuration',
- config, 'carpentry', ('swc', 'dc', 'lc', 'cp'))
- reporter.check_field(config_file, 'configuration', config, 'title')
- reporter.check_field(config_file, 'configuration', config, 'email')
+ reporter.check_field(CONFIG["config_file"], 'configuration',
+ CONFIG, 'kind', 'lesson')
+ reporter.check_field(CONFIG["config_file"], 'configuration',
+ CONFIG, 'carpentry', ('swc', 'dc', 'lc', 'cp', 'incubator'))
+ reporter.check_field(CONFIG["config_file"], 'configuration', CONFIG, 'title')
+ reporter.check_field(CONFIG["config_file"], 'configuration', CONFIG, 'email')
for defaults in [
{'values': {'root': '.', 'layout': 'page'}},
{'values': {'root': '..', 'layout': 'episode'}, 'scope': {'type': 'episodes', 'path': ''}},
{'values': {'root': '..', 'layout': 'page'}, 'scope': {'type': 'extras', 'path': ''}}
]:
- reporter.check(defaults in config.get('defaults', []),
- 'configuration',
- '"root" not set to "." in configuration')
+ error_text = 'incorrect settings for: root "{0}" layout "{1}"'
+ root = defaults["values"]["root"]
+ layout = defaults["values"]["layout"]
+ error_message = error_text.format(root, layout)
+
+ defaults_test = defaults in CONFIG.get('defaults', [])
+ reporter.check(defaults_test, 'configuration', error_message)
def check_source_rmd(reporter, source_dir, parser):
"""Check that Rmd episode files include `source: Rmd`"""
@@ -211,16 +246,29 @@ def read_references(reporter, ref_path):
{symbolic_name : URL}
"""
+ if 'remote_theme' in CONFIG:
+ return {}
+
if not ref_path:
raise Warning("No filename has been provided.")
result = {}
urls_seen = set()
- with open(ref_path, 'r') as reader:
+ with open(ref_path, 'r', encoding='utf-8') as reader:
for (num, line) in enumerate(reader, 1):
- if P_INTERNAL_INCLUDE_LINK.search(line): continue
+ # Skip empty lines
+ if len(line.strip()) == 0:
+ continue
+
+ # Skip HTML comments
+ if line.strip().startswith(""):
+ continue
+
+ # Skip Liquid's {% include ... %} lines
+ if P_INTERNAL_INCLUDE_LINK.search(line):
+ continue
m = P_INTERNAL_LINK_DEF.search(line)
@@ -272,7 +320,7 @@ def check_fileset(source_dir, reporter, filenames_present):
"""Are all required files present? Are extraneous files present?"""
# Check files with predictable names.
- required = [p.replace('%', source_dir) for p in REQUIRED_FILES]
+ required = [os.path.join(source_dir, p) for p in REQUIRED_FILES]
missing = set(required) - set(filenames_present)
for m in missing:
reporter.add(None, 'Missing required file {0}', m)
@@ -282,7 +330,10 @@ def check_fileset(source_dir, reporter, filenames_present):
for filename in filenames_present:
if '_episodes' not in filename:
continue
- m = P_EPISODE_FILENAME.search(filename)
+
+ # split path to check episode name
+ base_name = os.path.basename(filename)
+ m = P_EPISODE_FILENAME.search(base_name)
if m and m.group(1):
seen.append(m.group(1))
else:
@@ -356,12 +407,19 @@ def check_line_lengths(self):
"""Check the raw text of the lesson body."""
if self.args.line_lengths:
- over = [i for (i, l, n) in self.lines if (
- n > MAX_LINE_LEN) and (not l.startswith('!'))]
- self.reporter.check(not over,
+ over_limit = []
+
+ for (i, l, n) in self.lines:
+ # Report lines that are longer than the suggested
+ # line length limit only if they're not
+ # link-only or image-only lines.
+ if n > MAX_LINE_LEN and not P_LINK_IMAGE_LINE.match(l):
+ over_limit.append(i)
+
+ self.reporter.check(not over_limit,
self.filename,
'Line(s) too long: {0}',
- ', '.join([str(i) for i in over]))
+ ', '.join([str(i) for i in over_limit]))
def check_trailing_whitespace(self):
"""Check for whitespace at the ends of lines."""
@@ -389,7 +447,8 @@ def check_codeblock_classes(self):
for node in self.find_all(self.doc, {'type': 'codeblock'}):
cls = self.get_val(node, 'attr', 'class')
- self.reporter.check(cls in KNOWN_CODEBLOCKS,
+ self.reporter.check(cls is not None and (cls in KNOWN_CODEBLOCKS or
+ cls.startswith('language-')),
(self.filename, self.get_loc(node)),
'Unknown or missing code block type {0}',
cls)
@@ -519,6 +578,9 @@ def check_metadata_fields(self, expected):
def check_reference_inclusion(self):
"""Check that links file has been included."""
+ if 'remote_theme' in CONFIG:
+ return
+
if not self.args.reference_path:
return
@@ -556,7 +618,8 @@ def __init__(self, args, filename, metadata, metadata_len, text, lines, doc):
(re.compile(r'README\.md'), CheckNonJekyll),
(re.compile(r'index\.md'), CheckIndex),
(re.compile(r'reference\.md'), CheckReference),
- (re.compile(r'_episodes/.*\.md'), CheckEpisode),
+ # '.' below is what's passed on the command line via '-s' flag
+ (re.compile(os.path.join('.','_episodes', '[^/]*\.md')), CheckEpisode),
(re.compile(r'.*\.md'), CheckGeneric)
]
diff --git a/bin/lesson_initialize.py b/bin/lesson_initialize.py
old mode 100755
new mode 100644
index 9ba811629..2f7b8e672
--- a/bin/lesson_initialize.py
+++ b/bin/lesson_initialize.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python3
-
"""Initialize a newly-created repository."""
@@ -14,11 +12,11 @@
'CONTRIBUTING.md',
'README.md',
'_config.yml',
- '_episodes/01-introduction.md',
- '_extras/about.md',
- '_extras/discuss.md',
- '_extras/figures.md',
- '_extras/guide.md',
+ os.path.join('_episodes', '01-introduction.md'),
+ os.path.join('_extras', 'about.md'),
+ os.path.join('_extras', 'discuss.md'),
+ os.path.join('_extras', 'figures.md'),
+ os.path.join('_extras', 'guide.md'),
'index.md',
'reference.md',
'setup.md',
@@ -41,7 +39,7 @@ def main():
# Create.
for path in BOILERPLATE:
shutil.copyfile(
- "bin/boilerplate/{}".format(path),
+ os.path.join('bin', 'boilerplate', path),
path
)
diff --git a/bin/markdown_ast.rb b/bin/markdown_ast.rb
index c3fd0b5e2..2ef3f772c 100755
--- a/bin/markdown_ast.rb
+++ b/bin/markdown_ast.rb
@@ -1,11 +1,13 @@
#!/usr/bin/env ruby
+# frozen_string_literal: true
# Use Kramdown parser to produce AST for Markdown document.
-require "kramdown"
-require "json"
+require 'kramdown'
+require 'kramdown-parser-gfm'
+require 'json'
-markdown = STDIN.read()
-doc = Kramdown::Document.new(markdown)
+markdown = $stdin.read
+doc = Kramdown::Document.new(markdown, input: 'GFM', hard_wrap: false)
tree = doc.to_hash_a_s_t
puts JSON.pretty_generate(tree)
diff --git a/bin/repo_check.py b/bin/repo_check.py
old mode 100755
new mode 100644
index af4b78235..6988ca59a
--- a/bin/repo_check.py
+++ b/bin/repo_check.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python3
-
"""
Check repository settings.
"""
@@ -11,7 +9,8 @@
import re
from argparse import ArgumentParser
-from util import Reporter, require
+from util import require
+from reporter import Reporter
# Import this way to produce a more useful error message.
try:
@@ -22,7 +21,7 @@
# Pattern to match Git command-line output for remotes => (user name, project name).
-P_GIT_REMOTE = re.compile(r'upstream\s+[^:]+:([^/]+)/([^.]+)\.git\s+\(fetch\)')
+P_GIT_REMOTE = re.compile(r'upstream\s+(?:https://|git@)github.com[:/]([^/]+)/([^.]+)(\.git)?\s+\(fetch\)')
# Repository URL format string.
F_REPO_URL = 'https://github.com/{0}/{1}/'
@@ -104,7 +103,7 @@ def get_repo_url(repo_url):
# Guess.
cmd = 'git remote -v'
p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE,
- close_fds=True, universal_newlines=True)
+ close_fds=True, universal_newlines=True, encoding='utf-8')
stdout_data, stderr_data = p.communicate()
stdout_data = stdout_data.split('\n')
matches = [P_GIT_REMOTE.match(line) for line in stdout_data]
diff --git a/bin/reporter.py b/bin/reporter.py
new file mode 100644
index 000000000..550dbf059
--- /dev/null
+++ b/bin/reporter.py
@@ -0,0 +1,75 @@
+import sys
+
+class Reporter:
+ """Collect and report errors."""
+
+ # Marker to show that an expected value hasn't been provided.
+ # (Can't use 'None' because that might be a legitimate value.)
+ _DEFAULT_REPORTER = []
+
+ def __init__(self):
+ """Constructor."""
+ self.messages = []
+
+ def check_field(self, filename, name, values, key, expected=_DEFAULT_REPORTER):
+ """Check that a dictionary has an expected value."""
+
+ if key not in values:
+ self.add(filename, '{0} does not contain {1}', name, key)
+ elif expected is self._DEFAULT_REPORTER:
+ pass
+ elif type(expected) in (tuple, set, list):
+ if values[key] not in expected:
+ self.add(
+ filename, '{0} {1} value {2} is not in {3}', name, key, values[key], expected)
+ elif values[key] != expected:
+ self.add(filename, '{0} {1} is {2} not {3}',
+ name, key, values[key], expected)
+
+ def check(self, condition, location, fmt, *args):
+ """Append error if condition not met."""
+
+ if not condition:
+ self.add(location, fmt, *args)
+
+ def add(self, location, fmt, *args):
+ """Append error unilaterally."""
+
+ self.messages.append((location, fmt.format(*args)))
+
+ @staticmethod
+ def pretty(item):
+ location, message = item
+ if isinstance(location, type(None)):
+ return message
+ elif isinstance(location, str):
+ return location + ': ' + message
+ elif isinstance(location, tuple):
+ return '{0}:{1}: '.format(*location) + message
+
+ print('Unknown item "{0}"'.format(item), file=sys.stderr)
+ return NotImplemented
+
+ @staticmethod
+ def key(item):
+ location, message = item
+ if isinstance(location, type(None)):
+ return ('', -1, message)
+ elif isinstance(location, str):
+ return (location, -1, message)
+ elif isinstance(location, tuple):
+ return (location[0], location[1], message)
+
+ print('Unknown item "{0}"'.format(item), file=sys.stderr)
+ return NotImplemented
+
+ def report(self, stream=sys.stdout):
+ """Report all messages in order."""
+
+ if not self.messages:
+ return
+
+ for m in sorted(self.messages, key=self.key):
+ print(self.pretty(m), file=stream)
+
+
diff --git a/bin/run-make-docker-serve.sh b/bin/run-make-docker-serve.sh
new file mode 100755
index 000000000..1e091781e
--- /dev/null
+++ b/bin/run-make-docker-serve.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+set -o errexit
+set -o pipefail
+set -o nounset
+
+
+bundle install
+bundle update
+exec bundle exec jekyll serve --host 0.0.0.0
diff --git a/bin/test_lesson_check.py b/bin/test_lesson_check.py
old mode 100755
new mode 100644
index 960059e8d..7a6d603a4
--- a/bin/test_lesson_check.py
+++ b/bin/test_lesson_check.py
@@ -1,21 +1,17 @@
-#!/usr/bin/env python3
-
import unittest
import lesson_check
-import util
+import reporter
class TestFileList(unittest.TestCase):
def setUp(self):
- self.reporter = util.Reporter() # TODO: refactor reporter class.
+ self.reporter = reporter.Reporter() # TODO: refactor reporter class.
def test_file_list_has_expected_entries(self):
# For first pass, simply assume that all required files are present
- all_filenames = [filename.replace('%', '')
- for filename in lesson_check.REQUIRED_FILES]
- lesson_check.check_fileset('', self.reporter, all_filenames)
+ lesson_check.check_fileset('', self.reporter, lesson_check.REQUIRED_FILES)
self.assertEqual(len(self.reporter.messages), 0)
diff --git a/bin/util.py b/bin/util.py
index f9dc12f3f..1398c3788 100644
--- a/bin/util.py
+++ b/bin/util.py
@@ -10,94 +10,13 @@
print('Unable to import YAML module: please install PyYAML', file=sys.stderr)
sys.exit(1)
-
-# Things an image file's name can end with.
-IMAGE_FILE_SUFFIX = {
- '.gif',
- '.jpg',
- '.png',
- '.svg'
-}
+__all__ = ['check_unwanted_files', 'load_yaml', 'read_markdown', 'require']
# Files that shouldn't be present.
UNWANTED_FILES = [
'.nojekyll'
]
-# Marker to show that an expected value hasn't been provided.
-# (Can't use 'None' because that might be a legitimate value.)
-REPORTER_NOT_SET = []
-
-
-class Reporter:
- """Collect and report errors."""
-
- def __init__(self):
- """Constructor."""
- self.messages = []
-
- def check_field(self, filename, name, values, key, expected=REPORTER_NOT_SET):
- """Check that a dictionary has an expected value."""
-
- if key not in values:
- self.add(filename, '{0} does not contain {1}', name, key)
- elif expected is REPORTER_NOT_SET:
- pass
- elif type(expected) in (tuple, set, list):
- if values[key] not in expected:
- self.add(
- filename, '{0} {1} value {2} is not in {3}', name, key, values[key], expected)
- elif values[key] != expected:
- self.add(filename, '{0} {1} is {2} not {3}',
- name, key, values[key], expected)
-
- def check(self, condition, location, fmt, *args):
- """Append error if condition not met."""
-
- if not condition:
- self.add(location, fmt, *args)
-
- def add(self, location, fmt, *args):
- """Append error unilaterally."""
-
- self.messages.append((location, fmt.format(*args)))
-
- @staticmethod
- def pretty(item):
- location, message = item
- if isinstance(location, type(None)):
- return message
- elif isinstance(location, str):
- return location + ': ' + message
- elif isinstance(location, tuple):
- return '{0}:{1}: '.format(*location) + message
-
- print('Unknown item "{0}"'.format(item), file=sys.stderr)
- return NotImplemented
-
- @staticmethod
- def key(item):
- location, message = item
- if isinstance(location, type(None)):
- return ('', -1, message)
- elif isinstance(location, str):
- return (location, -1, message)
- elif isinstance(location, tuple):
- return (location[0], location[1], message)
-
- print('Unknown item "{0}"'.format(item), file=sys.stderr)
- return NotImplemented
-
- def report(self, stream=sys.stdout):
- """Report all messages in order."""
-
- if not self.messages:
- return
-
- for m in sorted(self.messages, key=self.key):
- print(self.pretty(m), file=stream)
-
-
def read_markdown(parser, path):
"""
Get YAML and AST for Markdown file, returning
@@ -105,7 +24,7 @@ def read_markdown(parser, path):
"""
# Split and extract YAML (if present).
- with open(path, 'r') as reader:
+ with open(path, 'r', encoding='utf-8') as reader:
body = reader.read()
metadata_raw, metadata_yaml, body = split_metadata(path, body)
@@ -115,9 +34,9 @@ def read_markdown(parser, path):
for (i, line) in enumerate(body.split('\n'))]
# Parse Markdown.
- cmd = 'ruby {0}'.format(parser)
+ cmd = 'bundle exec ruby {0}'.format(parser)
p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE,
- close_fds=True, universal_newlines=True)
+ close_fds=True, universal_newlines=True, encoding='utf-8')
stdout_data, stderr_data = p.communicate(body)
doc = json.loads(stdout_data)
@@ -144,11 +63,10 @@ def split_metadata(path, text):
metadata_raw = pieces[1]
text = pieces[2]
try:
- metadata_yaml = yaml.load(metadata_raw, Loader=yaml.FullLoader)
+ metadata_yaml = yaml.load(metadata_raw, Loader=yaml.SafeLoader)
except yaml.YAMLError as e:
- print('Unable to parse YAML header in {0}:\n{1}'.format(
- path, e), file=sys.stderr)
- sys.exit(1)
+ message = 'Unable to parse YAML header in {0}:\n{1}'
+ print(message.format(path, e), file=sys.stderr)
return metadata_raw, metadata_yaml, text
@@ -160,13 +78,16 @@ def load_yaml(filename):
"""
try:
- with open(filename, 'r') as reader:
- return yaml.load(reader, Loader=yaml.FullLoader)
- except (yaml.YAMLError, IOError) as e:
- print('Unable to load YAML file {0}:\n{1}'.format(
- filename, e), file=sys.stderr)
- sys.exit(1)
+ with open(filename, 'r', encoding='utf-8') as reader:
+ return yaml.load(reader, Loader=yaml.SafeLoader)
+ except yaml.YAMLError as e:
+ message = 'ERROR: Unable to load YAML file {0}:\n{1}'
+ print(message.format(filename, e), file=sys.stderr)
+ except (FileNotFoundError, IOError):
+ message = 'ERROR: File {} not found'
+ print(message.format(filename), file=sys.stderr)
+ return {}
def check_unwanted_files(dir_path, reporter):
"""
@@ -180,9 +101,11 @@ def check_unwanted_files(dir_path, reporter):
"Unwanted file found")
-def require(condition, message):
+def require(condition, message, fatal=False):
"""Fail if condition not met."""
if not condition:
print(message, file=sys.stderr)
- sys.exit(1)
+
+ if fatal:
+ sys.exit(1)
diff --git a/bin/workshop_check.py b/bin/workshop_check.py
old mode 100755
new mode 100644
index 0523d0c26..312b1a1de
--- a/bin/workshop_check.py
+++ b/bin/workshop_check.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python3
-
'''Check that a workshop's index.html metadata is valid. See the
docstrings on the checking functions for a summary of the checks.
'''
@@ -9,7 +7,8 @@
import os
import re
from datetime import date
-from util import Reporter, split_metadata, load_yaml, check_unwanted_files
+from util import split_metadata, load_yaml, check_unwanted_files
+from reporter import Reporter
# Metadata field patterns.
EMAIL_PATTERN = r'[^@]+@[^@]+\.[^@]+'
@@ -19,7 +18,7 @@
# Defaults.
CARPENTRIES = ("dc", "swc", "lc", "cp")
-DEFAULT_CONTACT_EMAIL = 'admin@software-carpentry.org'
+DEFAULT_CONTACT_EMAIL = 'team@carpentries.org'
USAGE = 'Usage: "workshop_check.py path/to/root/directory"'
@@ -410,7 +409,7 @@ def main():
reporter = Reporter()
check_config(reporter, config_file)
check_unwanted_files(root_dir, reporter)
- with open(index_file) as reader:
+ with open(index_file, encoding='utf-8') as reader:
data = reader.read()
check_file(reporter, index_file, data)
reporter.report()