=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").length > 0;
- if (trigger) {
- $(">*:not(h2)", this).toggle(400);
- $(">h2>span.fold-unfold", this).toggleClass("glyphicon-collapse-down glyphicon-collapse-up");
- event.stopPropagation();
- }
-});
-$(".solution").each(function() {
- $(">*:not(h2)", this).toggle();
- var h2 = $("h2:first", this);
- h2.append("");
-});
-
-
-// Handle searches.
-// Relies on document having 'meta' element with name 'search-domain'.
-function google_search() {
- var query = document.getElementById("google-search").value;
- var domain = $("meta[name=search-domain]").attr("value");
- window.open("https://www.google.com/search?q=" + query + "+site:" + domain);
-}
-
-// function to shrink the life cycle bar when scrolling
-$(function(){
- $('#life-cycle').data('size','big');
-});
-
-$(window).scroll(function(){
- if($(document).scrollTop() > 0)
- {
- if($('#life-cycle').data('size') == 'big')
- {
- $('#life-cycle').data('size','small');
- $('#life-cycle').stop().animate({
- padding: '5px'
- },100);
- }
- }
- else
- {
- if($('#life-cycle').data('size') == 'small')
- {
- $('#life-cycle').data('size','big');
- $('#life-cycle').stop().animate({
- padding: '15px'
- },100);
- }
- }
-});
diff --git a/bin/boilerplate/AUTHORS b/bin/boilerplate/AUTHORS
deleted file mode 100644
index 04e1f5a..0000000
--- a/bin/boilerplate/AUTHORS
+++ /dev/null
@@ -1 +0,0 @@
-FIXME: list authors' names and email addresses.
\ No newline at end of file
diff --git a/bin/boilerplate/CITATION b/bin/boilerplate/CITATION
deleted file mode 100644
index 56ece3c..0000000
--- a/bin/boilerplate/CITATION
+++ /dev/null
@@ -1 +0,0 @@
-FIXME: describe how to cite this lesson.
\ No newline at end of file
diff --git a/bin/boilerplate/CONTRIBUTING.md b/bin/boilerplate/CONTRIBUTING.md
deleted file mode 100644
index 8c095d8..0000000
--- a/bin/boilerplate/CONTRIBUTING.md
+++ /dev/null
@@ -1,151 +0,0 @@
-# Contributing
-
-[The Carpentries][c-site] ([Software Carpentry][swc-site], [Data Carpentry][dc-site], and [Library Carpentry][lc-site]) are open source projects,
-and we welcome contributions of all kinds:
-new lessons,
-fixes to existing material,
-bug reports,
-and reviews of proposed changes are all welcome.
-
-## Contributor Agreement
-
-By contributing,
-you agree that we may redistribute your work under [our license](LICENSE.md).
-In exchange,
-we will address your issues and/or assess your change proposal as promptly as we can,
-and help you become a member of our community.
-Everyone involved in [The Carpentries][c-site]
-agrees to abide by our [code of conduct](CODE_OF_CONDUCT.md).
-
-## How to Contribute
-
-The easiest way to get started is to file an issue
-to tell us about a spelling mistake,
-some awkward wording,
-or a factual error.
-This is a good way to introduce yourself
-and to meet some of our community members.
-
-1. If you do not have a [GitHub][github] account,
- you can [send us comments by email][email].
- However,
- we will be able to respond more quickly if you use one of the other methods described below.
-
-2. If you have a [GitHub][github] account,
- or are willing to [create one][github-join],
- but do not know how to use Git,
- you can report problems or suggest improvements by [creating an issue][issues].
- This allows us to assign the item to someone
- and to respond to it in a threaded discussion.
-
-3. If you are comfortable with Git,
- and would like to add or change material,
- you can submit a pull request (PR).
- Instructions for doing this are [included below](#using-github).
-
-## Where to Contribute
-
-1. If you wish to change this lesson,
- please work in ,
- which can be viewed at .
-
-2. If you wish to change the example lesson,
- please work in ,
- which documents the format of our lessons
- and can be viewed at .
-
-3. If you wish to change the template used for workshop websites,
- please work in .
- The home page of that repository explains how to set up workshop websites,
- while the extra pages in
- provide more background on our design choices.
-
-4. If you wish to change CSS style files, tools,
- or HTML boilerplate for lessons or workshops stored in `_includes` or `_layouts`,
- please work in .
-
-## What to Contribute
-
-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 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],
-[Software Carpentry][swc-issues], and [Library Carpentry][lc-issues] projects.
-
-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 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.
-
-## What *Not* to Contribute
-
-Our lessons already contain more material than we can cover in a typical workshop,
-so we are usually *not* looking for more concepts or tools to add to them.
-As a rule,
-if you want to introduce a new idea,
-you must (a) estimate how long it will take to teach
-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 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.
-
-## Using GitHub
-
-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 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:
-
-1. Fork the originating repository to your GitHub profile.
-2. Within your version of the forked repository, move to the `gh-pages` branch and
-create a new branch for each significant change being made.
-3. Navigate to the file(s) you wish to change within the new branches and make revisions as required.
-4. Commit all changed files within the appropriate branches.
-5. Create individual pull requests from each of your changed branches
-to the `gh-pages` branch within the originating repository.
-6. If you receive feedback, make changes using your issue-specific branches of the forked
-repository and the pull requests will update automatically.
-7. Repeat as needed until all feedback has been addressed.
-
-When starting work, please make sure your clone of the originating `gh-pages` branch is up-to-date
-before creating your own revision-specific branch(es) from there.
-Additionally, please only work from your newly-created branch(es) and *not*
-your clone of the originating `gh-pages` branch.
-Lastly, published copies of all the lessons are available in the `gh-pages` branch of the originating
-repository for reference while revising.
-
-## Other Resources
-
-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: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]: 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://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/
-[swc-site]: https://software-carpentry.org/
-[c-site]: https://carpentries.org/
-[lc-site]: https://librarycarpentry.org/
-[lc-issues]: https://github.com/issues?q=user%3Alibrarycarpentry
diff --git a/bin/boilerplate/README.md b/bin/boilerplate/README.md
deleted file mode 100644
index 060994a..0000000
--- a/bin/boilerplate/README.md
+++ /dev/null
@@ -1,40 +0,0 @@
-# FIXME Lesson title
-
-[](https://swc-slack-invite.herokuapp.com/)
-
-This repository generates the corresponding lesson website from [The Carpentries](https://carpentries.org/) repertoire of lessons.
-
-## Contributing
-
-We welcome all contributions to improve the lesson! Maintainers will do their best to help you if you have any
-questions, concerns, or experience any difficulties along the way.
-
-We'd like to ask you to familiarize yourself with our [Contribution Guide](CONTRIBUTING.md) and have a look at
-the [more detailed guidelines][lesson-example] on proper formatting, ways to render the lesson locally, and even
-how to write new episodes.
-
-Please see the current list of [issues][FIXME] for ideas for contributing to this
-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 . This indicates that the maintainers will welcome a pull request fixing this issue.
-
-
-## Maintainer(s)
-
-Current maintainers of this lesson are
-
-* FIXME
-* FIXME
-* FIXME
-
-
-## Authors
-
-A list of contributors to the lesson can be found in [AUTHORS](AUTHORS)
-
-## Citation
-
-To cite this lesson, please consult with [CITATION](CITATION)
-
-[lesson-example]: https://carpentries.github.io/lesson-example
diff --git a/bin/boilerplate/_config.yml b/bin/boilerplate/_config.yml
deleted file mode 100644
index 3fab917..0000000
--- a/bin/boilerplate/_config.yml
+++ /dev/null
@@ -1,104 +0,0 @@
-#------------------------------------------------------------
-# Values for this lesson.
-#------------------------------------------------------------
-
-# Which carpentry is this ("swc", "dc", "lc", or "cp")?
-# swc: Software Carpentry
-# 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
-# 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"
-
-#------------------------------------------------------------
-# Generic settings (should not need to change).
-#------------------------------------------------------------
-
-# What kind of thing is this ("workshop" or "lesson")?
-kind: "lesson"
-
-# Magic to make URLs resolve both locally and on GitHub.
-# See https://help.github.com/articles/repository-metadata-on-github-pages/.
-# Please don't change it: / is correct.
-repository: /
-
-# Email address, no mailto:
-email: "team@carpentries.org"
-
-# Sites.
-coc: "https://docs.carpentries.org/topic_folders/policies/code-of-conduct.html"
-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: "https://datacarpentry.org"
-example_repo: "https://github.com/carpentries/lesson-example"
-example_site: "https://carpentries.github.io/lesson-example"
-lc_site: "https://librarycarpentry.org/"
-swc_github: "https://github.com/swcarpentry"
-swc_pages: "https://swcarpentry.github.io"
-swc_site: "https://software-carpentry.org"
-template_repo: "https://github.com/carpentries/styles"
-training_site: "https://carpentries.github.io/instructor-training"
-workshop_repo: "https://github.com/carpentries/workshop-template"
-workshop_site: "https://carpentries.github.io/workshop-template"
-cc_by_human: "https://creativecommons.org/licenses/by/4.0/"
-
-# Surveys.
-pre_survey: "https://carpentries.typeform.com/to/wi32rS#slug="
-post_survey: "https://carpentries.typeform.com/to/UgVdRQ#slug="
-instructor_pre_survey: "https://carpentries.typeform.com/to/QVOarK#slug="
-instructor_post_survey: "https://carpentries.typeform.com/to/cjJ9UP#slug="
-
-# 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
-
-# Specify that things in the episodes collection should be output.
-collections:
- episodes:
- output: true
- permalink: /:path/index.html
- extras:
- output: true
- permalink: /:path/index.html
-
-# Set the default layout for things in the episodes collection.
-defaults:
- - values:
- root: .
- layout: page
- - scope:
- path: ""
- type: episodes
- values:
- root: ..
- layout: episode
- - scope:
- path: ""
- type: extras
- values:
- root: ..
- layout: page
-
-# Files and directories that are not to be copied.
-exclude:
- - Makefile
- - bin/
- - .Rproj.user/
- - .vendor/
- - vendor/
- - .docker-vendor/
-
-# Turn on built-in syntax highlighting.
-highlighter: rouge
diff --git a/bin/boilerplate/_episodes/01-introduction.md b/bin/boilerplate/_episodes/01-introduction.md
deleted file mode 100644
index 2e156c2..0000000
--- a/bin/boilerplate/_episodes/01-introduction.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-title: "Introduction"
-teaching: 0
-exercises: 0
-questions:
-- "Key question (FIXME)"
-objectives:
-- "First learning objective. (FIXME)"
-keypoints:
-- "First key point. Brief Answer to questions. (FIXME)"
----
-FIXME
-
-{% include links.md %}
-
diff --git a/bin/boilerplate/_extras/about.md b/bin/boilerplate/_extras/about.md
deleted file mode 100644
index 5f07f65..0000000
--- a/bin/boilerplate/_extras/about.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: About
----
-{% include carpentries.html %}
-{% include links.md %}
diff --git a/bin/boilerplate/_extras/discuss.md b/bin/boilerplate/_extras/discuss.md
deleted file mode 100644
index bfc33c5..0000000
--- a/bin/boilerplate/_extras/discuss.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Discussion
----
-FIXME
-
-{% include links.md %}
diff --git a/bin/boilerplate/_extras/figures.md b/bin/boilerplate/_extras/figures.md
deleted file mode 100644
index 0012c88..0000000
--- a/bin/boilerplate/_extras/figures.md
+++ /dev/null
@@ -1,79 +0,0 @@
----
-title: Figures
----
-
-{% include base_path.html %}
-{% include manual_episode_order.html %}
-
-
-
-{% 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 %}
-
-{% include links.md %}
diff --git a/bin/boilerplate/_extras/guide.md b/bin/boilerplate/_extras/guide.md
deleted file mode 100644
index 50f266f..0000000
--- a/bin/boilerplate/_extras/guide.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: "Instructor Notes"
----
-FIXME
-
-{% include links.md %}
diff --git a/bin/boilerplate/index.md b/bin/boilerplate/index.md
deleted file mode 100644
index 95ccdbd..0000000
--- a/bin/boilerplate/index.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-layout: lesson
-root: . # Is the only page that doesn't follow the pattern /:path/index.html
-permalink: index.html # Is the only page that doesn't follow the pattern /:path/index.html
----
-FIXME: home page introduction
-
-
-
-{% comment %} This is a comment in Liquid {% endcomment %}
-
-> ## Prerequisites
->
-> FIXME
-{: .prereq}
-
-{% include links.md %}
diff --git a/bin/boilerplate/reference.md b/bin/boilerplate/reference.md
deleted file mode 100644
index 8c82616..0000000
--- a/bin/boilerplate/reference.md
+++ /dev/null
@@ -1,9 +0,0 @@
----
-layout: reference
----
-
-## Glossary
-
-FIXME
-
-{% include links.md %}
diff --git a/bin/boilerplate/setup.md b/bin/boilerplate/setup.md
deleted file mode 100644
index b8c5032..0000000
--- a/bin/boilerplate/setup.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: Setup
----
-FIXME
-
-
-{% include links.md %}
diff --git a/bin/chunk-options.R b/bin/chunk-options.R
deleted file mode 100644
index 8e0d62a..0000000
--- a/bin/chunk-options.R
+++ /dev/null
@@ -1,70 +0,0 @@
-# These settings control the behavior of all chunks in the novice R materials.
-# For example, to generate the lessons with all the output hidden, simply change
-# `results` from "markup" to "hide".
-# For more information on available chunk options, see
-# http://yihui.name/knitr/options#chunk_options
-
-library("knitr")
-
-fix_fig_path <- function(pth) file.path("..", pth)
-
-
-## We set the path for the figures globally below, so if we want to
-## customize it for individual episodes, we can append a prefix to the
-## global path. For instance, if we call knitr_fig_path("01-") in the
-## first episode of the lesson, it will generate the figures in
-## `fig/rmd-01-`
-knitr_fig_path <- function(prefix) {
- new_path <- paste0(opts_chunk$get("fig.path"),
- prefix)
- opts_chunk$set(fig.path = new_path)
-}
-
-## We use the rmd- prefix for the figures generated by the lessons so
-## they can be easily identified and deleted by `make clean-rmd`. The
-## working directory when the lessons are generated is the root so the
-## figures need to be saved in fig/, but when the site is generated,
-## the episodes will be one level down. We fix the path using the
-## `fig.process` option.
-
-opts_chunk$set(tidy = FALSE, results = "markup", comment = NA,
- fig.align = "center", fig.path = "fig/rmd-",
- fig.process = fix_fig_path,
- fig.width = 8.5, fig.height = 8.5,
- fig.retina = 2)
-
-# The hooks below add html tags to the code chunks and their output so that they
-# 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{: ", 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")
-}
-
-hook_error <- function(x, options) {
- x <- gsub("\n$", "", x)
- stringr::str_c("\n\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_warning,
- error = hook_error, message = hook_out)
diff --git a/bin/dependencies.R b/bin/dependencies.R
deleted file mode 100644
index 4eeeb21..0000000
--- a/bin/dependencies.R
+++ /dev/null
@@ -1,107 +0,0 @@
-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
deleted file mode 100644
index 7fb4c5a..0000000
--- a/bin/generate_md_episodes.R
+++ /dev/null
@@ -1,44 +0,0 @@
-generate_md_episodes <- function() {
-
- # 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")
- }
-
- src_rmd <- args[1]
- dest_md <- args[2]
-
- ## 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
- 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
deleted file mode 100755
index 0280f24..0000000
--- a/bin/install_r_deps.sh
+++ /dev/null
@@ -1 +0,0 @@
-Rscript -e "source(file.path('bin', 'dependencies.R')); install_required_packages(); install_dependencies(identify_dependencies())"
diff --git a/bin/knit_lessons.sh b/bin/knit_lessons.sh
deleted file mode 100755
index 141c136..0000000
--- a/bin/knit_lessons.sh
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/usr/bin/env bash
-
-# Only try running R to translate files if there are some files present.
-# The Makefile passes in the names of files.
-
-if [ $# -eq 2 ] ; then
- Rscript -e "source('bin/generate_md_episodes.R')" "$@"
-fi
diff --git a/bin/lesson_check.py b/bin/lesson_check.py
deleted file mode 100644
index 86e4249..0000000
--- a/bin/lesson_check.py
+++ /dev/null
@@ -1,628 +0,0 @@
-"""
-Check lesson files and their contents.
-"""
-
-
-import os
-import glob
-import re
-import sys
-from argparse import ArgumentParser
-
-# 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'
-
-# Where to look for source Markdown files.
-SOURCE_DIRS = ['', '_episodes', '_extras']
-
-# Where to look for source Rmd files.
-SOURCE_RMD_DIRS = ['_episodes_rmd']
-
-# Required files: each entry is ('path': YAML_required).
-# FIXME: We do not yet validate whether any files have the required
-# YAML headers, but should in the future.
-# The '%' is replaced with the source directory path for checking.
-# Episodes are handled specially, and extra files in '_extras' are also handled
-# 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,
- 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'(\d\d)-[-\w]+.md$')
-
-# Pattern to match lines ending with whitespace.
-P_TRAILING_WHITESPACE = re.compile(r'\s+$')
-
-# Pattern to match figure references in HTML.
-P_FIGURE_REFS = re.compile(r'
]+src="([^"]+)"[^>]*>')
-
-# Pattern to match internally-defined Markdown links.
-P_INTERNAL_LINK_REF = re.compile(r'\[([^\]]+)\]\[([^\]]+)\]')
-
-# Pattern to match reference links (to resolve internally-defined references).
-P_INTERNAL_LINK_DEF = re.compile(r'^\[([^\]]+)\]:\s*(.+)')
-
-# 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',
- 'keypoints',
- 'objectives',
- 'prereq',
- 'quotation',
- 'solution',
- 'testimonial',
- 'warning'
-}
-
-# What kinds of code fragments are allowed?
-# Below we allow all 'language-*' code blocks
-KNOWN_CODEBLOCKS = {
- 'error',
- 'output',
- 'source',
- 'warning'
-}
-
-# What fields are required in teaching episode metadata?
-TEACHING_METADATA_FIELDS = {
- ('title', str),
- ('teaching', int),
- ('exercises', int),
- ('questions', list),
- ('objectives', list),
- ('keypoints', list)
-}
-
-# What fields are required in break episode metadata?
-BREAK_METADATA_FIELDS = {
- ('layout', str),
- ('title', str),
- ('break', int)
-}
-
-# How long are lines allowed to be?
-# 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()
-
- 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)
- check_fileset(args.source_dir, args.reporter, list(docs.keys()))
- check_unwanted_files(args.source_dir, args.reporter)
- for filename in list(docs.keys()):
- checker = create_checker(args, filename, docs[filename])
- checker.check()
-
- args.reporter.report()
- 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():
- """Parse command-line arguments."""
-
- parser = ArgumentParser(description="""Check episode files in a lesson.""")
- parser.add_argument('-l', '--linelen',
- default=False,
- action="store_true",
- dest='line_lengths',
- help='Check line lengths')
- parser.add_argument('-p', '--parser',
- default=None,
- dest='parser',
- help='path to Markdown parser')
- parser.add_argument('-r', '--references',
- default=None,
- dest='reference_path',
- help='path to Markdown file of external references')
- parser.add_argument('-s', '--source',
- default=os.curdir,
- dest='source_dir',
- help='source directory')
- parser.add_argument('-w', '--whitespace',
- default=False,
- action="store_true",
- dest='trailing_whitespace',
- help='Check for trailing whitespace')
- parser.add_argument('--permissive',
- default=False,
- action="store_true",
- dest='permissive',
- help='Do not raise an error even if issues are detected')
-
- args, extras = parser.parse_known_args()
- require(args.parser is not None,
- 'Path to Markdown parser not provided',
- True)
- require(not extras,
- 'Unexpected trailing command-line arguments "{0}"'.format(extras))
-
- return args
-
-def check_config(reporter):
- """Check configuration file."""
-
- 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': ''}}
- ]:
- 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`"""
-
- episode_rmd_dir = [os.path.join(source_dir, d) for d in SOURCE_RMD_DIRS]
- episode_rmd_files = [os.path.join(d, '*.Rmd') for d in episode_rmd_dir]
- results = {}
- for pat in episode_rmd_files:
- for f in glob.glob(pat):
- data = read_markdown(parser, f)
- dy = data['metadata']
- if dy:
- reporter.check_field(f, 'episode_rmd',
- dy, 'source', 'Rmd')
-
-def read_references(reporter, ref_path):
- """Read shared file of reference links, returning dictionary of valid references
- {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', encoding='utf-8') as reader:
- for (num, line) in enumerate(reader, 1):
-
- # 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)
-
- message = '{}: {} not a valid reference: {}'
- require(m, message.format(ref_path, num, line.rstrip()))
-
- name = m.group(1)
- url = m.group(2)
-
- message = 'Empty reference at {0}:{1}'
- require(name, message.format(ref_path, num))
-
- unique_name = name not in result
- unique_url = url not in urls_seen
-
- reporter.check(unique_name,
- ref_path,
- 'Duplicate reference name {0} at line {1}',
- name, num)
-
- reporter.check(unique_url,
- ref_path,
- 'Duplicate definition of URL {0} at line {1}',
- url, num)
-
- result[name] = url
- urls_seen.add(url)
-
- return result
-
-
-def read_all_markdown(source_dir, parser):
- """Read source files, returning
- {path : {'metadata':yaml, 'metadata_len':N, 'text':text, 'lines':[(i, line, len)], 'doc':doc}}
- """
-
- all_dirs = [os.path.join(source_dir, d) for d in SOURCE_DIRS]
- all_patterns = [os.path.join(d, '*.md') for d in all_dirs]
- result = {}
- for pat in all_patterns:
- for filename in glob.glob(pat):
- data = read_markdown(parser, filename)
- if data:
- result[filename] = data
- return result
-
-
-def check_fileset(source_dir, reporter, filenames_present):
- """Are all required files present? Are extraneous files present?"""
-
- # Check files with predictable names.
- 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)
-
- # Check episode files' names.
- seen = []
- for filename in filenames_present:
- if '_episodes' not in filename:
- continue
-
- # 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:
- reporter.add(
- None, 'Episode {0} has badly-formatted filename', filename)
-
- # Check for duplicate episode numbers.
- reporter.check(len(seen) == len(set(seen)),
- None,
- 'Duplicate episode numbers {0} vs {1}',
- sorted(seen), sorted(set(seen)))
-
- # Check that numbers are consecutive.
- seen = sorted([int(s) for s in seen])
- clean = True
- for i in range(len(seen) - 1):
- clean = clean and ((seen[i+1] - seen[i]) == 1)
- reporter.check(clean,
- None,
- 'Missing or non-consecutive episode numbers {0}',
- seen)
-
-
-def create_checker(args, filename, info):
- """Create appropriate checker for file."""
-
- for (pat, cls) in CHECKERS:
- if pat.search(filename):
- return cls(args, filename, **info)
- return NotImplemented
-
-class CheckBase:
- """Base class for checking Markdown files."""
-
- def __init__(self, args, filename, metadata, metadata_len, text, lines, doc):
- """Cache arguments for checking."""
-
- self.args = args
- self.reporter = self.args.reporter # for convenience
- self.filename = filename
- self.metadata = metadata
- self.metadata_len = metadata_len
- self.text = text
- self.lines = lines
- self.doc = doc
-
- self.layout = None
-
- def check(self):
- """Run tests."""
-
- self.check_metadata()
- self.check_line_lengths()
- self.check_trailing_whitespace()
- self.check_blockquote_classes()
- self.check_codeblock_classes()
- self.check_defined_link_references()
-
- def check_metadata(self):
- """Check the YAML metadata."""
-
- self.reporter.check(self.metadata is not None,
- self.filename,
- 'Missing metadata entirely')
-
- if self.metadata and (self.layout is not None):
- self.reporter.check_field(
- self.filename, 'metadata', self.metadata, 'layout', self.layout)
-
- def check_line_lengths(self):
- """Check the raw text of the lesson body."""
-
- if self.args.line_lengths:
- 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_limit]))
-
- def check_trailing_whitespace(self):
- """Check for whitespace at the ends of lines."""
-
- if self.args.trailing_whitespace:
- trailing = [
- i for (i, l, n) in self.lines if P_TRAILING_WHITESPACE.match(l)]
- self.reporter.check(not trailing,
- self.filename,
- 'Line(s) end with whitespace: {0}',
- ', '.join([str(i) for i in trailing]))
-
- def check_blockquote_classes(self):
- """Check that all blockquotes have known classes."""
-
- for node in self.find_all(self.doc, {'type': 'blockquote'}):
- cls = self.get_val(node, 'attr', 'class')
- self.reporter.check(cls in KNOWN_BLOCKQUOTES,
- (self.filename, self.get_loc(node)),
- 'Unknown or missing blockquote type {0}',
- cls)
-
- def check_codeblock_classes(self):
- """Check that all code blocks have known classes."""
-
- for node in self.find_all(self.doc, {'type': 'codeblock'}):
- cls = self.get_val(node, 'attr', 'class')
- 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)
-
- def check_defined_link_references(self):
- """Check that defined links resolve in the file.
-
- Internally-defined links match the pattern [text][label].
- """
-
- result = set()
- for node in self.find_all(self.doc, {'type': 'text'}):
- for match in P_INTERNAL_LINK_REF.findall(node['value']):
- text = match[0]
- link = match[1]
- if link not in self.args.references:
- result.add('"{0}"=>"{1}"'.format(text, link))
- self.reporter.check(not result,
- self.filename,
- 'Internally-defined links may be missing definitions: {0}',
- ', '.join(sorted(result)))
-
- def find_all(self, node, pattern, accum=None):
- """Find all matches for a pattern."""
-
- assert isinstance(pattern, dict), 'Patterns must be dictionaries'
- if accum is None:
- accum = []
- if self.match(node, pattern):
- accum.append(node)
- for child in node.get('children', []):
- self.find_all(child, pattern, accum)
- return accum
-
- def match(self, node, pattern):
- """Does this node match the given pattern?"""
-
- for key in pattern:
- if key not in node:
- return False
- val = pattern[key]
- if isinstance(val, str):
- if node[key] != val:
- return False
- elif isinstance(val, dict):
- if not self.match(node[key], val):
- return False
- return True
-
- @staticmethod
- def get_val(node, *chain):
- """Get value one or more levels down."""
-
- curr = node
- for selector in chain:
- curr = curr.get(selector, None)
- if curr is None:
- break
- return curr
-
- def get_loc(self, node):
- """Convenience method to get node's line number."""
-
- result = self.get_val(node, 'options', 'location')
- if self.metadata_len is not None:
- result += self.metadata_len
- return result
-
-
-class CheckNonJekyll(CheckBase):
- """Check a file that isn't translated by Jekyll."""
-
- def check_metadata(self):
- self.reporter.check(self.metadata is None,
- self.filename,
- 'Unexpected metadata')
-
-
-class CheckIndex(CheckBase):
- """Check the main index page."""
-
- def __init__(self, args, filename, metadata, metadata_len, text, lines, doc):
- super().__init__(args, filename, metadata, metadata_len, text, lines, doc)
- self.layout = 'lesson'
-
- def check_metadata(self):
- super().check_metadata()
- self.reporter.check(self.metadata.get('root', '') == '.',
- self.filename,
- 'Root not set to "."')
-
-
-class CheckEpisode(CheckBase):
- """Check an episode page."""
-
- def check(self):
- """Run extra tests."""
-
- super().check()
- self.check_reference_inclusion()
-
- def check_metadata(self):
- super().check_metadata()
- if self.metadata:
- if 'layout' in self.metadata:
- if self.metadata['layout'] == 'break':
- self.check_metadata_fields(BREAK_METADATA_FIELDS)
- else:
- self.reporter.add(self.filename,
- 'Unknown episode layout "{0}"',
- self.metadata['layout'])
- else:
- self.check_metadata_fields(TEACHING_METADATA_FIELDS)
-
- def check_metadata_fields(self, expected):
- """Check metadata fields."""
- for (name, type_) in expected:
- if name not in self.metadata:
- self.reporter.add(self.filename,
- 'Missing metadata field {0}',
- name)
- elif not isinstance(self.metadata[name], type_):
- self.reporter.add(self.filename,
- '"{0}" has wrong type in metadata ({1} instead of {2})',
- name, type(self.metadata[name]), type_)
-
- 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
-
- for (i, last_line, line_len) in reversed(self.lines):
- if last_line:
- break
-
- require(last_line,
- 'No non-empty lines in {0}'.format(self.filename))
-
- include_filename = os.path.split(self.args.reference_path)[-1]
- if include_filename not in last_line:
- self.reporter.add(self.filename,
- 'episode does not include "{0}"',
- include_filename)
-
-
-class CheckReference(CheckBase):
- """Check the reference page."""
-
- def __init__(self, args, filename, metadata, metadata_len, text, lines, doc):
- super().__init__(args, filename, metadata, metadata_len, text, lines, doc)
- self.layout = 'reference'
-
-
-class CheckGeneric(CheckBase):
- """Check a generic page."""
-
- def __init__(self, args, filename, metadata, metadata_len, text, lines, doc):
- super().__init__(args, filename, metadata, metadata_len, text, lines, doc)
-
-
-CHECKERS = [
- (re.compile(r'CONTRIBUTING\.md'), CheckNonJekyll),
- (re.compile(r'README\.md'), CheckNonJekyll),
- (re.compile(r'index\.md'), CheckIndex),
- (re.compile(r'reference\.md'), CheckReference),
- # '.' 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)
-]
-
-
-if __name__ == '__main__':
- main()
diff --git a/bin/lesson_initialize.py b/bin/lesson_initialize.py
deleted file mode 100644
index 79ec05c..0000000
--- a/bin/lesson_initialize.py
+++ /dev/null
@@ -1,47 +0,0 @@
-"""Initialize a newly-created repository."""
-
-
-import sys
-import os
-import shutil
-
-BOILERPLATE = (
- 'AUTHORS',
- 'CITATION',
- 'CONTRIBUTING.md',
- 'README.md',
- '_config.yml',
- 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',
-)
-
-
-def main():
- """Check for collisions, then create."""
-
- # Check.
- errors = False
- for path in BOILERPLATE:
- if os.path.exists(path):
- print('Warning: {0} already exists.'.format(path), file=sys.stderr)
- errors = True
- if errors:
- print('**Exiting without creating files.**', file=sys.stderr)
- sys.exit(1)
-
- # Create.
- for path in BOILERPLATE:
- shutil.copyfile(
- os.path.join('bin', 'boilerplate', path),
- path
- )
-
-
-if __name__ == '__main__':
- main()
diff --git a/bin/markdown_ast.rb b/bin/markdown_ast.rb
deleted file mode 100755
index 2ef3f77..0000000
--- a/bin/markdown_ast.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/usr/bin/env ruby
-# frozen_string_literal: true
-
-# Use Kramdown parser to produce AST for Markdown document.
-
-require 'kramdown'
-require 'kramdown-parser-gfm'
-require 'json'
-
-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
deleted file mode 100644
index 6988ca5..0000000
--- a/bin/repo_check.py
+++ /dev/null
@@ -1,181 +0,0 @@
-"""
-Check repository settings.
-"""
-
-
-import sys
-import os
-from subprocess import Popen, PIPE
-import re
-from argparse import ArgumentParser
-
-from util import require
-from reporter import Reporter
-
-# Import this way to produce a more useful error message.
-try:
- import requests
-except ImportError:
- print('Unable to import requests module: please install requests', file=sys.stderr)
- sys.exit(1)
-
-
-# Pattern to match Git command-line output for remotes => (user name, project name).
-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}/'
-
-# Pattern to match repository URLs => (user name, project name)
-P_REPO_URL = re.compile(r'https?://github\.com/([^.]+)/([^/]+)/?')
-
-# API URL format string.
-F_API_URL = 'https://api.github.com/repos/{0}/{1}/labels'
-
-# Expected labels and colors.
-EXPECTED = {
- 'help wanted': 'dcecc7',
- 'status:in progress': '9bcc65',
- 'status:changes requested': '679f38',
- 'status:wait': 'fff2df',
- 'status:refer to cac': 'ffdfb2',
- 'status:need more info': 'ee6c00',
- 'status:blocked': 'e55100',
- 'status:out of scope': 'eeeeee',
- 'status:duplicate': 'bdbdbd',
- 'type:typo text': 'f8bad0',
- 'type:bug': 'eb3f79',
- 'type:formatting': 'ac1357',
- 'type:template and tools': '7985cb',
- 'type:instructor guide': '00887a',
- 'type:discussion': 'b2e5fc',
- 'type:enhancement': '7fdeea',
- 'type:clarification': '00acc0',
- 'type:teaching example': 'ced8dc',
- 'good first issue': 'ffeb3a',
- 'high priority': 'd22e2e'
-}
-
-
-def main():
- """
- Main driver.
- """
-
- args = parse_args()
- reporter = Reporter()
- repo_url = get_repo_url(args.repo_url)
- check_labels(reporter, repo_url)
- reporter.report()
-
-
-def parse_args():
- """
- Parse command-line arguments.
- """
-
- parser = ArgumentParser(description="""Check repository settings.""")
- parser.add_argument('-r', '--repo',
- default=None,
- dest='repo_url',
- help='repository URL')
- parser.add_argument('-s', '--source',
- default=os.curdir,
- dest='source_dir',
- help='source directory')
-
- args, extras = parser.parse_known_args()
- require(not extras,
- 'Unexpected trailing command-line arguments "{0}"'.format(extras))
-
- return args
-
-
-def get_repo_url(repo_url):
- """
- Figure out which repository to query.
- """
-
- # Explicitly specified.
- if repo_url is not None:
- return repo_url
-
- # Guess.
- cmd = 'git remote -v'
- p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE,
- 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]
- matches = [m for m in matches if m is not None]
- require(len(matches) == 1,
- 'Unexpected output from git remote command: "{0}"'.format(matches))
-
- username = matches[0].group(1)
- require(
- username, 'empty username in git remote output {0}'.format(matches[0]))
-
- project_name = matches[0].group(2)
- require(
- username, 'empty project name in git remote output {0}'.format(matches[0]))
-
- url = F_REPO_URL.format(username, project_name)
- return url
-
-
-def check_labels(reporter, repo_url):
- """
- Check labels in repository.
- """
-
- actual = get_labels(repo_url)
- extra = set(actual.keys()) - set(EXPECTED.keys())
-
- reporter.check(not extra,
- None,
- 'Extra label(s) in repository {0}: {1}',
- repo_url, ', '.join(sorted(extra)))
-
- missing = set(EXPECTED.keys()) - set(actual.keys())
- reporter.check(not missing,
- None,
- 'Missing label(s) in repository {0}: {1}',
- repo_url, ', '.join(sorted(missing)))
-
- overlap = set(EXPECTED.keys()).intersection(set(actual.keys()))
- for name in sorted(overlap):
- reporter.check(EXPECTED[name].lower() == actual[name].lower(),
- None,
- 'Color mis-match for label {0} in {1}: expected {2}, found {3}',
- name, repo_url, EXPECTED[name], actual[name])
-
-
-def get_labels(repo_url):
- """
- Get actual labels from repository.
- """
-
- m = P_REPO_URL.match(repo_url)
- require(
- m, 'repository URL {0} does not match expected pattern'.format(repo_url))
-
- username = m.group(1)
- require(username, 'empty username in repository URL {0}'.format(repo_url))
-
- project_name = m.group(2)
- require(
- username, 'empty project name in repository URL {0}'.format(repo_url))
-
- url = F_API_URL.format(username, project_name)
- r = requests.get(url)
- require(r.status_code == 200,
- 'Request for {0} failed with {1}'.format(url, r.status_code))
-
- result = {}
- for entry in r.json():
- result[entry['name']] = entry['color']
- return result
-
-
-if __name__ == '__main__':
- main()
diff --git a/bin/reporter.py b/bin/reporter.py
deleted file mode 100644
index 550dbf0..0000000
--- a/bin/reporter.py
+++ /dev/null
@@ -1,75 +0,0 @@
-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
deleted file mode 100755
index 1e09178..0000000
--- a/bin/run-make-docker-serve.sh
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/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
deleted file mode 100644
index 7a6d603..0000000
--- a/bin/test_lesson_check.py
+++ /dev/null
@@ -1,19 +0,0 @@
-import unittest
-
-import lesson_check
-import reporter
-
-
-class TestFileList(unittest.TestCase):
- def setUp(self):
- 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
-
- lesson_check.check_fileset('', self.reporter, lesson_check.REQUIRED_FILES)
- self.assertEqual(len(self.reporter.messages), 0)
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/bin/util.py b/bin/util.py
deleted file mode 100644
index 1398c37..0000000
--- a/bin/util.py
+++ /dev/null
@@ -1,111 +0,0 @@
-import sys
-import os
-import json
-from subprocess import Popen, PIPE
-
-# Import this way to produce a more useful error message.
-try:
- import yaml
-except ImportError:
- print('Unable to import YAML module: please install PyYAML', file=sys.stderr)
- sys.exit(1)
-
-__all__ = ['check_unwanted_files', 'load_yaml', 'read_markdown', 'require']
-
-# Files that shouldn't be present.
-UNWANTED_FILES = [
- '.nojekyll'
-]
-
-def read_markdown(parser, path):
- """
- Get YAML and AST for Markdown file, returning
- {'metadata':yaml, 'metadata_len':N, 'text':text, 'lines':[(i, line, len)], 'doc':doc}.
- """
-
- # Split and extract YAML (if present).
- with open(path, 'r', encoding='utf-8') as reader:
- body = reader.read()
- metadata_raw, metadata_yaml, body = split_metadata(path, body)
-
- # Split into lines.
- metadata_len = 0 if metadata_raw is None else metadata_raw.count('\n')
- lines = [(metadata_len+i+1, line, len(line))
- for (i, line) in enumerate(body.split('\n'))]
-
- # Parse Markdown.
- cmd = 'bundle exec ruby {0}'.format(parser)
- p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE,
- close_fds=True, universal_newlines=True, encoding='utf-8')
- stdout_data, stderr_data = p.communicate(body)
- doc = json.loads(stdout_data)
-
- return {
- 'metadata': metadata_yaml,
- 'metadata_len': metadata_len,
- 'text': body,
- 'lines': lines,
- 'doc': doc
- }
-
-
-def split_metadata(path, text):
- """
- Get raw (text) metadata, metadata as YAML, and rest of body.
- If no metadata, return (None, None, body).
- """
-
- metadata_raw = None
- metadata_yaml = None
-
- pieces = text.split('---', 2)
- if len(pieces) == 3:
- metadata_raw = pieces[1]
- text = pieces[2]
- try:
- metadata_yaml = yaml.load(metadata_raw, Loader=yaml.SafeLoader)
- except yaml.YAMLError as e:
- message = 'Unable to parse YAML header in {0}:\n{1}'
- print(message.format(path, e), file=sys.stderr)
-
- return metadata_raw, metadata_yaml, text
-
-
-def load_yaml(filename):
- """
- Wrapper around YAML loading so that 'import yaml' is only needed
- in one file.
- """
-
- try:
- 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):
- """
- Check that unwanted files are not present.
- """
-
- for filename in UNWANTED_FILES:
- path = os.path.join(dir_path, filename)
- reporter.check(not os.path.exists(path),
- path,
- "Unwanted file found")
-
-
-def require(condition, message, fatal=False):
- """Fail if condition not met."""
-
- if not condition:
- print(message, file=sys.stderr)
-
- if fatal:
- sys.exit(1)
diff --git a/bin/workshop_check.py b/bin/workshop_check.py
deleted file mode 100644
index 312b1a1..0000000
--- a/bin/workshop_check.py
+++ /dev/null
@@ -1,419 +0,0 @@
-'''Check that a workshop's index.html metadata is valid. See the
-docstrings on the checking functions for a summary of the checks.
-'''
-
-
-import sys
-import os
-import re
-from datetime import date
-from util import split_metadata, load_yaml, check_unwanted_files
-from reporter import Reporter
-
-# Metadata field patterns.
-EMAIL_PATTERN = r'[^@]+@[^@]+\.[^@]+'
-HUMANTIME_PATTERN = r'((0?[1-9]|1[0-2]):[0-5]\d(am|pm)(-|to)(0?[1-9]|1[0-2]):[0-5]\d(am|pm))|((0?\d|1\d|2[0-3]):[0-5]\d(-|to)(0?\d|1\d|2[0-3]):[0-5]\d)'
-EVENTBRITE_PATTERN = r'\d{9,10}'
-URL_PATTERN = r'https?://.+'
-
-# Defaults.
-CARPENTRIES = ("dc", "swc", "lc", "cp")
-DEFAULT_CONTACT_EMAIL = 'team@carpentries.org'
-
-USAGE = 'Usage: "workshop_check.py path/to/root/directory"'
-
-# Country and language codes. Note that codes mean different things: 'ar'
-# is 'Arabic' as a language but 'Argentina' as a country.
-
-ISO_COUNTRY = [
- 'ad', 'ae', 'af', 'ag', 'ai', 'al', 'am', 'an', 'ao', 'aq', 'ar', 'as',
- 'at', 'au', 'aw', 'ax', 'az', 'ba', 'bb', 'bd', 'be', 'bf', 'bg', 'bh',
- 'bi', 'bj', 'bm', 'bn', 'bo', 'br', 'bs', 'bt', 'bv', 'bw', 'by', 'bz',
- 'ca', 'cc', 'cd', 'cf', 'cg', 'ch', 'ci', 'ck', 'cl', 'cm', 'cn', 'co',
- 'cr', 'cu', 'cv', 'cx', 'cy', 'cz', 'de', 'dj', 'dk', 'dm', 'do', 'dz',
- 'ec', 'ee', 'eg', 'eh', 'er', 'es', 'et', 'eu', 'fi', 'fj', 'fk', 'fm',
- 'fo', 'fr', 'ga', 'gb', 'gd', 'ge', 'gf', 'gg', 'gh', 'gi', 'gl', 'gm',
- 'gn', 'gp', 'gq', 'gr', 'gs', 'gt', 'gu', 'gw', 'gy', 'hk', 'hm', 'hn',
- 'hr', 'ht', 'hu', 'id', 'ie', 'il', 'im', 'in', 'io', 'iq', 'ir', 'is',
- 'it', 'je', 'jm', 'jo', 'jp', 'ke', 'kg', 'kh', 'ki', 'km', 'kn', 'kp',
- 'kr', 'kw', 'ky', 'kz', 'la', 'lb', 'lc', 'li', 'lk', 'lr', 'ls', 'lt',
- 'lu', 'lv', 'ly', 'ma', 'mc', 'md', 'me', 'mg', 'mh', 'mk', 'ml', 'mm',
- 'mn', 'mo', 'mp', 'mq', 'mr', 'ms', 'mt', 'mu', 'mv', 'mw', 'mx', 'my',
- 'mz', 'na', 'nc', 'ne', 'nf', 'ng', 'ni', 'nl', 'no', 'np', 'nr', 'nu',
- 'nz', 'om', 'pa', 'pe', 'pf', 'pg', 'ph', 'pk', 'pl', 'pm', 'pn', 'pr',
- 'ps', 'pt', 'pw', 'py', 'qa', 're', 'ro', 'rs', 'ru', 'rw', 'sa', 'sb',
- 'sc', 'sd', 'se', 'sg', 'sh', 'si', 'sj', 'sk', 'sl', 'sm', 'sn', 'so',
- 'sr', 'st', 'sv', 'sy', 'sz', 'tc', 'td', 'tf', 'tg', 'th', 'tj', 'tk',
- 'tl', 'tm', 'tn', 'to', 'tr', 'tt', 'tv', 'tw', 'tz', 'ua', 'ug', 'um',
- 'us', 'uy', 'uz', 'va', 'vc', 've', 'vg', 'vi', 'vn', 'vu', 'wf', 'ws',
- 'ye', 'yt', 'za', 'zm', 'zw'
-]
-
-ISO_LANGUAGE = [
- 'aa', 'ab', 'ae', 'af', 'ak', 'am', 'an', 'ar', 'as', 'av', 'ay', 'az',
- 'ba', 'be', 'bg', 'bh', 'bi', 'bm', 'bn', 'bo', 'br', 'bs', 'ca', 'ce',
- 'ch', 'co', 'cr', 'cs', 'cu', 'cv', 'cy', 'da', 'de', 'dv', 'dz', 'ee',
- 'el', 'en', 'eo', 'es', 'et', 'eu', 'fa', 'ff', 'fi', 'fj', 'fo', 'fr',
- 'fy', 'ga', 'gd', 'gl', 'gn', 'gu', 'gv', 'ha', 'he', 'hi', 'ho', 'hr',
- 'ht', 'hu', 'hy', 'hz', 'ia', 'id', 'ie', 'ig', 'ii', 'ik', 'io', 'is',
- 'it', 'iu', 'ja', 'jv', 'ka', 'kg', 'ki', 'kj', 'kk', 'kl', 'km', 'kn',
- 'ko', 'kr', 'ks', 'ku', 'kv', 'kw', 'ky', 'la', 'lb', 'lg', 'li', 'ln',
- 'lo', 'lt', 'lu', 'lv', 'mg', 'mh', 'mi', 'mk', 'ml', 'mn', 'mr', 'ms',
- 'mt', 'my', 'na', 'nb', 'nd', 'ne', 'ng', 'nl', 'nn', 'no', 'nr', 'nv',
- 'ny', 'oc', 'oj', 'om', 'or', 'os', 'pa', 'pi', 'pl', 'ps', 'pt', 'qu',
- 'rm', 'rn', 'ro', 'ru', 'rw', 'sa', 'sc', 'sd', 'se', 'sg', 'si', 'sk',
- 'sl', 'sm', 'sn', 'so', 'sq', 'sr', 'ss', 'st', 'su', 'sv', 'sw', 'ta',
- 'te', 'tg', 'th', 'ti', 'tk', 'tl', 'tn', 'to', 'tr', 'ts', 'tt', 'tw',
- 'ty', 'ug', 'uk', 'ur', 'uz', 've', 'vi', 'vo', 'wa', 'wo', 'xh', 'yi',
- 'yo', 'za', 'zh', 'zu'
-]
-
-
-def look_for_fixme(func):
- """Decorator to fail test if text argument starts with "FIXME"."""
-
- def inner(arg):
- if (arg is not None) and \
- isinstance(arg, str) and \
- arg.lstrip().startswith('FIXME'):
- return False
- return func(arg)
- return inner
-
-
-@look_for_fixme
-def check_layout(layout):
- '''"layout" in YAML header must be "workshop".'''
-
- return layout == 'workshop'
-
-
-@look_for_fixme
-def check_carpentry(layout):
- '''"carpentry" in YAML header must be "dc", "swc", "lc", or "cp".'''
-
- return layout in CARPENTRIES
-
-
-@look_for_fixme
-def check_country(country):
- '''"country" must be a lowercase ISO-3166 two-letter code.'''
-
- return country in ISO_COUNTRY
-
-
-@look_for_fixme
-def check_language(language):
- '''"language" must be a lowercase ISO-639 two-letter code.'''
-
- return language in ISO_LANGUAGE
-
-
-@look_for_fixme
-def check_humandate(date):
- """
- 'humandate' must be a human-readable date with a 3-letter month
- and 4-digit year. Examples include 'Feb 18-20, 2025' and 'Feb 18
- and 20, 2025'. It may be in languages other than English, but the
- month name should be kept short to aid formatting of the main
- Carpentries web site.
- """
-
- if ',' not in date:
- return False
-
- month_dates, year = date.split(',')
-
- # The first three characters of month_dates are not empty
- month = month_dates[:3]
- if any(char == ' ' for char in month):
- return False
-
- # But the fourth character is empty ("February" is illegal)
- if month_dates[3] != ' ':
- return False
-
- # year contains *only* numbers
- try:
- int(year)
- except:
- return False
-
- return True
-
-
-@look_for_fixme
-def check_humantime(time):
- """
- 'humantime' is a human-readable start and end time for the
- workshop, such as '09:00 - 16:00'.
- """
-
- return bool(re.match(HUMANTIME_PATTERN, time.replace(' ', '')))
-
-
-def check_date(this_date):
- """
- 'startdate' and 'enddate' are machine-readable start and end dates
- for the workshop, and must be in YYYY-MM-DD format, e.g.,
- '2015-07-01'.
- """
-
- # YAML automatically loads valid dates as datetime.date.
- return isinstance(this_date, date)
-
-
-@look_for_fixme
-def check_latitude_longitude(latlng):
- """
- 'latlng' must be a valid latitude and longitude represented as two
- floating-point numbers separated by a comma.
- """
-
- try:
- lat, lng = latlng.split(',')
- lat = float(lat)
- lng = float(lng)
- return (-90.0 <= lat <= 90.0) and (-180.0 <= lng <= 180.0)
- except ValueError:
- return False
-
-
-def check_instructors(instructors):
- """
- 'instructor' must be a non-empty comma-separated list of quoted
- names, e.g. ['First name', 'Second name', ...']. Do not use 'TBD'
- or other placeholders.
- """
-
- # YAML automatically loads list-like strings as lists.
- return isinstance(instructors, list) and len(instructors) > 0
-
-
-def check_helpers(helpers):
- """
- 'helper' must be a comma-separated list of quoted names,
- e.g. ['First name', 'Second name', ...']. The list may be empty.
- Do not use 'TBD' or other placeholders.
- """
-
- # YAML automatically loads list-like strings as lists.
- return isinstance(helpers, list) and len(helpers) >= 0
-
-
-@look_for_fixme
-def check_emails(emails):
- """
- 'emails' must be a comma-separated list of valid email addresses.
- The list may be empty. A valid email address consists of characters,
- an '@', and more characters. It should not contain the default contact
- """
-
- # YAML automatically loads list-like strings as lists.
- if (isinstance(emails, list) and len(emails) >= 0):
- for email in emails:
- if ((not bool(re.match(EMAIL_PATTERN, email))) or (email == DEFAULT_CONTACT_EMAIL)):
- return False
- else:
- return False
-
- return True
-
-
-def check_eventbrite(eventbrite):
- """
- 'eventbrite' (the Eventbrite registration key) must be 9 or more
- digits. It may appear as an integer or as a string.
- """
-
- if isinstance(eventbrite, int):
- return True
- else:
- return bool(re.match(EVENTBRITE_PATTERN, eventbrite))
-
-
-@look_for_fixme
-def check_collaborative_notes(collaborative_notes):
- """
- 'collaborative_notes' must be a valid URL.
- """
-
- return bool(re.match(URL_PATTERN, collaborative_notes))
-
-
-@look_for_fixme
-def check_pass(value):
- """
- This test always passes (it is used for 'checking' things like the
- workshop address, for which no sensible validation is feasible).
- """
-
- return True
-
-
-HANDLERS = {
- 'layout': (True, check_layout, 'layout isn\'t "workshop"'),
-
- 'carpentry': (True, check_carpentry, 'carpentry isn\'t in ' +
- ', '.join(CARPENTRIES)),
-
- 'country': (True, check_country,
- 'country invalid: must use lowercase two-letter ISO code ' +
- 'from ' + ', '.join(ISO_COUNTRY)),
-
- 'language': (False, check_language,
- 'language invalid: must use lowercase two-letter ISO code' +
- ' from ' + ', '.join(ISO_LANGUAGE)),
-
- 'humandate': (True, check_humandate,
- 'humandate invalid. Please use three-letter months like ' +
- '"Jan" and four-letter years like "2025"'),
-
- 'humantime': (True, check_humantime,
- 'humantime doesn\'t include numbers'),
-
- 'startdate': (True, check_date,
- 'startdate invalid. Must be of format year-month-day, ' +
- 'i.e., 2014-01-31'),
-
- 'enddate': (False, check_date,
- 'enddate invalid. Must be of format year-month-day, i.e.,' +
- ' 2014-01-31'),
-
- 'latlng': (True, check_latitude_longitude,
- 'latlng invalid. Check that it is two floating point ' +
- 'numbers, separated by a comma'),
-
- 'instructor': (True, check_instructors,
- 'instructor list isn\'t a valid list of format ' +
- '["First instructor", "Second instructor",..]'),
-
- 'helper': (True, check_helpers,
- 'helper list isn\'t a valid list of format ' +
- '["First helper", "Second helper",..]'),
-
- 'email': (True, check_emails,
- 'contact email list isn\'t a valid list of format ' +
- '["me@example.org", "you@example.org",..] or contains incorrectly formatted email addresses or ' +
- '"{0}".'.format(DEFAULT_CONTACT_EMAIL)),
-
- 'eventbrite': (False, check_eventbrite, 'Eventbrite key appears invalid'),
-
- 'collaborative_notes': (False, check_collaborative_notes, 'Collaborative Notes URL appears invalid'),
-
- 'venue': (False, check_pass, 'venue name not specified'),
-
- 'address': (False, check_pass, 'address not specified')
-}
-
-# REQUIRED is all required categories.
-REQUIRED = {k for k in HANDLERS if HANDLERS[k][0]}
-
-# OPTIONAL is all optional categories.
-OPTIONAL = {k for k in HANDLERS if not HANDLERS[k][0]}
-
-
-def check_blank_lines(reporter, raw):
- """
- Blank lines are not allowed in category headers.
- """
-
- lines = [(i, x) for (i, x) in enumerate(
- raw.strip().split('\n')) if not x.strip()]
- reporter.check(not lines,
- None,
- 'Blank line(s) in header: {0}',
- ', '.join(["{0}: {1}".format(i, x.rstrip()) for (i, x) in lines]))
-
-
-def check_categories(reporter, left, right, msg):
- """
- Report differences (if any) between two sets of categories.
- """
-
- diff = left - right
- reporter.check(len(diff) == 0,
- None,
- '{0}: offending entries {1}',
- msg, sorted(list(diff)))
-
-
-def check_file(reporter, path, data):
- """
- Get header from file, call all other functions, and check file for
- validity.
- """
-
- # Get metadata as text and as YAML.
- raw, header, body = split_metadata(path, data)
-
- # Do we have any blank lines in the header?
- check_blank_lines(reporter, raw)
-
- # Look through all header entries. If the category is in the input
- # file and is either required or we have actual data (as opposed to
- # a commented-out entry), we check it. If it *isn't* in the header
- # but is required, report an error.
- for category in HANDLERS:
- required, handler, message = HANDLERS[category]
- if category in header:
- if required or header[category]:
- reporter.check(handler(header[category]),
- None,
- '{0}\n actual value "{1}"',
- message, header[category])
- elif required:
- reporter.add(None,
- 'Missing mandatory key "{0}"',
- category)
-
- # Check whether we have missing or too many categories
- seen_categories = set(header.keys())
- check_categories(reporter, REQUIRED, seen_categories,
- 'Missing categories')
- check_categories(reporter, seen_categories, REQUIRED.union(OPTIONAL),
- 'Superfluous categories')
-
-
-def check_config(reporter, filename):
- """
- Check YAML configuration file.
- """
-
- config = load_yaml(filename)
-
- kind = config.get('kind', None)
- reporter.check(kind == 'workshop',
- filename,
- 'Missing or unknown kind of event: {0}',
- kind)
-
- carpentry = config.get('carpentry', None)
- reporter.check(carpentry in ('swc', 'dc', 'lc', 'cp'),
- filename,
- 'Missing or unknown carpentry: {0}',
- carpentry)
-
-
-def main():
- '''Run as the main program.'''
-
- if len(sys.argv) != 2:
- print(USAGE, file=sys.stderr)
- sys.exit(1)
-
- root_dir = sys.argv[1]
- index_file = os.path.join(root_dir, 'index.html')
- config_file = os.path.join(root_dir, '_config.yml')
-
- reporter = Reporter()
- check_config(reporter, config_file)
- check_unwanted_files(root_dir, reporter)
- with open(index_file, encoding='utf-8') as reader:
- data = reader.read()
- check_file(reporter, index_file, data)
- reporter.report()
-
-
-if __name__ == '__main__':
- main()
diff --git a/code/.gitkeep b/code/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/config.yaml b/config.yaml
new file mode 100644
index 0000000..13339f1
--- /dev/null
+++ b/config.yaml
@@ -0,0 +1,76 @@
+#------------------------------------------------------------
+# Values for this lesson.
+#------------------------------------------------------------
+
+# Which carpentry is this (swc, dc, lc, or cp)?
+# swc: Software Carpentry
+# dc: Data Carpentry
+# lc: Library Carpentry
+# cp: Carpentries (to use for instructor training for instance)
+# incubator: The Carpentries Incubator
+#
+# This option supports custom types so lessons can be branded
+# and themed with your own logo and alt-text (see `carpentry_description`)
+# See https://carpentries.github.io/sandpaper-docs/editing.html#adding-a-custom-logo
+carpentry: 'incubator'
+
+# Alt-text description of the lesson.
+carpentry_description: 'EIC Tutorials'
+
+# Overall title for pages.
+title: 'Setting Up Your EIC Environment'
+
+# Date the lesson was created (YYYY-MM-DD, this is empty by default)
+created: 2021-01-01
+
+# Comma-separated list of keywords for the lesson
+keywords: 'EIC, ePIC, eic-shell, container, singularity, apptainer, docker, GitHub, environment setup'
+
+# Life cycle stage of the lesson
+# possible values: pre-alpha, alpha, beta, stable
+life_cycle: 'stable'
+
+# License of the lesson
+license: 'CC-BY 4.0'
+
+# Link to the source repository for this lesson
+source: 'https://github.com/eic/tutorial-setting-up-environment'
+
+# Default branch of your lesson
+branch: 'main'
+
+# Who to contact if there are any issues
+contact: 'wouter.deconinck@umanitoba.ca'
+
+# Navigation ------------------------------------------------
+#
+# Use the following menu items to specify the order of
+# individual pages in each dropdown section. Leave blank to
+# include all pages in the folder.
+
+# Order of episodes in your lesson
+episodes:
+- 01-introduction.md
+- 02-eic-shell.md
+- 03-github.md
+
+# Information for Learners
+learners:
+- setup.md
+- reference.md
+- bnl-sdcc-quickstart.md
+- jlab-farm-quickstart.md
+- discuss.md
+
+# Information for Instructors
+instructors:
+- instructor-notes.md
+
+# Learner Profiles
+profiles:
+- learner-profiles.md
+
+# Customisation ---------------------------------------------
+#
+# This space below is where custom yaml items (e.g. pinning
+# sandpaper and varnish versions) should live
diff --git a/data/.gitkeep b/data/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/episodes/01-introduction.md b/episodes/01-introduction.md
new file mode 100644
index 0000000..452d2be
--- /dev/null
+++ b/episodes/01-introduction.md
@@ -0,0 +1,31 @@
+---
+title: "Introduction"
+teaching: 5
+exercises: 0
+---
+
+::::::::::::::::::::::::::::::::::::::::::::: questions
+
+- What is the EIC environment?
+
+:::::::::::::::::::::::::::::::::::::::::::::
+
+::::::::::::::::::::::::::::::::::::::::::::: objectives
+
+- Understand what we mean with the EIC environment, or `eic-shell`.
+- Appreciate the benefits of containers as the development environment.
+
+:::::::::::::::::::::::::::::::::::::::::::::
+
+In this episode we will cover two aspects of the EIC Software Environment:
+
+- the actual commands you can run to access the software components we rely on for simulations and analysis,
+- the services and repositories you will want to contribute to in order to work collaboratively.
+
+Much of the work we do at the EIC relies on both of these collaborative environments.
+
+::::::::::::::::::::::::::::::::::::::::::::: keypoints
+
+- The EIC environment `eic-shell` is a singularity/docker container with a curated selection of software components.
+
+:::::::::::::::::::::::::::::::::::::::::::::
diff --git a/_episodes/02-eic-shell.md b/episodes/02-eic-shell.md
similarity index 55%
rename from _episodes/02-eic-shell.md
rename to episodes/02-eic-shell.md
index b905f25..a318909 100644
--- a/_episodes/02-eic-shell.md
+++ b/episodes/02-eic-shell.md
@@ -2,15 +2,23 @@
title: "The EIC Software Environment"
teaching: 5
exercises: 5
-questions:
-- "What is the EIC environment?"
-objectives:
-- "Understand what we mean with the EIC environment, or `eic-shell`."
-- "Appreciate the benefits of containers as development environment."
-keypoints:
-- "The EIC environment `eic-shell` is a singularity/docker container with a curated selection of software components."
---
+
+::::::::::::::::::::::::::::::::::::::::::::: questions
+
+- What is the EIC environment?
+
+:::::::::::::::::::::::::::::::::::::::::::::
+
+::::::::::::::::::::::::::::::::::::::::::::: objectives
+
+- Understand what we mean with the EIC environment, or `eic-shell`.
+- Appreciate the benefits of containers as development environment.
+
+:::::::::::::::::::::::::::::::::::::::::::::
+
## Why a EIC standard environment?
+
- EIC software is modular, so not just a matter of a single `make install` command but close to 20 necessary dependencies and 10 EIC-specific packages.
- Laboratory operating systems are stable and (therefore) slow to upgrade to new versions, even though we need those versions in the modern software we write:
- gcc-11 or gcc-12 for C++17 ranges, C++20 for concepts and filtered ranges, and C++23 for zipped ranges
@@ -20,75 +28,135 @@ keypoints:
- If you encounter a bug that you can reproduce in a standard environment, it makes it much much easier for software developers to fix. Making it easier for developers to fix you bug will typically result in your bug getting fixed quicker. If developers cannot reproduce the bug, it often becomes a fishing expedition that few developers have appetite for.
## Are you required to use the EIC standard environment?
+
- No. You can use any environment you want. We are merely trying to make this easy for all users to get started, in particular if you are not yet familiar with compiling source code from scratch.
- Most of the people in the software groups use a mix of the standard environment and other installations (for example, we may have ROOT installed separately).
- We may ask you to reproduce issues that you observe in the standard environment before we can act on them.
- If there is a specific workflow or use case that you cannot achieve in the standard environment, let us know and we'll work with you to address that.
## How to get `eic-shell`, the EIC standard environment, and what it is?
+
- The EIC standard environment is accessed with the `eic-shell` command. That will start an `eic-shell` session in the standard environment.
- To install the `eic-shell` command in the directory `~/eic/` (which you may need to create), you can run the following command:
-```console
+
+```bash
cd ~/eic
curl --location https://get.epic-eic.org | bash
```
-- You can also save the file at https://get.epic-eic.org as `install.sh` and run this script by hand:
-```console
+
+- You can also save the file at as `install.sh` and run this script by hand:
+
+```bash
cd ~/eic
wget --output-document install.sh https://get.epic-eic.org
bash install.sh
```
+
- The install script will search for several components that are required (see the prerequisites):
- which operating system you are running (linux and mac are supported),
- whether you have singularity/apptainer or docker installed (see prerequisites),
- - whether you have access to the Cern VM file system at `/cvmfs` (typically available on clusters, and can be easily installed on individual linux systems, see https://cernvm.cern.ch/fs/).
+ - whether you have access to the Cern VM file system at `/cvmfs` (typically available on clusters, and can be easily installed on individual linux systems, see ).
- If you are on a system that requires downloading the full container (up to 13 GB), this may take a while and no progress bar is shown.
- The install script will create a new file `./eic-shell` which will start the `eic-shell` environment, i.e. the EIC standard environment.
-> Note: An alternative location for downloading this script is at https://eicweb.phy.anl.gov/containers/eic_container/-/raw/master/install.sh, in case you experience difficulties accessing the custom domain.
-{: .callout}
+::::::::::::::::::::::::::::::::::::::::::::: callout
+
+An alternative location for downloading this script is at
+, in case you
+experience difficulties accessing the custom domain.
+
+:::::::::::::::::::::::::::::::::::::::::::::
+
+::::::::::::::::::::::::::::::::::::::::::::: callout
+
+On some high performance computing servers, there are security restrictions that require the use of
+additional flags `curl --insecure` and `wget --no-check-certificate`.
+
+:::::::::::::::::::::::::::::::::::::::::::::
+
+::::::::::::::::::::::::::::::::::::::::::::: challenge
+
+## Exercise 1
+
+- Install the `eic-shell` executable in a suitable location on the system you will be using mainly for EIC work.
+- Look at the output and compare with what is on the instructor's screen. If you do not have a `/cvmfs` directory on your system, this may take a while as the environment has to be downloaded. Keep this in mind for later, as you will have to perform updates periodically.
+- Advanced users: Take a look at the installation options when you run `bash install.sh --help`. They include information on how to tweak your setup for specific use scenarios.
-> Note: On some high performance computing servers, there are security restrictions that require the use of additional flags `curl --insecure` and `wget --no-check-certificate`.
-{: .callout}
+::::::::::::::: solution
-> Exercise 1:
-> - Install the `eic-shell` executable in a suitable location on the system you will be using mainly for EIC work.
-> - Look at the output and compare with what is on the instructor's screen. If you do not have a `/cvmfs` directory on your system, this may take a while as the environment has to be downloaded. Keep this in mind for later, as you will have to perform updates periodically.
-> - Advanced users: Take a look at the installation options when you run `bash install.sh --help`. The include information on how to tweak your setup for specific use scenarios.
-{: .challenge}
+After running the installer you should have an `eic-shell` script in `~/eic/`. Running `./eic-shell`
+drops you into the container with a `jug_dev>` prompt.
+
+:::::::::::::::
+
+:::::::::::::::::::::::::::::::::::::::::::::
## How to start `eic-shell` on common systems?
+
You can start `eic-shell` from inside the directory where it is installed with
-```console
+
+```bash
cd ~/eic
./eic-shell
```
+
or you can start it from any directory with `~/eic/eic-shell` (if you used the same directory structure).
-- When you have successfully started `eic-shell` you should see a `jug_dev> ` prompt, indicating that you are now **inside** the EIC standard environment.
+
+- When you have successfully started `eic-shell` you should see a `jug_dev>` prompt, indicating that you are now **inside** the EIC standard environment.
- Pro-tip: You can also add the directory where `eic-shell` is installed to your search path, so you can start `eic-shell` from anywhere. Or you can install `eic-shell` in a directory that is already in your search path.
-> Exercise:
-> - Run the `eic-shell` script from the current directory. You should get the `jug_dev> ` prompt. Exit the environment again.
-> - Take a look at the running options with `eic-shell --help`. If you do not have a `/cvmfs` directory on your system, you will need to run `eic-shell --upgrade` periodically to ensure that you are staying up to date.
-> - Advanced users: Set up your environment to be able to run `eic-shell` from anywhere.
-{: .challenge}
+::::::::::::::::::::::::::::::::::::::::::::: challenge
+
+## Exercise 2
+
+- Run the `eic-shell` script from the current directory. You should get the `jug_dev>` prompt. Exit the environment again.
+- Take a look at the running options with `eic-shell --help`. If you do not have a `/cvmfs` directory on your system, you will need to run `eic-shell --upgrade` periodically to ensure that you are staying up to date.
+- Advanced users: Set up your environment to be able to run `eic-shell` from anywhere.
+
+::::::::::::::: solution
+
+Running `./eic-shell` gives you the `jug_dev>` prompt (you are now inside the container); type
+`exit` to leave. `eic-shell --help` lists options such as `--upgrade`, `--version`, and
+`--container`. To run from anywhere, add the install directory to your `PATH`.
+
+:::::::::::::::
+
+:::::::::::::::::::::::::::::::::::::::::::::
## What is where in the eic-shell environment?
+
When you start `eic-shell`, you enter into a container: a self-contained operating system that integrates with the core of the operating system on the host system. When you look at the top-level directories, you'll see that they are similar to the directories on the host system. Don't let that fool you: you are looking at content that is only inside the container.
There are a few directories that are shared between the container and the host operating system: the current directory you are working on, your home directories, etc. Essentially, the typical directories where you would store your own work (as opposed to system directories).
All programs that are installed in the container, are accessible under the `/opt/local` directory tree. This is a standard location for programs that are not installed by the operating system's package manager, which is how we install the dependencies for the EIC software stack. It also means that these programs are automatically found by the `eic-shell` without needing to modify the search path.
-The jumble of files in `/opt/local` are in fact merely a view into software installed in a more organized fashion in `/opt/software`, potentially for different compilers, operating systems, etc. The full definition of all software installed in `/opt/software` is in `/opt/spack-environment/dev/spack.yaml`, the ultimate definition of the EIC standard environment.
+The jumble of files in `/opt/local` are in fact merely a view into software installed in a more organized fashion in `/opt/software`, potentially for different compilers, operating systems, etc. The full definition of all software installed in `/opt/software` is in `/opt/spack-environment/xl/spack.yaml`, the ultimate definition of the EIC standard environment.
+
+::::::::::::::::::::::::::::::::::::::::::::: challenge
+
+## Exercise 3
+
+- Using `cd` and `ls`, check that you can navigate to various directories in the container, and that you can access the directories for your own work.
+- Verify that you can run some of the software dependencies that are installed in `eic-shell`. For example, run the command `ddsim --help` to get some information on one way to run Geant4 simulations on DD4hep-based geometries. Use the command `which ddsim` to see where `ddsim` is located.
+- Advanced users: Based on the location of `ddsim` found in the previous step, verify that this is a symbolic link to a different location under `/opt/software`. Verify that the version in the path of that location agrees with the version specified in the `spack.yaml` file.
+
+::::::::::::::: solution
-> Exercise:
-> - Using `cd` and `ls`, check that you can navigate to various directories in the container, and that you can access the directories for your own work.
-> - Verify that you can run some of the software dependencies that are installed in `eic-shell`. For example, run the command `ddsim --help` to get some informaton on one way to run Geant4 simulations on DD4hep-based geometries. Use the command `which ddsim` to see where `ddsim` is located.
-> - Advanced users: Based on the location of `ddsim` found in the previous step, verify that this is a symbolic link to a different location under `/opt/software`. Verify that the version in the path of that location agrees with the version specified in the `spack.yaml` file.
-{: .challenge}
+`which ddsim` reports `/opt/local/bin/ddsim`, which is a symbolic link into a versioned path under
+`/opt/software`. `ddsim --help` prints the DD4hep/Geant4 simulation usage. The version in the
+`/opt/software` path matches the one pinned in `/opt/spack-environment/xl/spack.yaml`.
+
+:::::::::::::::
+
+:::::::::::::::::::::::::::::::::::::::::::::
## Notes on starting graphical programs
+
Graphical programs work just as well inside the `eic-shell` environment as outside, with the exception of docker on Mac where some additional work is needed. On a Mac it is therefore often easier to open ROOT files outside the container.
-{% include links.md %}
+::::::::::::::::::::::::::::::::::::::::::::: keypoints
+
+- The EIC environment `eic-shell` is a singularity/docker container with a curated selection of software components.
+
+:::::::::::::::::::::::::::::::::::::::::::::
diff --git a/episodes/03-github.md b/episodes/03-github.md
new file mode 100644
index 0000000..aa58226
--- /dev/null
+++ b/episodes/03-github.md
@@ -0,0 +1,116 @@
+---
+title: "Using GitHub with EIC Software"
+teaching: 10
+exercises: 5
+---
+
+::::::::::::::::::::::::::::::::::::::::::: questions
+
+- How do we use GitHub within the EIC community?
+
+:::::::::::::::::::::::::::::::::::::::::::
+
+::::::::::::::::::::::::::::::::::::::::::: objectives
+
+- Several repositories contain the key geometry, simulation, reconstruction, and analysis software.
+- Working with repositories requires membership of the EIC organization on GitHub.
+- Issue reports and pull requests are ways to collaborate virtually on collaboration software tools.
+
+:::::::::::::::::::::::::::::::::::::::::::
+
+## How do I get added to @eic organization on GitHub?
+
+In order to contribute, you need your GitHub account to be a member of the [EIC organization](https://github.com/eic) on GitHub. If it is not, follow the [getting-started instructions](https://eic.github.io/documentation/getstarted).
+
+## Where are repositories located?
+
+The main repositories are located on two locations:
+
+- the GitHub service contains most user-facing repositories: .
+- the eicweb GitLab server contains mainly continuous integration infrastructure: .
+
+We will use GitHub as the main code repository tool. The top of the [github.com/eic](https://github.com/eic) page has several 'pinned' repositories that are most important. You can also see where most activity is happening based on the ordering of the repositories in the list.
+
+You can 'watch' repositories to be notified of activity. This is helpful if you wish to remain up-to-date on the activity. You can 'star' repositories as another way to find them easily.
+
+::::::::::::::::::::::::::::::::::::::::::: challenge
+
+## Exercise 1
+
+- Verify that you are a member of the EIC organization on GitHub: Do you see the members-only page with the grid of software meetings? If not, see previous section.
+- Check which teams you are in: Enter the search terms "members:me" into the search box in the Teams tab to see only the teams you are a member of. If you are not in ePIC Devs, request to be added at .
+- Choose one repository to subscribe to, at some level of activity.
+
+::::::::::::::: solution
+
+If you can see the members-only page you are in the organization. The Teams tab with the
+`members:me` filter shows your teams; if `ePIC Devs` is missing, request to join. Use the
+"Watch" / "Star" buttons on a repository to subscribe.
+
+:::::::::::::::
+
+:::::::::::::::::::::::::::::::::::::::::::
+
+## What is the proper way to work with these repositories?
+
+Duplication of work is avoided when everyone knows what is going on.
+
+1. A typical workflow starts from the creation of an **issue** with a feature request or a bug report. This happens in the browser on the github repository site. When creating the issue, feel free to tag people you want to notify.
+1. Create a branch that includes the issue number in the name. This can be done from the browser or command line as `git checkout -b issue123`
+1. Edit and work on your branch. When you are ready, you can commit your changes to the branch.
+1. If you are ready to merge your branch into the main repository, you can generate a **pull request** from the browser. You can assign a reviewer at this stage and add information in the Write box that explains the changes made. If your work is not yet completed, you can utilize the Draft PR.
+1. Once you've generated a pull request, continuous integration tests are run to ensure compatibility. The reviewer will approve the changes, merging your code into the main branch.
+1. After the pull request is approved and the code is merged, you can delete your branch using the browser or via the command line.
+
+A good example of the above workflow is shown in detail in the [JANA2 contributing tutorial](https://eic.github.io/tutorial-jana2/05-contributing/index.html) for the EICrecon repository.
+
+::::::::::::::::::::::::::::::::::::::::::: challenge
+
+## Exercise 2
+
+- Think of one issue that is related to the analysis or detector you are mainly working on.
+- Think of a good title that summarizes what the issue is.
+- Open an issue in the relevant repository and fill out the provided template.
+- Tag one person (who is not the instructor) whose input you would like on this issue.
+- Advanced users: Create a branch from the issue using the 'Development' section in the right side bar of the issue.
+
+::::::::::::::: solution
+
+A good issue has a specific, searchable title and a filled-in template describing the problem and
+expected behaviour. Tagging a relevant person with `@username` notifies them. The issue's
+"Development" sidebar can create a linked branch you can then check out locally.
+
+:::::::::::::::
+
+:::::::::::::::::::::::::::::::::::::::::::
+
+## How can you use eic-shell with the repositories that you are working on?
+
+`eic-shell` already contains all of the compilers and dependencies (ROOT, DD4hep, Geant4, ACTS, podio, and more) that the EIC repositories build against, so you do not have to install any of them yourself. Because your home directory and the current working directory are shared between the host and the container, the typical development loop is:
+
+1. Clone the repository on your host system, e.g. `git clone https://github.com/eic/EICrecon`.
+2. Start `eic-shell` from a directory that contains your clone, so the source tree is visible inside the container.
+3. Configure and build **inside** `eic-shell` with the repository's build instructions (usually `cmake` followed by `cmake --build`). Because the build uses the container's dependencies, your result matches the standard environment used for productions and continuous integration.
+4. Edit the code with your usual editor on the host, then rebuild inside `eic-shell` — no need to copy files back and forth.
+
+The [Reconstruction framework tutorial](https://eic.github.io/tutorial-jana2/) walks through building and running EICrecon inside `eic-shell` in detail.
+
+## What are the checks that are run on pull requests?
+
+When you open a pull request, continuous-integration (CI) jobs run automatically, and the PR must pass them and receive at least one review from a collaborator before it can be merged. The recommended workflow and the full set of requirements for EICrecon are documented in the [EICrecon contribution guidelines](https://github.com/eic/EICrecon/blob/main/CONTRIBUTING.md). A pull request is ready to be reviewed when:
+
+- a detailed description of the change is provided,
+- all CI jobs pass (build and tests),
+- `clang-format` and `clang-tidy` have been run locally (code style and static checks),
+- the commit history is legible (small, self-contained commits with descriptive messages),
+- the code is appropriately documented.
+
+The reviewer then checks that the code is maintainable and scalable, that it is correct with respect to physics requirements, and that any changes in physics or computing performance (the benchmarks) are understood.
+
+::::::::::::::::::::::::::::::::::::::::::: keypoints
+
+- We use several tools on GitHub to ensure we keep the overview of who does what work.
+- `eic-shell` provides the dependencies to build the EIC repositories; clone on the host and build inside the container.
+- Every pull request must pass CI (build, tests, `clang-format`/`clang-tidy`) and receive a review before merging — see the [EICrecon contribution guidelines](https://github.com/eic/EICrecon/blob/main/CONTRIBUTING.md).
+
+:::::::::::::::::::::::::::::::::::::::::::
diff --git a/fig/.gitkeep b/fig/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/files/.gitkeep b/files/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/index.md b/index.md
index 5999f11..3fa3766 100644
--- a/index.md
+++ b/index.md
@@ -1,15 +1,19 @@
---
-layout: lesson
-root: . # Is the only page that doesn't follow the pattern /:path/index.html
-permalink: index.html # Is the only page that doesn't follow the pattern /:path/index.html
+site: sandpaper::sandpaper_site
---
-Welcome to the EIC Tutorial on setting up on your environment!
-This is typically the first tutorial in a series. It will help you in setting up your environment to be productive in the EIC community.
+Welcome to the EIC Tutorial on setting up your environment!
-> ## Prerequisites
->
-> Please take a look in the setup section for necessary prerequisites for this lesson.
-{: .prereq}
+This is typically the first tutorial in the [series of EIC tutorials](https://eic.github.io/documentation/tutorials.html).
+It will help you set up your environment to be productive in the EIC community. Once your environment
+is working, good next steps are the [Analysis tutorial](https://eic.github.io/tutorial-analysis/) and
+the [File Access tutorial](https://eic.github.io/tutorial-file-access/).
-{% include links.md %}
+::::::::::::::::::::::::::::::::::::::::::::: prereq
+
+## Prerequisites
+
+Please take a look at the [Setup](learners/setup.md) page for necessary prerequisites for this
+lesson.
+
+:::::::::::::::::::::::::::::::::::::::::::::
diff --git a/instructors/instructor-notes.md b/instructors/instructor-notes.md
new file mode 100644
index 0000000..ff6e3f4
--- /dev/null
+++ b/instructors/instructor-notes.md
@@ -0,0 +1,19 @@
+---
+title: "Instructor Notes"
+---
+
+This is the first tutorial in the EIC series and establishes the environment (`eic-shell`) and
+collaboration tools (GitHub) that every later tutorial depends on.
+
+## Before the session
+
+- Ask learners to complete the [Setup](../learners/setup.md) page in advance: downloading the
+ container can take a long time on systems without `/cvmfs` (up to ~13 GB), so it should not be
+ done live.
+- Confirm learners have a GitHub account and, ideally, membership of the
+ [EIC organization](https://github.com/eic).
+
+## Timing
+
+The lesson is short (~30 minutes of teaching). Most of the wall-clock time is the container
+download, which learners should have done beforehand.
diff --git a/_extras/BNL_SDCC_QuickStart.md b/learners/bnl-sdcc-quickstart.md
similarity index 70%
rename from _extras/BNL_SDCC_QuickStart.md
rename to learners/bnl-sdcc-quickstart.md
index 8e00e1b..556b3c8 100644
--- a/_extras/BNL_SDCC_QuickStart.md
+++ b/learners/bnl-sdcc-quickstart.md
@@ -2,22 +2,26 @@
title: "BNL SDCC Quick Start"
---
-If you will be running eic-shell on the BNL SDCC systems, some pointers for getting started on the SDCC system are included below.
+If you will be running eic-shell on the BNL SDCC systems, some pointers for getting started on the
+SDCC system are included below.
## Connecting to an eic interactive node
Assuming you have ssh-keys set up for the SDCC, you can connect to a login node via -
-```console
+```bash
ssh -XY USER@ssh.sdcc.bnl.gov
```
-Where you should replace USER with your username. This should connect you to a login node, connect to an eic interactive node via
-```console
+Where you should replace USER with your username. This should connect you to a login node, connect
+to an eic interactive node via
+
+```bash
rterm -i
```
+
OR connect to a specific node via
-```console
+```bash
rterm -i eicXXXX
```
diff --git a/learners/discuss.md b/learners/discuss.md
new file mode 100644
index 0000000..c283249
--- /dev/null
+++ b/learners/discuss.md
@@ -0,0 +1,8 @@
+---
+title: Discussion
+---
+
+For questions and discussion about the EIC software environment, see the
+[EIC getting-started guide](https://eic.github.io/documentation/getstarted) and the list of
+[EIC tutorials](https://eic.github.io/documentation/tutorials.html), and ask in the
+[ePIC software-tutorials channel on Mattermost](https://chat.epic-eic.org/main/channels/software-tutorials).
diff --git a/_extras/JLAB_Farm_QuickStart.md b/learners/jlab-farm-quickstart.md
similarity index 51%
rename from _extras/JLAB_Farm_QuickStart.md
rename to learners/jlab-farm-quickstart.md
index 32f4ccd..c4d85f7 100644
--- a/_extras/JLAB_Farm_QuickStart.md
+++ b/learners/jlab-farm-quickstart.md
@@ -2,29 +2,38 @@
title: "JLab Farm Quick Start"
---
-If you will be running eic-shell on the JLab farm systems, some pointers for getting started on the SDCC system are included below.
+If you will be running eic-shell on the JLab farm systems, some pointers for getting started on the
+JLab farm are included below.
## Connecting to an iFarm node
Before connecting to an iFarm node, you must first connect to the login node via -
-```console
+```bash
ssh USER@login.jlab.org
```
-Where you should replace USER with your username. You will be prompted to enter your password. For the login node this is your PIN + two-factor token. Do not insert a space between the PIN and token. Once on the login node, you can then connect to an interactive node via -
-```console
+Where you should replace USER with your username. You will be prompted to enter your password. For
+the login node this is your PIN + two-factor token. Do not insert a space between the PIN and token.
+Once on the login node, you can then connect to an interactive node via -
+
+```bash
ssh USER@ifarm9
```
+
You will again be prompted for your password, this time, it's your CUE password.
Other iFarm nodes can be specified if desired. Additionally, you can also just use -
-```console
-ssh -J USER@login.jlab.org USER`@ifarm9
+```bash
+ssh -J USER@login.jlab.org USER@ifarm9
```
-to do both steps at once.
-See this [Service Now](https://jlab.servicenowservices.com/scicomp?id=kb_article_view&sysparm_article=KB0015066) documentation for further pointers and assistance.
+to do both steps at once.
+
+See this [Service Now](https://jlab.servicenowservices.com/scicomp?id=kb_article_view&sysparm_article=KB0015066)
+documentation for further pointers and assistance.
-For running larger/longer simulation/reconstruction jobs, you may find the [scripts](https://github.com/eic/snippets/tree/main/JLab_Farm_Scripts) in the snippets repository useful.
+For running larger/longer simulation/reconstruction jobs, you may find the
+[scripts](https://github.com/eic/snippets/tree/main/JLab_Farm_Scripts) in the snippets repository
+useful.
diff --git a/learners/reference.md b/learners/reference.md
new file mode 100644
index 0000000..7db7d59
--- /dev/null
+++ b/learners/reference.md
@@ -0,0 +1,12 @@
+---
+title: 'Reference'
+---
+
+## Glossary
+
+EIC
+: Electron-Ion Collider
+
+eic-shell
+: The EIC standard software environment, a singularity/apptainer or docker container with a
+ curated selection of software components used for EIC simulations and analysis.
diff --git a/learners/setup.md b/learners/setup.md
new file mode 100644
index 0000000..95693af
--- /dev/null
+++ b/learners/setup.md
@@ -0,0 +1,48 @@
+---
+title: Setup
+---
+
+::::::::::::::::::::::::::::::::::::::::::::: callout
+
+If you are using the [BNL SDCC](bnl-sdcc-quickstart.md) or the [JLab Farm](jlab-farm-quickstart.md),
+you may find the quick start info useful if you are new to these systems.
+
+:::::::::::::::::::::::::::::::::::::::::::::
+
+In advance of the training session, please ensure that
+
+1. You have a GitHub account ([sign up here](https://github.com/signup))
+2. You have singularity/apptainer or docker (on Mac) installed and working
+ - Provided by default on BNL SDCC and the JLab iFarm, check by trying the command `singularity` and verify that you get an info dump on the usage of the command
+ - On your local system:
+ - systems that use cvmfs or download images can install **singularity or apptainer** (see the [apptainer quick-start guide](https://apptainer.org/docs/user/main/quick_start.html))
+ - systems that download images can install **docker** (see the [Docker website](https://www.docker.com/))
+ - You should at a minimum be able to run either of the following commands and open a shell:
+ - `singularity run docker://alpine`
+ - `docker run --rm -it alpine`
+3. Download `eic-shell`:
+ - Due to time and bandwidth limitations, this should be downloaded to your desired system before the live tutorial
+ - From the system you use with singularity/apptainer or docker, make a folder called `eic`
+ - From inside this directory:
+ - Install by running the command:
+
+ ```bash
+ curl --location https://get.epic-eic.org | bash
+ ```
+
+ - **or** you can save the file at as `install.sh` and run this script by hand:
+
+ ```bash
+ wget --output-document install.sh https://get.epic-eic.org
+ bash install.sh
+ ```
+
+If you have completed the numbered steps above, then you are ready for the tutorial. Additional
+information and training links are below:
+
+- Basic familiarity with Unix shell and Git
+ - Software Carpentry [Unix Shell](https://swcarpentry.github.io/shell-novice/) training (recommended)
+ - Software Carpentry [Basic Git](https://swcarpentry.github.io/git-novice/) training (recommended)
+- Basic familiarity with singularity and docker:
+ - Software Carpentry [Incubator Singularity](https://carpentries-incubator.github.io/singularity-introduction/) training (optional)
+ - HEP Software Foundation [Docker](https://hsf-training.github.io/hsf-training-docker/index.html) training (optional)
diff --git a/links.md b/links.md
new file mode 100644
index 0000000..4c5cd2f
--- /dev/null
+++ b/links.md
@@ -0,0 +1,10 @@
+
+
+[pandoc]: https://pandoc.org/MANUAL.html
+[r-markdown]: https://rmarkdown.rstudio.com/
+[rstudio]: https://www.rstudio.com/
+[carpentries-workbench]: https://carpentries.github.io/sandpaper-docs/
+
diff --git a/profiles/learner-profiles.md b/profiles/learner-profiles.md
new file mode 100644
index 0000000..69093b4
--- /dev/null
+++ b/profiles/learner-profiles.md
@@ -0,0 +1,8 @@
+---
+title: 'Learner Profiles'
+---
+
+This lesson targets people who are new to the EIC software environment — students, postdocs, and
+researchers joining the ePIC collaboration who need a working `eic-shell` and a GitHub account
+before starting the other [EIC tutorials](https://eic.github.io/documentation/tutorials.html). No
+prior experience with containers or the EIC software stack is assumed.
diff --git a/reference.md b/reference.md
deleted file mode 100644
index d6fe4ff..0000000
--- a/reference.md
+++ /dev/null
@@ -1,9 +0,0 @@
----
-layout: reference
----
-
-## Glossary
-
-EIC: Electron-Ion Collider
-
-{% include links.md %}
diff --git a/setup.md b/setup.md
deleted file mode 100644
index c269616..0000000
--- a/setup.md
+++ /dev/null
@@ -1,40 +0,0 @@
----
-title: Setup
----
-
-> Note: If you are using the [BNL SDCC](https://eic.github.io/tutorial-setting-up-environment/BNL_SDCC_QuickStart/index.html)or the [JLab Farm](https://eic.github.io/tutorial-setting-up-environment/JLAB_Farm_QuickStart/index.html), you may find the quick start info in the extras section useful if you are new to these systems.
-{: .callout}
-
-In advance of the training session, please ensure that
-1. You have a GitHub account ([sign up here](https://github.com/signup))
-1. You have singularity/apptainer or docker (on Mac) installed and working
-- Provided by default on BNL SDCC and the JLab iFarm, check by trying the command `singularity` and verify that you get an info dump on the usage of the command
-- On your local system:
- - systems that use cvmfs or download images can install **singularity or apptainer** (see [here](https://apptainer.org/docs/user/main/quick_start.html))
- - systems that download images can install **docker** (see [here](https://www.docker.com/))
-- You should at a minimum be able to run either of the following commands and open a shell:
- - `singularity run docker://alpine`
- - `docker run --rm -it alpine`
-1. Download `eic-shell`:
-- Due to time and bandwidth limitations, this should be downloaded to your desired system before the live tutorial
-- From the system you use with singularity/apptainer or docker, make a folder called `eic`
-- From inside this directory:
- - Install by running the command:
-```
-curl --location https://get.epic-eic.org | bash
-```
- - **or** you can save the file at https://get.epic-eic.org as `install.sh` and run this script by hand:
-```
-wget --output-document install.sh https://get.epic-eic.org
-bash install.sh
-```
-
-If you have completed the numbered steps above, then you are ready for the tutorial. Additional information and training links are below:
-- Basic familiarity with Unix shell and Git
- - Software Carpentry [Unix Shell](https://swcarpentry.github.io/shell-novice/) training (recommended)
- - Software Carpentry [Basic Git](https://swcarpentry.github.io/git-novice/) training (recommended)
-- Basic familiarity with singularity and docker:
- - Software Carpentry [Incubator Singularity](https://carpentries-incubator.github.io/singularity-introduction/) training (optional)
- - HEP Software Foundation [Docker](https://hsf-training.github.io/hsf-training-docker/index.html) training (optional)
-
-{% include links.md %}