commit 583a8e81a1c92d2f0d97b386d8e52fe2285fe48f parent 52b178ee4084c2f858372699901e454817e3ef5a Author: Vincent Demeester <vincent@sbr.pm> Date: Fri, 27 Nov 2020 18:24:56 +0100 www: import vincent.demeest.fr website 😅 Signed-off-by: Vincent Demeester <vincent@sbr.pm> Diffstat:
164 files changed, 16116 insertions(+), 0 deletions(-)
diff --git a/www/README.org b/www/README.org @@ -0,0 +1,2 @@ +#+TITLE: www +#+SUBTITLE: my website(s) diff --git a/www/vincent.demeester.fr/.fancyindex/HEADER.md b/www/vincent.demeester.fr/.fancyindex/HEADER.md @@ -0,0 +1,4 @@ +# Test of a HEADER.md file +## Test of a HEADER.md file +### Test of a HEADER.md file +#### Test of a HEADER.md file diff --git a/www/vincent.demeester.fr/.fancyindex/addNginxFancyIndexForm.js b/www/vincent.demeester.fr/.fancyindex/addNginxFancyIndexForm.js @@ -0,0 +1,33 @@ +// addNginxFancyIndexForm.js +// Add a small form to filter through the output of Nginx FancyIndex page +// © 2017, Lilian Besson (Naereen) and contributors, +// open-sourced under the MIT License, https://lbesson.mit-license.org/ +// hosted on GitHub, https://GitHub.com/Naereen/Nginx-Fancyindex-Theme +var form = document.createElement('form'); +var input = document.createElement('input'); + +input.name = 'filter'; +input.id = 'search'; +input.placeholder = 'Type to search...'; + +form.appendChild(input); + +document.querySelector('h1').after(form); + +var listItems = [].slice.call(document.querySelectorAll('#list tbody tr')); + +input.addEventListener('keyup', function () { + var i, + // Word sequence _matching_ to input. All, except last, words must be _complete_. + e = "(^|.*[^\\pL])" + this.value.trim().split(/\s+/).join("([^\\pL]|[^\\pL].*[^\\pL])") + ".*$", + n = RegExp(e, "i"); + listItems.forEach(function(item) { + item.removeAttribute('hidden'); + }); + listItems.filter(function(item) { + i = item.querySelector('td').textContent.replace(/\s+/g, " "); + return !n.test(i); + }).forEach(function(item) { + item.hidden = true; + }); +}); diff --git a/www/vincent.demeester.fr/.fancyindex/footer.html b/www/vincent.demeester.fr/.fancyindex/footer.html @@ -0,0 +1,40 @@ +<section id="raw_include_README_md"></section> +</main> +<footer> + <span class='copyright'> + Content and design by Vincent Demeester + (<a rel='licence' href='http://creativecommons.org/licenses/by-nc-sa/3.0/'>Some rights reserved</a>) + </span> +</footer> +<script src="https://unpkg.com/xregexp/xregexp-all.js"></script> +<script type="text/javascript" src="/.fancyindex/addNginxFancyIndexForm.js"></script> +<script type="text/javascript" src="/.fancyindex/showdown.min.js"></script> +<script type="text/javascript" defer> + var converter = new showdown.Converter(); + $( "#raw_include_HEADER_md" ).load( "HEADER.md", function (){ + var elem = document.querySelector("#raw_include_HEADER_md"); + // strip leading whitespace so it isn't evaluated as code + var text = elem.innerHTML; + // console.log("text =", text); + text = text.replace(/\n[ ]*/g, '\n'); + // console.log("text =", text); + var html = converter.makeHtml(text); + // console.log("html =", html); + // here, have some HTML + elem.innerHTML = html; + }); + $( "#raw_include_README_md" ).load( "README.md", function (){ + var elem = document.querySelector("#raw_include_README_md"); + // strip leading whitespace so it isn't evaluated as code + var text = elem.innerHTML; + // console.log("text =", text); + text = text.replace(/\n[ ]*/g, '\n'); + // console.log("text =", text); + var html = converter.makeHtml(text); + // console.log("html =", html); + // here, have some HTML + elem.innerHTML = html; + }); +</script> +</body> +</html> diff --git a/www/vincent.demeester.fr/.fancyindex/header.html b/www/vincent.demeester.fr/.fancyindex/header.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <meta http-equiv="x-ua-compatible" content="IE=edge"> + <title>Directory</title> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="https://vincent.demeester.fr/css/new.css"> + <script type="text/javascript" src="/.fancyindex/jquery.min.js"></script> + </head> + <body> + <header id="top" class="status"> + <nav> + <img src="https://vincent.demeester.fr/images/favicon.ico" id="sitelogo"/> <a href='https://vincent.demeester.fr/'>home</a> / + <a href='https://vincent.demeester.fr/posts/'>posts</a> (<a href='https://vincent.demeester.fr/index.xml'>rss</a>) / + <a href='https://vincent.demeester.fr/articles/'>articles</a> / + <a href='https://vincent.demeester.fr/configurations/'>configurations</a> / + <a href='https://dl.sbr.pm/'>files</a> / + <a href='https://vincent.demeester.fr/about/'>about</a></li> + </nav> + </header> + <main id="content"> + <header id="raw_include_HEADER_md"> + </header> +<h1>Directory: diff --git a/www/vincent.demeester.fr/.fancyindex/jquery.min.js b/www/vincent.demeester.fr/.fancyindex/jquery.min.js @@ -0,0 +1,5 @@ +/*! jQuery v2.1.0 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */ +/* Used on Lilian Besson's web pages ~ 2011-2014 */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k="".trim,l={},m=a.document,n="2.1.0",o=function(a,b){return new o.fn.init(a,b)},p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};o.fn=o.prototype={jquery:n,constructor:o,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=o.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return o.each(this,a,b)},map:function(a){return this.pushStack(o.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},o.extend=o.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||o.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(o.isPlainObject(d)||(e=o.isArray(d)))?(e?(e=!1,f=c&&o.isArray(c)?c:[]):f=c&&o.isPlainObject(c)?c:{},g[b]=o.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},o.extend({expando:"jQuery"+(n+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===o.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return a-parseFloat(a)>=0},isPlainObject:function(a){if("object"!==o.type(a)||a.nodeType||o.isWindow(a))return!1;try{if(a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(b){return!1}return!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=o.trim(a),a&&(1===a.indexOf("use strict")?(b=m.createElement("script"),b.text=a,m.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":k.call(a)},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?o.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),o.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||o.guid++,f):void 0},now:Date.now,support:l}),o.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=a.length,c=o.type(a);return"function"===c||o.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s="sizzle"+-new Date,t=a.document,u=0,v=0,w=eb(),x=eb(),y=eb(),z=function(a,b){return a===b&&(j=!0),0},A="undefined",B=1<<31,C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=D.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},J="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",K="[\\x20\\t\\r\\n\\f]",L="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",M=L.replace("w","w#"),N="\\["+K+"*("+L+")"+K+"*(?:([*^$|!~]?=)"+K+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+M+")|)|)"+K+"*\\]",O=":("+L+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+N.replace(3,8)+")*)|.*)\\)|)",P=new RegExp("^"+K+"+|((?:^|[^\\\\])(?:\\\\.)*)"+K+"+$","g"),Q=new RegExp("^"+K+"*,"+K+"*"),R=new RegExp("^"+K+"*([>+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(O),U=new RegExp("^"+M+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L.replace("w","w*")+")"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=/'|\\/g,ab=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),bb=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{G.apply(D=H.call(t.childNodes),t.childNodes),D[t.childNodes.length].nodeType}catch(cb){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function db(a,b,d,e){var f,g,h,i,j,m,p,q,u,v;if((b?b.ownerDocument||b:t)!==l&&k(b),b=b||l,d=d||[],!a||"string"!=typeof a)return d;if(1!==(i=b.nodeType)&&9!==i)return[];if(n&&!e){if(f=Z.exec(a))if(h=f[1]){if(9===i){if(g=b.getElementById(h),!g||!g.parentNode)return d;if(g.id===h)return d.push(g),d}else if(b.ownerDocument&&(g=b.ownerDocument.getElementById(h))&&r(b,g)&&g.id===h)return d.push(g),d}else{if(f[2])return G.apply(d,b.getElementsByTagName(a)),d;if((h=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(h)),d}if(c.qsa&&(!o||!o.test(a))){if(q=p=s,u=b,v=9===i&&a,1===i&&"object"!==b.nodeName.toLowerCase()){m=ob(a),(p=b.getAttribute("id"))?q=p.replace(_,"\\$&"):b.setAttribute("id",q),q="[id='"+q+"'] ",j=m.length;while(j--)m[j]=q+pb(m[j]);u=$.test(a)&&mb(b.parentNode)||b,v=m.join(",")}if(v)try{return G.apply(d,u.querySelectorAll(v)),d}catch(w){}finally{p||b.removeAttribute("id")}}}return xb(a.replace(P,"$1"),b,d,e)}function eb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function fb(a){return a[s]=!0,a}function gb(a){var b=l.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function hb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function ib(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||B)-(~a.sourceIndex||B);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function jb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function kb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function lb(a){return fb(function(b){return b=+b,fb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function mb(a){return a&&typeof a.getElementsByTagName!==A&&a}c=db.support={},f=db.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},k=db.setDocument=function(a){var b,e=a?a.ownerDocument||a:t,g=e.defaultView;return e!==l&&9===e.nodeType&&e.documentElement?(l=e,m=e.documentElement,n=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){k()},!1):g.attachEvent&&g.attachEvent("onunload",function(){k()})),c.attributes=gb(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=gb(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(e.getElementsByClassName)&&gb(function(a){return a.innerHTML="<div class='a'></div><div class='a i'></div>",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=gb(function(a){return m.appendChild(a).id=s,!e.getElementsByName||!e.getElementsByName(s).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==A&&n){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ab,bb);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ab,bb);return function(a){var c=typeof a.getAttributeNode!==A&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==A?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==A&&n?b.getElementsByClassName(a):void 0},p=[],o=[],(c.qsa=Y.test(e.querySelectorAll))&&(gb(function(a){a.innerHTML="<select t=''><option selected=''></option></select>",a.querySelectorAll("[t^='']").length&&o.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||o.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll(":checked").length||o.push(":checked")}),gb(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&o.push("name"+K+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||o.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),o.push(",.*:")})),(c.matchesSelector=Y.test(q=m.webkitMatchesSelector||m.mozMatchesSelector||m.oMatchesSelector||m.msMatchesSelector))&&gb(function(a){c.disconnectedMatch=q.call(a,"div"),q.call(a,"[s!='']:x"),p.push("!=",O)}),o=o.length&&new RegExp(o.join("|")),p=p.length&&new RegExp(p.join("|")),b=Y.test(m.compareDocumentPosition),r=b||Y.test(m.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},z=b?function(a,b){if(a===b)return j=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===t&&r(t,a)?-1:b===e||b.ownerDocument===t&&r(t,b)?1:i?I.call(i,a)-I.call(i,b):0:4&d?-1:1)}:function(a,b){if(a===b)return j=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],k=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:i?I.call(i,a)-I.call(i,b):0;if(f===g)return ib(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)k.unshift(c);while(h[d]===k[d])d++;return d?ib(h[d],k[d]):h[d]===t?-1:k[d]===t?1:0},e):l},db.matches=function(a,b){return db(a,null,null,b)},db.matchesSelector=function(a,b){if((a.ownerDocument||a)!==l&&k(a),b=b.replace(S,"='$1']"),!(!c.matchesSelector||!n||p&&p.test(b)||o&&o.test(b)))try{var d=q.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return db(b,l,null,[a]).length>0},db.contains=function(a,b){return(a.ownerDocument||a)!==l&&k(a),r(a,b)},db.attr=function(a,b){(a.ownerDocument||a)!==l&&k(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!n):void 0;return void 0!==f?f:c.attributes||!n?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},db.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},db.uniqueSort=function(a){var b,d=[],e=0,f=0;if(j=!c.detectDuplicates,i=!c.sortStable&&a.slice(0),a.sort(z),j){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return i=null,a},e=db.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=db.selectors={cacheLength:50,createPseudo:fb,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ab,bb),a[3]=(a[4]||a[5]||"").replace(ab,bb),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||db.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&db.error(a[0]),a},PSEUDO:function(a){var b,c=!a[5]&&a[2];return V.CHILD.test(a[0])?null:(a[3]&&void 0!==a[4]?a[2]=a[4]:c&&T.test(c)&&(b=ob(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ab,bb).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=w[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&w(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==A&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=db.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),t=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&t){k=q[s]||(q[s]={}),j=k[a]||[],n=j[0]===u&&j[1],m=j[0]===u&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[u,n,m];break}}else if(t&&(j=(b[s]||(b[s]={}))[a])&&j[0]===u)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(t&&((l[s]||(l[s]={}))[a]=[u,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||db.error("unsupported pseudo: "+a);return e[s]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?fb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:fb(function(a){var b=[],c=[],d=g(a.replace(P,"$1"));return d[s]?fb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:fb(function(a){return function(b){return db(a,b).length>0}}),contains:fb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:fb(function(a){return U.test(a||"")||db.error("unsupported lang: "+a),a=a.replace(ab,bb).toLowerCase(),function(b){var c;do if(c=n?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===m},focus:function(a){return a===l.activeElement&&(!l.hasFocus||l.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:lb(function(){return[0]}),last:lb(function(a,b){return[b-1]}),eq:lb(function(a,b,c){return[0>c?c+b:c]}),even:lb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:lb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:lb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:lb(function(a,b,c){for(var d=0>c?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=jb(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=kb(b);function nb(){}nb.prototype=d.filters=d.pseudos,d.setFilters=new nb;function ob(a,b){var c,e,f,g,h,i,j,k=x[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){(!c||(e=Q.exec(h)))&&(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=R.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(P," ")}),h=h.slice(c.length));for(g in d.filter)!(e=V[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?db.error(a):x(a,i).slice(0)}function pb(a){for(var b=0,c=a.length,d="";c>b;b++)d+=a[b].value;return d}function qb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=v++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[u,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[s]||(b[s]={}),(h=i[d])&&h[0]===u&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function rb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function sb(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function tb(a,b,c,d,e,f){return d&&!d[s]&&(d=tb(d)),e&&!e[s]&&(e=tb(e,f)),fb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||wb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:sb(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=sb(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?I.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=sb(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ub(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],i=g||d.relative[" "],j=g?1:0,k=qb(function(a){return a===b},i,!0),l=qb(function(a){return I.call(b,a)>-1},i,!0),m=[function(a,c,d){return!g&&(d||c!==h)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>j;j++)if(c=d.relative[a[j].type])m=[qb(rb(m),c)];else{if(c=d.filter[a[j].type].apply(null,a[j].matches),c[s]){for(e=++j;f>e;e++)if(d.relative[a[e].type])break;return tb(j>1&&rb(m),j>1&&pb(a.slice(0,j-1).concat({value:" "===a[j-2].type?"*":""})).replace(P,"$1"),c,e>j&&ub(a.slice(j,e)),f>e&&ub(a=a.slice(e)),f>e&&pb(a))}m.push(c)}return rb(m)}function vb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,i,j,k){var m,n,o,p=0,q="0",r=f&&[],s=[],t=h,v=f||e&&d.find.TAG("*",k),w=u+=null==t?1:Math.random()||.1,x=v.length;for(k&&(h=g!==l&&g);q!==x&&null!=(m=v[q]);q++){if(e&&m){n=0;while(o=a[n++])if(o(m,g,i)){j.push(m);break}k&&(u=w)}c&&((m=!o&&m)&&p--,f&&r.push(m))}if(p+=q,c&&q!==p){n=0;while(o=b[n++])o(r,s,g,i);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=E.call(j));s=sb(s)}G.apply(j,s),k&&!f&&s.length>0&&p+b.length>1&&db.uniqueSort(j)}return k&&(u=w,h=t),r};return c?fb(f):f}g=db.compile=function(a,b){var c,d=[],e=[],f=y[a+" "];if(!f){b||(b=ob(a)),c=b.length;while(c--)f=ub(b[c]),f[s]?d.push(f):e.push(f);f=y(a,vb(e,d))}return f};function wb(a,b,c){for(var d=0,e=b.length;e>d;d++)db(a,b[d],c);return c}function xb(a,b,e,f){var h,i,j,k,l,m=ob(a);if(!f&&1===m.length){if(i=m[0]=m[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&c.getById&&9===b.nodeType&&n&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(ab,bb),b)||[])[0],!b)return e;a=a.slice(i.shift().value.length)}h=V.needsContext.test(a)?0:i.length;while(h--){if(j=i[h],d.relative[k=j.type])break;if((l=d.find[k])&&(f=l(j.matches[0].replace(ab,bb),$.test(i[0].type)&&mb(b.parentNode)||b))){if(i.splice(h,1),a=f.length&&pb(i),!a)return G.apply(e,f),e;break}}}return g(a,m)(f,b,!n,e,$.test(a)&&mb(b.parentNode)||b),e}return c.sortStable=s.split("").sort(z).join("")===s,c.detectDuplicates=!!j,k(),c.sortDetached=gb(function(a){return 1&a.compareDocumentPosition(l.createElement("div"))}),gb(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||hb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&gb(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||hb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),gb(function(a){return null==a.getAttribute("disabled")})||hb(J,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),db}(a);o.find=t,o.expr=t.selectors,o.expr[":"]=o.expr.pseudos,o.unique=t.uniqueSort,o.text=t.getText,o.isXMLDoc=t.isXML,o.contains=t.contains;var u=o.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(o.isFunction(b))return o.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return o.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return o.filter(b,a,c);b=o.filter(b,a)}return o.grep(a,function(a){return g.call(b,a)>=0!==c})}o.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?o.find.matchesSelector(d,a)?[d]:[]:o.find.matches(a,o.grep(b,function(a){return 1===a.nodeType}))},o.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(o(a).filter(function(){for(b=0;c>b;b++)if(o.contains(e[b],this))return!0}));for(b=0;c>b;b++)o.find(a,e[b],d);return d=this.pushStack(c>1?o.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?o(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=o.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof o?b[0]:b,o.merge(this,o.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:m,!0)),v.test(c[1])&&o.isPlainObject(b))for(c in b)o.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=m.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=m,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):o.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(o):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),o.makeArray(a,this))};A.prototype=o.fn,y=o(m);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};o.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&o(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),o.fn.extend({has:function(a){var b=o(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(o.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?o(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&o.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?o.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(o(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(o.unique(o.merge(this.get(),o(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}o.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return o.dir(a,"parentNode")},parentsUntil:function(a,b,c){return o.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return o.dir(a,"nextSibling")},prevAll:function(a){return o.dir(a,"previousSibling")},nextUntil:function(a,b,c){return o.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return o.dir(a,"previousSibling",c)},siblings:function(a){return o.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return o.sibling(a.firstChild)},contents:function(a){return a.contentDocument||o.merge([],a.childNodes)}},function(a,b){o.fn[a]=function(c,d){var e=o.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=o.filter(d,e)),this.length>1&&(C[a]||o.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return o.each(a.match(E)||[],function(a,c){b[c]=!0}),b}o.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):o.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){o.each(b,function(b,c){var d=o.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&o.each(arguments,function(a,b){var c;while((c=o.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?o.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},o.extend({Deferred:function(a){var b=[["resolve","done",o.Callbacks("once memory"),"resolved"],["reject","fail",o.Callbacks("once memory"),"rejected"],["notify","progress",o.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return o.Deferred(function(c){o.each(b,function(b,f){var g=o.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&o.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?o.extend(a,d):d}},e={};return d.pipe=d.then,o.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&o.isFunction(a.promise)?e:0,g=1===f?a:o.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&o.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;o.fn.ready=function(a){return o.ready.promise().done(a),this},o.extend({isReady:!1,readyWait:1,holdReady:function(a){a?o.readyWait++:o.ready(!0)},ready:function(a){(a===!0?--o.readyWait:o.isReady)||(o.isReady=!0,a!==!0&&--o.readyWait>0||(H.resolveWith(m,[o]),o.fn.trigger&&o(m).trigger("ready").off("ready")))}});function I(){m.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),o.ready()}o.ready.promise=function(b){return H||(H=o.Deferred(),"complete"===m.readyState?setTimeout(o.ready):(m.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},o.ready.promise();var J=o.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===o.type(c)){e=!0;for(h in c)o.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,o.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(o(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};o.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=o.expando+Math.random()}K.uid=1,K.accepts=o.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,o.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(o.isEmptyObject(f))o.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,o.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{o.isArray(b)?d=b.concat(b.map(o.camelCase)):(e=o.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!o.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?o.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}o.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){return M.access(a,b,c)},removeData:function(a,b){M.remove(a,b)},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),o.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length; +while(c--)d=g[c].name,0===d.indexOf("data-")&&(d=o.camelCase(d.slice(5)),P(f,d,e[d]));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=o.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),o.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||o.isArray(c)?d=L.access(a,b,o.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=o.queue(a,b),d=c.length,e=c.shift(),f=o._queueHooks(a,b),g=function(){o.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:o.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),o.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?o.queue(this[0],a):void 0===b?this:this.each(function(){var c=o.queue(this,a,b);o._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&o.dequeue(this,a)})},dequeue:function(a){return this.each(function(){o.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=o.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=L.get(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var Q=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,R=["Top","Right","Bottom","Left"],S=function(a,b){return a=b||a,"none"===o.css(a,"display")||!o.contains(a.ownerDocument,a)},T=/^(?:checkbox|radio)$/i;!function(){var a=m.createDocumentFragment(),b=a.appendChild(m.createElement("div"));b.innerHTML="<input type='radio' checked='checked' name='t'/>",l.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="<textarea>x</textarea>",l.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";l.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return m.activeElement}catch(a){}}o.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=o.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof o!==U&&o.event.triggered!==b.type?o.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],n=q=h[1],p=(h[2]||"").split(".").sort(),n&&(l=o.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=o.event.special[n]||{},k=o.extend({type:n,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&o.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(n,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),o.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],n=q=h[1],p=(h[2]||"").split(".").sort(),n){l=o.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||o.removeEvent(a,n,r.handle),delete i[n])}else for(n in i)o.event.remove(a,n+b[j],c,d,!0);o.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,n,p=[d||m],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||m,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+o.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[o.expando]?b:new o.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:o.makeArray(c,[b]),n=o.event.special[q]||{},e||!n.trigger||n.trigger.apply(d,c)!==!1)){if(!e&&!n.noBubble&&!o.isWindow(d)){for(i=n.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||m)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:n.bindType||q,l=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),l&&l.apply(g,c),l=k&&g[k],l&&l.apply&&o.acceptData(g)&&(b.result=l.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||n._default&&n._default.apply(p.pop(),c)!==!1||!o.acceptData(d)||k&&o.isFunction(d[q])&&!o.isWindow(d)&&(h=d[k],h&&(d[k]=null),o.event.triggered=q,d[q](),o.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=o.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=o.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=o.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((o.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?o(e,this).index(i)>=0:o.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h<b.length&&g.push({elem:this,handlers:b.slice(h)}),g},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return null==a.which&&(a.which=null!=b.charCode?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,b){var c,d,e,f=b.button;return null==a.pageX&&null!=b.clientX&&(c=a.target.ownerDocument||m,d=c.documentElement,e=c.body,a.pageX=b.clientX+(d&&d.scrollLeft||e&&e.scrollLeft||0)-(d&&d.clientLeft||e&&e.clientLeft||0),a.pageY=b.clientY+(d&&d.scrollTop||e&&e.scrollTop||0)-(d&&d.clientTop||e&&e.clientTop||0)),a.which||void 0===f||(a.which=1&f?1:2&f?3:4&f?2:0),a}},fix:function(a){if(a[o.expando])return a;var b,c,d,e=a.type,f=a,g=this.fixHooks[e];g||(this.fixHooks[e]=g=W.test(e)?this.mouseHooks:V.test(e)?this.keyHooks:{}),d=g.props?this.props.concat(g.props):this.props,a=new o.Event(f),b=d.length;while(b--)c=d[b],a[c]=f[c];return a.target||(a.target=m),3===a.target.nodeType&&(a.target=a.target.parentNode),g.filter?g.filter(a,f):a},special:{load:{noBubble:!0},focus:{trigger:function(){return this!==_()&&this.focus?(this.focus(),!1):void 0},delegateType:"focusin"},blur:{trigger:function(){return this===_()&&this.blur?(this.blur(),!1):void 0},delegateType:"focusout"},click:{trigger:function(){return"checkbox"===this.type&&this.click&&o.nodeName(this,"input")?(this.click(),!1):void 0},_default:function(a){return o.nodeName(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&(a.originalEvent.returnValue=a.result)}}},simulate:function(a,b,c,d){var e=o.extend(new o.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?o.event.trigger(e,null,b):o.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},o.removeEvent=function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)},o.Event=function(a,b){return this instanceof o.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.getPreventDefault&&a.getPreventDefault()?Z:$):this.type=a,b&&o.extend(this,b),this.timeStamp=a&&a.timeStamp||o.now(),void(this[o.expando]=!0)):new o.Event(a,b)},o.Event.prototype={isDefaultPrevented:$,isPropagationStopped:$,isImmediatePropagationStopped:$,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=Z,a&&a.preventDefault&&a.preventDefault()},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=Z,a&&a.stopPropagation&&a.stopPropagation()},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=Z,this.stopPropagation()}},o.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){o.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return(!e||e!==d&&!o.contains(d,e))&&(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),l.focusinBubbles||o.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){o.event.simulate(b,a.target,o.event.fix(a),!0)};o.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=L.access(d,b);e||d.addEventListener(a,c,!0),L.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=L.access(d,b)-1;e?L.access(d,b,e):(d.removeEventListener(a,c,!0),L.remove(d,b))}}}),o.fn.extend({on:function(a,b,c,d,e){var f,g;if("object"==typeof a){"string"!=typeof b&&(c=c||b,b=void 0);for(g in a)this.on(g,b,c,a[g],e);return this}if(null==c&&null==d?(d=b,c=b=void 0):null==d&&("string"==typeof b?(d=c,c=void 0):(d=c,c=b,b=void 0)),d===!1)d=$;else if(!d)return this;return 1===e&&(f=d,d=function(a){return o().off(a),f.apply(this,arguments)},d.guid=f.guid||(f.guid=o.guid++)),this.each(function(){o.event.add(this,a,d,c,b)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,o(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return(b===!1||"function"==typeof b)&&(c=b,b=void 0),c===!1&&(c=$),this.each(function(){o.event.remove(this,a,c,b)})},trigger:function(a,b){return this.each(function(){o.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];return c?o.event.trigger(a,b,c,!0):void 0}});var ab=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bb=/<([\w:]+)/,cb=/<|&#?\w+;/,db=/<(?:script|style|link)/i,eb=/checked\s*(?:[^=]|=\s*.checked.)/i,fb=/^$|\/(?:java|ecma)script/i,gb=/^true\/(.*)/,hb=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,ib={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};ib.optgroup=ib.option,ib.tbody=ib.tfoot=ib.colgroup=ib.caption=ib.thead,ib.th=ib.td;function jb(a,b){return o.nodeName(a,"table")&&o.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function kb(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function lb(a){var b=gb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function mb(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function nb(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)o.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=o.extend({},h),M.set(b,i))}}function ob(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&o.nodeName(a,b)?o.merge([a],c):c}function pb(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}o.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=o.contains(a.ownerDocument,a);if(!(l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||o.isXMLDoc(a)))for(g=ob(h),f=ob(a),d=0,e=f.length;e>d;d++)pb(f[d],g[d]);if(b)if(c)for(f=f||ob(a),g=g||ob(h),d=0,e=f.length;e>d;d++)nb(f[d],g[d]);else nb(a,h);return g=ob(h,"script"),g.length>0&&mb(g,!i&&ob(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,n=a.length;n>m;m++)if(e=a[m],e||0===e)if("object"===o.type(e))o.merge(l,e.nodeType?[e]:e);else if(cb.test(e)){f=f||k.appendChild(b.createElement("div")),g=(bb.exec(e)||["",""])[1].toLowerCase(),h=ib[g]||ib._default,f.innerHTML=h[1]+e.replace(ab,"<$1></$2>")+h[2],j=h[0];while(j--)f=f.lastChild;o.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===o.inArray(e,d))&&(i=o.contains(e.ownerDocument,e),f=ob(k.appendChild(e),"script"),i&&mb(f),c)){j=0;while(e=f[j++])fb.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f,g,h=o.event.special,i=0;void 0!==(c=a[i]);i++){if(o.acceptData(c)&&(f=c[L.expando],f&&(b=L.cache[f]))){if(d=Object.keys(b.events||{}),d.length)for(g=0;void 0!==(e=d[g]);g++)h[e]?o.event.remove(c,e):o.removeEvent(c,e,b.handle);L.cache[f]&&delete L.cache[f]}delete M.cache[c[M.expando]]}}}),o.fn.extend({text:function(a){return J(this,function(a){return void 0===a?o.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?o.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||o.cleanData(ob(c)),c.parentNode&&(b&&o.contains(c.ownerDocument,c)&&mb(ob(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(o.cleanData(ob(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return o.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!db.test(a)&&!ib[(bb.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(ab,"<$1></$2>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(o.cleanData(ob(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,o.cleanData(ob(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,k=this.length,m=this,n=k-1,p=a[0],q=o.isFunction(p);if(q||k>1&&"string"==typeof p&&!l.checkClone&&eb.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(k&&(c=o.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=o.map(ob(c,"script"),kb),g=f.length;k>j;j++)h=c,j!==n&&(h=o.clone(h,!0,!0),g&&o.merge(f,ob(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,o.map(f,lb),j=0;g>j;j++)h=f[j],fb.test(h.type||"")&&!L.access(h,"globalEval")&&o.contains(i,h)&&(h.src?o._evalUrl&&o._evalUrl(h.src):o.globalEval(h.textContent.replace(hb,"")))}return this}}),o.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){o.fn[a]=function(a){for(var c,d=[],e=o(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),o(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qb,rb={};function sb(b,c){var d=o(c.createElement(b)).appendTo(c.body),e=a.getDefaultComputedStyle?a.getDefaultComputedStyle(d[0]).display:o.css(d[0],"display");return d.detach(),e}function tb(a){var b=m,c=rb[a];return c||(c=sb(a,b),"none"!==c&&c||(qb=(qb||o("<iframe frameborder='0' width='0' height='0'/>")).appendTo(b.documentElement),b=qb[0].contentDocument,b.write(),b.close(),c=sb(a,b),qb.detach()),rb[a]=c),c}var ub=/^margin/,vb=new RegExp("^("+Q+")(?!px)[a-z%]+$","i"),wb=function(a){return a.ownerDocument.defaultView.getComputedStyle(a,null)};function xb(a,b,c){var d,e,f,g,h=a.style;return c=c||wb(a),c&&(g=c.getPropertyValue(b)||c[b]),c&&(""!==g||o.contains(a.ownerDocument,a)||(g=o.style(a,b)),vb.test(g)&&ub.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0!==g?g+"":g}function yb(a,b){return{get:function(){return a()?void delete this.get:(this.get=b).apply(this,arguments)}}}!function(){var b,c,d="padding:0;margin:0;border:0;display:block;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box",e=m.documentElement,f=m.createElement("div"),g=m.createElement("div");g.style.backgroundClip="content-box",g.cloneNode(!0).style.backgroundClip="",l.clearCloneStyle="content-box"===g.style.backgroundClip,f.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",f.appendChild(g);function h(){g.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%",e.appendChild(f);var d=a.getComputedStyle(g,null);b="1%"!==d.top,c="4px"===d.width,e.removeChild(f)}a.getComputedStyle&&o.extend(l,{pixelPosition:function(){return h(),b},boxSizingReliable:function(){return null==c&&h(),c},reliableMarginRight:function(){var b,c=g.appendChild(m.createElement("div"));return c.style.cssText=g.style.cssText=d,c.style.marginRight=c.style.width="0",g.style.width="1px",e.appendChild(f),b=!parseFloat(a.getComputedStyle(c,null).marginRight),e.removeChild(f),g.innerHTML="",b}})}(),o.swap=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};var zb=/^(none|table(?!-c[ea]).+)/,Ab=new RegExp("^("+Q+")(.*)$","i"),Bb=new RegExp("^([+-])=("+Q+")","i"),Cb={position:"absolute",visibility:"hidden",display:"block"},Db={letterSpacing:0,fontWeight:400},Eb=["Webkit","O","Moz","ms"];function Fb(a,b){if(b in a)return b;var c=b[0].toUpperCase()+b.slice(1),d=b,e=Eb.length;while(e--)if(b=Eb[e]+c,b in a)return b;return d}function Gb(a,b,c){var d=Ab.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function Hb(a,b,c,d,e){for(var f=c===(d?"border":"content")?4:"width"===b?1:0,g=0;4>f;f+=2)"margin"===c&&(g+=o.css(a,c+R[f],!0,e)),d?("content"===c&&(g-=o.css(a,"padding"+R[f],!0,e)),"margin"!==c&&(g-=o.css(a,"border"+R[f]+"Width",!0,e))):(g+=o.css(a,"padding"+R[f],!0,e),"padding"!==c&&(g+=o.css(a,"border"+R[f]+"Width",!0,e)));return g}function Ib(a,b,c){var d=!0,e="width"===b?a.offsetWidth:a.offsetHeight,f=wb(a),g="border-box"===o.css(a,"boxSizing",!1,f);if(0>=e||null==e){if(e=xb(a,b,f),(0>e||null==e)&&(e=a.style[b]),vb.test(e))return e;d=g&&(l.boxSizingReliable()||e===a.style[b]),e=parseFloat(e)||0}return e+Hb(a,b,c||(g?"border":"content"),d,f)+"px"}function Jb(a,b){for(var c,d,e,f=[],g=0,h=a.length;h>g;g++)d=a[g],d.style&&(f[g]=L.get(d,"olddisplay"),c=d.style.display,b?(f[g]||"none"!==c||(d.style.display=""),""===d.style.display&&S(d)&&(f[g]=L.access(d,"olddisplay",tb(d.nodeName)))):f[g]||(e=S(d),(c&&"none"!==c||!e)&&L.set(d,"olddisplay",e?c:o.css(d,"display"))));for(g=0;h>g;g++)d=a[g],d.style&&(b&&"none"!==d.style.display&&""!==d.style.display||(d.style.display=b?f[g]||"":"none"));return a}o.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=xb(a,"opacity");return""===c?"1":c}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=o.camelCase(b),i=a.style;return b=o.cssProps[h]||(o.cssProps[h]=Fb(i,h)),g=o.cssHooks[b]||o.cssHooks[h],void 0===c?g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:i[b]:(f=typeof c,"string"===f&&(e=Bb.exec(c))&&(c=(e[1]+1)*e[2]+parseFloat(o.css(a,b)),f="number"),null!=c&&c===c&&("number"!==f||o.cssNumber[h]||(c+="px"),l.clearCloneStyle||""!==c||0!==b.indexOf("background")||(i[b]="inherit"),g&&"set"in g&&void 0===(c=g.set(a,c,d))||(i[b]="",i[b]=c)),void 0)}},css:function(a,b,c,d){var e,f,g,h=o.camelCase(b);return b=o.cssProps[h]||(o.cssProps[h]=Fb(a.style,h)),g=o.cssHooks[b]||o.cssHooks[h],g&&"get"in g&&(e=g.get(a,!0,c)),void 0===e&&(e=xb(a,b,d)),"normal"===e&&b in Db&&(e=Db[b]),""===c||c?(f=parseFloat(e),c===!0||o.isNumeric(f)?f||0:e):e}}),o.each(["height","width"],function(a,b){o.cssHooks[b]={get:function(a,c,d){return c?0===a.offsetWidth&&zb.test(o.css(a,"display"))?o.swap(a,Cb,function(){return Ib(a,b,d)}):Ib(a,b,d):void 0},set:function(a,c,d){var e=d&&wb(a);return Gb(a,c,d?Hb(a,b,d,"border-box"===o.css(a,"boxSizing",!1,e),e):0)}}}),o.cssHooks.marginRight=yb(l.reliableMarginRight,function(a,b){return b?o.swap(a,{display:"inline-block"},xb,[a,"marginRight"]):void 0}),o.each({margin:"",padding:"",border:"Width"},function(a,b){o.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];4>d;d++)e[a+R[d]+b]=f[d]||f[d-2]||f[0];return e}},ub.test(a)||(o.cssHooks[a+b].set=Gb)}),o.fn.extend({css:function(a,b){return J(this,function(a,b,c){var d,e,f={},g=0;if(o.isArray(b)){for(d=wb(a),e=b.length;e>g;g++)f[b[g]]=o.css(a,b[g],!1,d);return f}return void 0!==c?o.style(a,b,c):o.css(a,b)},a,b,arguments.length>1)},show:function(){return Jb(this,!0)},hide:function(){return Jb(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){S(this)?o(this).show():o(this).hide()})}});function Kb(a,b,c,d,e){return new Kb.prototype.init(a,b,c,d,e)}o.Tween=Kb,Kb.prototype={constructor:Kb,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(o.cssNumber[c]?"":"px")},cur:function(){var a=Kb.propHooks[this.prop];return a&&a.get?a.get(this):Kb.propHooks._default.get(this)},run:function(a){var b,c=Kb.propHooks[this.prop];return this.pos=b=this.options.duration?o.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Kb.propHooks._default.set(this),this}},Kb.prototype.init.prototype=Kb.prototype,Kb.propHooks={_default:{get:function(a){var b;return null==a.elem[a.prop]||a.elem.style&&null!=a.elem.style[a.prop]?(b=o.css(a.elem,a.prop,""),b&&"auto"!==b?b:0):a.elem[a.prop]},set:function(a){o.fx.step[a.prop]?o.fx.step[a.prop](a):a.elem.style&&(null!=a.elem.style[o.cssProps[a.prop]]||o.cssHooks[a.prop])?o.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},Kb.propHooks.scrollTop=Kb.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},o.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},o.fx=Kb.prototype.init,o.fx.step={};var Lb,Mb,Nb=/^(?:toggle|show|hide)$/,Ob=new RegExp("^(?:([+-])=|)("+Q+")([a-z%]*)$","i"),Pb=/queueHooks$/,Qb=[Vb],Rb={"*":[function(a,b){var c=this.createTween(a,b),d=c.cur(),e=Ob.exec(b),f=e&&e[3]||(o.cssNumber[a]?"":"px"),g=(o.cssNumber[a]||"px"!==f&&+d)&&Ob.exec(o.css(c.elem,a)),h=1,i=20;if(g&&g[3]!==f){f=f||g[3],e=e||[],g=+d||1;do h=h||".5",g/=h,o.style(c.elem,a,g+f);while(h!==(h=c.cur()/d)&&1!==h&&--i)}return e&&(g=c.start=+g||+d||0,c.unit=f,c.end=e[1]?g+(e[1]+1)*e[2]:+e[2]),c}]};function Sb(){return setTimeout(function(){Lb=void 0}),Lb=o.now()}function Tb(a,b){var c,d=0,e={height:a};for(b=b?1:0;4>d;d+=2-b)c=R[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function Ub(a,b,c){for(var d,e=(Rb[b]||[]).concat(Rb["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function Vb(a,b,c){var d,e,f,g,h,i,j,k=this,l={},m=a.style,n=a.nodeType&&S(a),p=L.get(a,"fxshow");c.queue||(h=o._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,k.always(function(){k.always(function(){h.unqueued--,o.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[m.overflow,m.overflowX,m.overflowY],j=o.css(a,"display"),"none"===j&&(j=tb(a.nodeName)),"inline"===j&&"none"===o.css(a,"float")&&(m.display="inline-block")),c.overflow&&(m.overflow="hidden",k.always(function(){m.overflow=c.overflow[0],m.overflowX=c.overflow[1],m.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],Nb.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(n?"hide":"show")){if("show"!==e||!p||void 0===p[d])continue;n=!0}l[d]=p&&p[d]||o.style(a,d)}if(!o.isEmptyObject(l)){p?"hidden"in p&&(n=p.hidden):p=L.access(a,"fxshow",{}),f&&(p.hidden=!n),n?o(a).show():k.done(function(){o(a).hide()}),k.done(function(){var b;L.remove(a,"fxshow");for(b in l)o.style(a,b,l[b])});for(d in l)g=Ub(n?p[d]:0,d,k),d in p||(p[d]=g.start,n&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function Wb(a,b){var c,d,e,f,g;for(c in a)if(d=o.camelCase(c),e=b[d],f=a[c],o.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=o.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function Xb(a,b,c){var d,e,f=0,g=Qb.length,h=o.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=Lb||Sb(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;i>g;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),1>f&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:o.extend({},b),opts:o.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:Lb||Sb(),duration:c.duration,tweens:[],createTween:function(b,c){var d=o.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;d>c;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;for(Wb(k,j.opts.specialEasing);g>f;f++)if(d=Qb[f].call(j,a,k,j.opts))return d;return o.map(k,Ub,j),o.isFunction(j.opts.start)&&j.opts.start.call(a,j),o.fx.timer(o.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}o.Animation=o.extend(Xb,{tweener:function(a,b){o.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");for(var c,d=0,e=a.length;e>d;d++)c=a[d],Rb[c]=Rb[c]||[],Rb[c].unshift(b)},prefilter:function(a,b){b?Qb.unshift(a):Qb.push(a)}}),o.speed=function(a,b,c){var d=a&&"object"==typeof a?o.extend({},a):{complete:c||!c&&b||o.isFunction(a)&&a,duration:a,easing:c&&b||b&&!o.isFunction(b)&&b};return d.duration=o.fx.off?0:"number"==typeof d.duration?d.duration:d.duration in o.fx.speeds?o.fx.speeds[d.duration]:o.fx.speeds._default,(null==d.queue||d.queue===!0)&&(d.queue="fx"),d.old=d.complete,d.complete=function(){o.isFunction(d.old)&&d.old.call(this),d.queue&&o.dequeue(this,d.queue)},d},o.fn.extend({fadeTo:function(a,b,c,d){return this.filter(S).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=o.isEmptyObject(a),f=o.speed(b,c,d),g=function(){var b=Xb(this,o.extend({},a),f);(e||L.get(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=o.timers,g=L.get(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&Pb.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));(b||!c)&&o.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=L.get(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=o.timers,g=d?d.length:0;for(c.finish=!0,o.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;g>b;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),o.each(["toggle","show","hide"],function(a,b){var c=o.fn[b];o.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(Tb(b,!0),a,d,e)}}),o.each({slideDown:Tb("show"),slideUp:Tb("hide"),slideToggle:Tb("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){o.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),o.timers=[],o.fx.tick=function(){var a,b=0,c=o.timers;for(Lb=o.now();b<c.length;b++)a=c[b],a()||c[b]!==a||c.splice(b--,1);c.length||o.fx.stop(),Lb=void 0},o.fx.timer=function(a){o.timers.push(a),a()?o.fx.start():o.timers.pop()},o.fx.interval=13,o.fx.start=function(){Mb||(Mb=setInterval(o.fx.tick,o.fx.interval))},o.fx.stop=function(){clearInterval(Mb),Mb=null},o.fx.speeds={slow:600,fast:200,_default:400},o.fn.delay=function(a,b){return a=o.fx?o.fx.speeds[a]||a:a,b=b||"fx",this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},function(){var a=m.createElement("input"),b=m.createElement("select"),c=b.appendChild(m.createElement("option"));a.type="checkbox",l.checkOn=""!==a.value,l.optSelected=c.selected,b.disabled=!0,l.optDisabled=!c.disabled,a=m.createElement("input"),a.value="t",a.type="radio",l.radioValue="t"===a.value}();var Yb,Zb,$b=o.expr.attrHandle;o.fn.extend({attr:function(a,b){return J(this,o.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){o.removeAttr(this,a)})}}),o.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(a&&3!==f&&8!==f&&2!==f)return typeof a.getAttribute===U?o.prop(a,b,c):(1===f&&o.isXMLDoc(a)||(b=b.toLowerCase(),d=o.attrHooks[b]||(o.expr.match.bool.test(b)?Zb:Yb)),void 0===c?d&&"get"in d&&null!==(e=d.get(a,b))?e:(e=o.find.attr(a,b),null==e?void 0:e):null!==c?d&&"set"in d&&void 0!==(e=d.set(a,c,b))?e:(a.setAttribute(b,c+""),c):void o.removeAttr(a,b))},removeAttr:function(a,b){var c,d,e=0,f=b&&b.match(E);if(f&&1===a.nodeType)while(c=f[e++])d=o.propFix[c]||c,o.expr.match.bool.test(c)&&(a[d]=!1),a.removeAttribute(c)},attrHooks:{type:{set:function(a,b){if(!l.radioValue&&"radio"===b&&o.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}}}),Zb={set:function(a,b,c){return b===!1?o.removeAttr(a,c):a.setAttribute(c,c),c}},o.each(o.expr.match.bool.source.match(/\w+/g),function(a,b){var c=$b[b]||o.find.attr;$b[b]=function(a,b,d){var e,f; +return d||(f=$b[b],$b[b]=e,e=null!=c(a,b,d)?b.toLowerCase():null,$b[b]=f),e}});var _b=/^(?:input|select|textarea|button)$/i;o.fn.extend({prop:function(a,b){return J(this,o.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[o.propFix[a]||a]})}}),o.extend({propFix:{"for":"htmlFor","class":"className"},prop:function(a,b,c){var d,e,f,g=a.nodeType;if(a&&3!==g&&8!==g&&2!==g)return f=1!==g||!o.isXMLDoc(a),f&&(b=o.propFix[b]||b,e=o.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){return a.hasAttribute("tabindex")||_b.test(a.nodeName)||a.href?a.tabIndex:-1}}}}),l.optSelected||(o.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null}}),o.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){o.propFix[this.toLowerCase()]=this});var ac=/[\t\r\n\f]/g;o.fn.extend({addClass:function(a){var b,c,d,e,f,g,h="string"==typeof a&&a,i=0,j=this.length;if(o.isFunction(a))return this.each(function(b){o(this).addClass(a.call(this,b,this.className))});if(h)for(b=(a||"").match(E)||[];j>i;i++)if(c=this[i],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(ac," "):" ")){f=0;while(e=b[f++])d.indexOf(" "+e+" ")<0&&(d+=e+" ");g=o.trim(d),c.className!==g&&(c.className=g)}return this},removeClass:function(a){var b,c,d,e,f,g,h=0===arguments.length||"string"==typeof a&&a,i=0,j=this.length;if(o.isFunction(a))return this.each(function(b){o(this).removeClass(a.call(this,b,this.className))});if(h)for(b=(a||"").match(E)||[];j>i;i++)if(c=this[i],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(ac," "):"")){f=0;while(e=b[f++])while(d.indexOf(" "+e+" ")>=0)d=d.replace(" "+e+" "," ");g=a?o.trim(d):"",c.className!==g&&(c.className=g)}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):this.each(o.isFunction(a)?function(c){o(this).toggleClass(a.call(this,c,this.className,b),b)}:function(){if("string"===c){var b,d=0,e=o(this),f=a.match(E)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else(c===U||"boolean"===c)&&(this.className&&L.set(this,"__className__",this.className),this.className=this.className||a===!1?"":L.get(this,"__className__")||"")})},hasClass:function(a){for(var b=" "+a+" ",c=0,d=this.length;d>c;c++)if(1===this[c].nodeType&&(" "+this[c].className+" ").replace(ac," ").indexOf(b)>=0)return!0;return!1}});var bc=/\r/g;o.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=o.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,o(this).val()):a,null==e?e="":"number"==typeof e?e+="":o.isArray(e)&&(e=o.map(e,function(a){return null==a?"":a+""})),b=o.valHooks[this.type]||o.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=o.valHooks[e.type]||o.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(bc,""):null==c?"":c)}}}),o.extend({valHooks:{select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type||0>e,g=f?null:[],h=f?e+1:d.length,i=0>e?h:f?e:0;h>i;i++)if(c=d[i],!(!c.selected&&i!==e||(l.optDisabled?c.disabled:null!==c.getAttribute("disabled"))||c.parentNode.disabled&&o.nodeName(c.parentNode,"optgroup"))){if(b=o(c).val(),f)return b;g.push(b)}return g},set:function(a,b){var c,d,e=a.options,f=o.makeArray(b),g=e.length;while(g--)d=e[g],(d.selected=o.inArray(o(d).val(),f)>=0)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),o.each(["radio","checkbox"],function(){o.valHooks[this]={set:function(a,b){return o.isArray(b)?a.checked=o.inArray(o(a).val(),b)>=0:void 0}},l.checkOn||(o.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})}),o.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){o.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),o.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}});var cc=o.now(),dc=/\?/;o.parseJSON=function(a){return JSON.parse(a+"")},o.parseXML=function(a){var b,c;if(!a||"string"!=typeof a)return null;try{c=new DOMParser,b=c.parseFromString(a,"text/xml")}catch(d){b=void 0}return(!b||b.getElementsByTagName("parsererror").length)&&o.error("Invalid XML: "+a),b};var ec,fc,gc=/#.*$/,hc=/([?&])_=[^&]*/,ic=/^(.*?):[ \t]*([^\r\n]*)$/gm,jc=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,kc=/^(?:GET|HEAD)$/,lc=/^\/\//,mc=/^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,nc={},oc={},pc="*/".concat("*");try{fc=location.href}catch(qc){fc=m.createElement("a"),fc.href="",fc=fc.href}ec=mc.exec(fc.toLowerCase())||[];function rc(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(E)||[];if(o.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function sc(a,b,c,d){var e={},f=a===oc;function g(h){var i;return e[h]=!0,o.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function tc(a,b){var c,d,e=o.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&o.extend(!0,a,d),a}function uc(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}return f?(f!==i[0]&&i.unshift(f),c[f]):void 0}function vc(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}o.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:fc,type:"GET",isLocal:jc.test(ec[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":pc,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":o.parseJSON,"text xml":o.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?tc(tc(a,o.ajaxSettings),b):tc(o.ajaxSettings,a)},ajaxPrefilter:rc(nc),ajaxTransport:rc(oc),ajax:function(a,b){"object"==typeof a&&(b=a,a=void 0),b=b||{};var c,d,e,f,g,h,i,j,k=o.ajaxSetup({},b),l=k.context||k,m=k.context&&(l.nodeType||l.jquery)?o(l):o.event,n=o.Deferred(),p=o.Callbacks("once memory"),q=k.statusCode||{},r={},s={},t=0,u="canceled",v={readyState:0,getResponseHeader:function(a){var b;if(2===t){if(!f){f={};while(b=ic.exec(e))f[b[1].toLowerCase()]=b[2]}b=f[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return 2===t?e:null},setRequestHeader:function(a,b){var c=a.toLowerCase();return t||(a=s[c]=s[c]||a,r[a]=b),this},overrideMimeType:function(a){return t||(k.mimeType=a),this},statusCode:function(a){var b;if(a)if(2>t)for(b in a)q[b]=[q[b],a[b]];else v.always(a[v.status]);return this},abort:function(a){var b=a||u;return c&&c.abort(b),x(0,b),this}};if(n.promise(v).complete=p.add,v.success=v.done,v.error=v.fail,k.url=((a||k.url||fc)+"").replace(gc,"").replace(lc,ec[1]+"//"),k.type=b.method||b.type||k.method||k.type,k.dataTypes=o.trim(k.dataType||"*").toLowerCase().match(E)||[""],null==k.crossDomain&&(h=mc.exec(k.url.toLowerCase()),k.crossDomain=!(!h||h[1]===ec[1]&&h[2]===ec[2]&&(h[3]||("http:"===h[1]?"80":"443"))===(ec[3]||("http:"===ec[1]?"80":"443")))),k.data&&k.processData&&"string"!=typeof k.data&&(k.data=o.param(k.data,k.traditional)),sc(nc,k,b,v),2===t)return v;i=k.global,i&&0===o.active++&&o.event.trigger("ajaxStart"),k.type=k.type.toUpperCase(),k.hasContent=!kc.test(k.type),d=k.url,k.hasContent||(k.data&&(d=k.url+=(dc.test(d)?"&":"?")+k.data,delete k.data),k.cache===!1&&(k.url=hc.test(d)?d.replace(hc,"$1_="+cc++):d+(dc.test(d)?"&":"?")+"_="+cc++)),k.ifModified&&(o.lastModified[d]&&v.setRequestHeader("If-Modified-Since",o.lastModified[d]),o.etag[d]&&v.setRequestHeader("If-None-Match",o.etag[d])),(k.data&&k.hasContent&&k.contentType!==!1||b.contentType)&&v.setRequestHeader("Content-Type",k.contentType),v.setRequestHeader("Accept",k.dataTypes[0]&&k.accepts[k.dataTypes[0]]?k.accepts[k.dataTypes[0]]+("*"!==k.dataTypes[0]?", "+pc+"; q=0.01":""):k.accepts["*"]);for(j in k.headers)v.setRequestHeader(j,k.headers[j]);if(k.beforeSend&&(k.beforeSend.call(l,v,k)===!1||2===t))return v.abort();u="abort";for(j in{success:1,error:1,complete:1})v[j](k[j]);if(c=sc(oc,k,b,v)){v.readyState=1,i&&m.trigger("ajaxSend",[v,k]),k.async&&k.timeout>0&&(g=setTimeout(function(){v.abort("timeout")},k.timeout));try{t=1,c.send(r,x)}catch(w){if(!(2>t))throw w;x(-1,w)}}else x(-1,"No Transport");function x(a,b,f,h){var j,r,s,u,w,x=b;2!==t&&(t=2,g&&clearTimeout(g),c=void 0,e=h||"",v.readyState=a>0?4:0,j=a>=200&&300>a||304===a,f&&(u=uc(k,v,f)),u=vc(k,u,v,j),j?(k.ifModified&&(w=v.getResponseHeader("Last-Modified"),w&&(o.lastModified[d]=w),w=v.getResponseHeader("etag"),w&&(o.etag[d]=w)),204===a||"HEAD"===k.type?x="nocontent":304===a?x="notmodified":(x=u.state,r=u.data,s=u.error,j=!s)):(s=x,(a||!x)&&(x="error",0>a&&(a=0))),v.status=a,v.statusText=(b||x)+"",j?n.resolveWith(l,[r,x,v]):n.rejectWith(l,[v,x,s]),v.statusCode(q),q=void 0,i&&m.trigger(j?"ajaxSuccess":"ajaxError",[v,k,j?r:s]),p.fireWith(l,[v,x]),i&&(m.trigger("ajaxComplete",[v,k]),--o.active||o.event.trigger("ajaxStop")))}return v},getJSON:function(a,b,c){return o.get(a,b,c,"json")},getScript:function(a,b){return o.get(a,void 0,b,"script")}}),o.each(["get","post"],function(a,b){o[b]=function(a,c,d,e){return o.isFunction(c)&&(e=e||d,d=c,c=void 0),o.ajax({url:a,type:b,dataType:e,data:c,success:d})}}),o.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){o.fn[b]=function(a){return this.on(b,a)}}),o._evalUrl=function(a){return o.ajax({url:a,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})},o.fn.extend({wrapAll:function(a){var b;return o.isFunction(a)?this.each(function(b){o(this).wrapAll(a.call(this,b))}):(this[0]&&(b=o(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this)},wrapInner:function(a){return this.each(o.isFunction(a)?function(b){o(this).wrapInner(a.call(this,b))}:function(){var b=o(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=o.isFunction(a);return this.each(function(c){o(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){o.nodeName(this,"body")||o(this).replaceWith(this.childNodes)}).end()}}),o.expr.filters.hidden=function(a){return a.offsetWidth<=0&&a.offsetHeight<=0},o.expr.filters.visible=function(a){return!o.expr.filters.hidden(a)};var wc=/%20/g,xc=/\[\]$/,yc=/\r?\n/g,zc=/^(?:submit|button|image|reset|file)$/i,Ac=/^(?:input|select|textarea|keygen)/i;function Bc(a,b,c,d){var e;if(o.isArray(b))o.each(b,function(b,e){c||xc.test(a)?d(a,e):Bc(a+"["+("object"==typeof e?b:"")+"]",e,c,d)});else if(c||"object"!==o.type(b))d(a,b);else for(e in b)Bc(a+"["+e+"]",b[e],c,d)}o.param=function(a,b){var c,d=[],e=function(a,b){b=o.isFunction(b)?b():null==b?"":b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};if(void 0===b&&(b=o.ajaxSettings&&o.ajaxSettings.traditional),o.isArray(a)||a.jquery&&!o.isPlainObject(a))o.each(a,function(){e(this.name,this.value)});else for(c in a)Bc(c,a[c],b,e);return d.join("&").replace(wc,"+")},o.fn.extend({serialize:function(){return o.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=o.prop(this,"elements");return a?o.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!o(this).is(":disabled")&&Ac.test(this.nodeName)&&!zc.test(a)&&(this.checked||!T.test(a))}).map(function(a,b){var c=o(this).val();return null==c?null:o.isArray(c)?o.map(c,function(a){return{name:b.name,value:a.replace(yc,"\r\n")}}):{name:b.name,value:c.replace(yc,"\r\n")}}).get()}}),o.ajaxSettings.xhr=function(){try{return new XMLHttpRequest}catch(a){}};var Cc=0,Dc={},Ec={0:200,1223:204},Fc=o.ajaxSettings.xhr();a.ActiveXObject&&o(a).on("unload",function(){for(var a in Dc)Dc[a]()}),l.cors=!!Fc&&"withCredentials"in Fc,l.ajax=Fc=!!Fc,o.ajaxTransport(function(a){var b;return l.cors||Fc&&!a.crossDomain?{send:function(c,d){var e,f=a.xhr(),g=++Cc;if(f.open(a.type,a.url,a.async,a.username,a.password),a.xhrFields)for(e in a.xhrFields)f[e]=a.xhrFields[e];a.mimeType&&f.overrideMimeType&&f.overrideMimeType(a.mimeType),a.crossDomain||c["X-Requested-With"]||(c["X-Requested-With"]="XMLHttpRequest");for(e in c)f.setRequestHeader(e,c[e]);b=function(a){return function(){b&&(delete Dc[g],b=f.onload=f.onerror=null,"abort"===a?f.abort():"error"===a?d(f.status,f.statusText):d(Ec[f.status]||f.status,f.statusText,"string"==typeof f.responseText?{text:f.responseText}:void 0,f.getAllResponseHeaders()))}},f.onload=b(),f.onerror=b("error"),b=Dc[g]=b("abort"),f.send(a.hasContent&&a.data||null)},abort:function(){b&&b()}}:void 0}),o.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(a){return o.globalEval(a),a}}}),o.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),o.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(d,e){b=o("<script>").prop({async:!0,charset:a.scriptCharset,src:a.url}).on("load error",c=function(a){b.remove(),c=null,a&&e("error"===a.type?404:200,a.type)}),m.head.appendChild(b[0])},abort:function(){c&&c()}}}});var Gc=[],Hc=/(=)\?(?=&|$)|\?\?/;o.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=Gc.pop()||o.expando+"_"+cc++;return this[a]=!0,a}}),o.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(Hc.test(b.url)?"url":"string"==typeof b.data&&!(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&Hc.test(b.data)&&"data");return h||"jsonp"===b.dataTypes[0]?(e=b.jsonpCallback=o.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(Hc,"$1"+e):b.jsonp!==!1&&(b.url+=(dc.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||o.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,Gc.push(e)),g&&o.isFunction(f)&&f(g[0]),g=f=void 0}),"script"):void 0}),o.parseHTML=function(a,b,c){if(!a||"string"!=typeof a)return null;"boolean"==typeof b&&(c=b,b=!1),b=b||m;var d=v.exec(a),e=!c&&[];return d?[b.createElement(d[1])]:(d=o.buildFragment([a],b,e),e&&e.length&&o(e).remove(),o.merge([],d.childNodes))};var Ic=o.fn.load;o.fn.load=function(a,b,c){if("string"!=typeof a&&Ic)return Ic.apply(this,arguments);var d,e,f,g=this,h=a.indexOf(" ");return h>=0&&(d=a.slice(h),a=a.slice(0,h)),o.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(e="POST"),g.length>0&&o.ajax({url:a,type:e,dataType:"html",data:b}).done(function(a){f=arguments,g.html(d?o("<div>").append(o.parseHTML(a)).find(d):a)}).complete(c&&function(a,b){g.each(c,f||[a.responseText,b,a])}),this},o.expr.filters.animated=function(a){return o.grep(o.timers,function(b){return a===b.elem}).length};var Jc=a.document.documentElement;function Kc(a){return o.isWindow(a)?a:9===a.nodeType&&a.defaultView}o.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=o.css(a,"position"),l=o(a),m={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=o.css(a,"top"),i=o.css(a,"left"),j=("absolute"===k||"fixed"===k)&&(f+i).indexOf("auto")>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),o.isFunction(b)&&(b=b.call(a,c,h)),null!=b.top&&(m.top=b.top-h.top+g),null!=b.left&&(m.left=b.left-h.left+e),"using"in b?b.using.call(a,m):l.css(m)}},o.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){o.offset.setOffset(this,a,b)});var b,c,d=this[0],e={top:0,left:0},f=d&&d.ownerDocument;if(f)return b=f.documentElement,o.contains(b,d)?(typeof d.getBoundingClientRect!==U&&(e=d.getBoundingClientRect()),c=Kc(f),{top:e.top+c.pageYOffset-b.clientTop,left:e.left+c.pageXOffset-b.clientLeft}):e},position:function(){if(this[0]){var a,b,c=this[0],d={top:0,left:0};return"fixed"===o.css(c,"position")?b=c.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),o.nodeName(a[0],"html")||(d=a.offset()),d.top+=o.css(a[0],"borderTopWidth",!0),d.left+=o.css(a[0],"borderLeftWidth",!0)),{top:b.top-d.top-o.css(c,"marginTop",!0),left:b.left-d.left-o.css(c,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||Jc;while(a&&!o.nodeName(a,"html")&&"static"===o.css(a,"position"))a=a.offsetParent;return a||Jc})}}),o.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(b,c){var d="pageYOffset"===c;o.fn[b]=function(e){return J(this,function(b,e,f){var g=Kc(b);return void 0===f?g?g[c]:b[e]:void(g?g.scrollTo(d?a.pageXOffset:f,d?f:a.pageYOffset):b[e]=f)},b,e,arguments.length,null)}}),o.each(["top","left"],function(a,b){o.cssHooks[b]=yb(l.pixelPosition,function(a,c){return c?(c=xb(a,b),vb.test(c)?o(a).position()[b]+"px":c):void 0})}),o.each({Height:"height",Width:"width"},function(a,b){o.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){o.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return J(this,function(b,c,d){var e;return o.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?o.css(b,c,g):o.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),o.fn.size=function(){return this.length},o.fn.andSelf=o.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return o});var Lc=a.jQuery,Mc=a.$;return o.noConflict=function(b){return a.$===o&&(a.$=Mc),b&&a.jQuery===o&&(a.jQuery=Lc),o},typeof b===U&&(a.jQuery=a.$=o),o}); diff --git a/www/vincent.demeester.fr/.fancyindex/new.css b/www/vincent.demeester.fr/.fancyindex/new.css @@ -0,0 +1,605 @@ +:root { + ---font-sans: 'Ubuntu Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + ---font-mono: 'Ubuntu Mono', Consolas, monaco, 'Liberation Mono', 'Courier New', Courier, monospace; + ---tx-1: #000000; + ---tx-2: #1A1A1A; + ---tx-3: #666666; + ---tx-4: #999999; + ---bg-1: #FFFFFF; + ---bg-2: #F6F8FA; + ---bg-3: #E5E7EB; + ---lk-1: #0070F3; + ---lk-2: #0366D6; + ---lk-tx: #FFFFFF; + ---ac-1: #79FFE1; + ---ac-tx: #0C4047; + + --content-box-padding: 0.5rem; + --aside-width: 12.5rem; +} + +@media (prefers-color-scheme: dark) { + :root { + ---tx-1: #ffffff; + ---tx-2: #eeeeee; + ---tx-3: #cccccc; + ---tx-4: #aaaaaa; + ---bg-1: #000000; + ---bg-2: #111111; + ---bg-3: #222222; + ---lk-1: #3291FF; + ---lk-2: #0070F3; + ---lk-tx: #FFFFFF; + ---ac-1: #7928CA; + ---ac-tx: #FFFFFF; + } +} + +* { + /* Reset margins and padding */ + margin: 0; + padding: 0; +} + +address, +area, +article, +aside, +audio, +blockquote, +datalist, +details, +dl, +fieldset, +figure, +form, +input, +iframe, +img, +meter, +nav, +ol, +optgroup, +option, +output, +p, +pre, +progress, +ruby, +section, +table, +textarea, +ul, +video { + /* Margins for most elements */ + margin-bottom: 1rem; +} + +html,input,select,button { + /* Set body font family and some finicky elements */ + font-family: var(---font-sans); +} + +body { + /* Center body in page */ + margin: 0 auto; + max-width: 70rem; + padding: 2rem; + border-radius: 6px; + overflow-x: hidden; + word-break: break-word; + overflow-wrap: break-word; + background: var(---bg-1); + + /* Main body text */ + color: var(---tx-2); + font-size: 1.03rem; + line-height: 1.5; +} + +::selection { + /* Set background color for selected text */ + background: var(---ac-1); + color: var(---ac-tx); +} + +h1,h2,h3,h4,h5,h6 { + line-height: 1; + color: var(---tx-1); + padding-top: .875rem; +} + +h1, +h2, +h3 { + color: var(---tx-1); + padding-bottom: 2px; + margin-bottom: 8px; + border-bottom: 1px solid var(---bg-2); +} + +h4, +h5, +h6 { + margin-bottom: .3rem; +} + +h1 { + font-size: 2.25rem; +} + +h2 { + font-size: 1.85rem; +} + +h3 { + font-size: 1.55rem; +} + +h4 { + font-size: 1.25rem; +} + +h5 { + font-size: 1rem; +} + +h6 { + font-size: .875rem; +} + +a { + color: var(---lk-1); +} + +a:hover { + color: var(---lk-2); +} + +abbr:hover { + /* Set the '?' cursor while hovering an abbreviation */ + cursor: help; +} + +blockquote { + padding: 1.5rem; + background: var(---bg-2); + border-left: 5px solid var(---bg-3); +} + +abbr { + cursor: help; +} + +blockquote *:last-child { + padding-bottom: 0; + margin-bottom: 0; +} + +header { + background: var(---bg-2); + border-bottom: 1px solid var(---bg-3); + padding: 1rem 1.5rem; + + /* This sets the right and left margins to cancel out the body's margins. It's width is still the same, but the background stretches across the page's width. */ + + margin: -2rem calc(0px - (50vw - 50%)) 2rem; + + /* Shorthand for: + + margin-top: -2rem; + margin-bottom: 2rem; + + margin-left: calc(0px - (50vw - 50%)); + margin-right: calc(0px - (50vw - 50%)); */ + + padding-left: calc(50vw - 50%); + padding-right: calc(50vw - 50%); +} + +header img#sitelogo { + margin-bottom: 0; + margin-right: 1em; +} + +header h1, +header h2, +header h3 { + padding-bottom: 0; + border-bottom: 0; +} + +header > *:first-child { + margin-top: 0; + padding-top: 0; +} + +header > *:last-child { + margin-bottom: 0; +} + +main header { + background: var(---bg-3); +} + +a button, +button, +input[type="submit"], +input[type="reset"], +input[type="button"] { + font-size: 1rem; + display: inline-block; + padding: 6px 12px; + text-align: center; + text-decoration: none; + white-space: nowrap; + background: var(---lk-1); + color: var(---lk-tx); + border: 0; + border-radius: 4px; + box-sizing: border-box; + cursor: pointer; + color: var(---lk-tx); +} + +a button[disabled], +button[disabled], +input[type="submit"][disabled], +input[type="reset"][disabled], +input[type="button"][disabled] { + cursor: default; + opacity: .5; + + /* Set the [X] cursor while hovering a disabled link */ + cursor: not-allowed; +} + +.button:focus, +.button:hover, +button:focus, +button:hover, +input[type="submit"]:focus, +input[type="submit"]:hover, +input[type="reset"]:focus, +input[type="reset"]:hover, +input[type="button"]:focus, +input[type="button"]:hover { + background: var(---lk-2); +} + +code, +pre, +kbd, +samp { + /* Set the font family for monospaced elements */ + font-family: var(---font-mono); +} + +code, +samp, +kbd, +pre { + /* The main preformatted style. This is changed slightly across different cases. */ + background: var(---bg-2); + border: 1px solid var(---bg-3); + border-radius: 4px; + padding: 3px 6px; +} + +kbd { + /* Makes the kbd element look like a keyboard key */ + border-bottom: 3px solid var(---bg-3); +} + +pre { + padding: 1rem 1.4rem; + max-width: 100%; + overflow: auto; +} + +pre.src { + position: relative; +} +pre.src:before { + /*display: none;*/ + position: absolute; + background-color: var(---bg-3); + /*color: #f3f4f4;*/ + top: 0; + right: 0; + padding: 0.2rem 0.5rem; +} + +pre code { + /* When <code> is in a <pre>, reset it's formatting to blend in */ + background: inherit; + font-size: inherit; + color: inherit; + border: 0; + padding: 0; + margin: 0; +} + +code pre { + /* When <pre> is in a <code>, reset it's formatting to blend in */ + display: inline; + background: inherit; + font-size: inherit; + color: inherit; + border: 0; + padding: 0; + margin: 0; +} + +details { + /* Make the <details> look more "clickable" */ + padding: .6rem 1rem; + background: var(---bg-2); + border: 1px solid var(---bg-3); + border-radius: 4px; +} + +summary { + /* Makes the <summary> look more like a "clickable" link with the pointer cursor */ + cursor: pointer; + font-weight: bold; +} + +details[open] { + /* Adjust the <details> padding while open */ + padding-bottom: .75rem; +} + +details[open] summary { + /* Adjust the <details> padding while open */ + margin-bottom: 6px; +} + +details[open]>*:last-child { + /* Resets the bottom margin of the last element in the <details> while <details> is opened. This prevents double margins/paddings. */ + margin-bottom: 0; +} + +dt { + font-weight: bold; +} + +dd::before { + /* Add an arrow to data table definitions */ + content: '→ '; +} + +hr { + /* Reset the border of the <hr> separator, then set a better line */ + border: 0; + border-bottom: 1px solid var(---bg-3); + margin: 1rem auto; +} + +fieldset { + margin-top: 1rem; + padding: 2rem; + border: 1px solid var(---bg-3); + border-radius: 4px; +} + +legend { + padding: auto .5rem; +} + +table { + /* border-collapse sets the table's elements to share borders, rather than floating as separate "boxes". */ + border-collapse: collapse; + width: 100% +} + +td, +th { + border: 1px solid var(---bg-3); + text-align: left; + padding: .5rem; +} + +th { + background: var(---bg-2); +} + +tr:nth-child(even) { + /* Set every other cell slightly darker. Improves readability. */ + background: var(---bg-2); +} + +table caption { + font-weight: bold; + margin-bottom: .5rem; +} + +textarea { + /* Don't let the <textarea> extend off the screen naturally or when dragged by the user */ + max-width: 100%; +} + +ol, +ul { + /* Replace the browser default padding */ + padding-left: 2rem; +} + +li { + margin-top: .4rem; +} + +ul ul, +ol ul, +ul ol, +ol ol { + margin-bottom: 0; +} + +mark { + padding: 3px 6px; + background: var(---ac-1); + color: var(---ac-tx); +} + +textarea, +select, +input { + padding: 6px 12px; + margin-bottom: .5rem; + background: var(---bg-2); + color: var(---tx-2); + border: 1px solid var(---bg-3); + border-radius: 4px; + box-shadow: none; + box-sizing: border-box; +} + +img { + max-width: 100%; +} + +aside { + --aside-offset: 1rem; + --aside-offset-lineheight: 1.5rem; + background-color: var(---bg-2); + padding: calc(var(--content-box-padding) * 0.5) calc(var(--content-box-padding) * 2); + padding-top: 1em; + margin-bottom: 0em; + float: right; + width: var(--aside-width); + font-size: 0.9rem; + line-height: 1.25rem; + border: 1px solid var(--bg-3); +} + +figure figcaption { + text-align: center; + color: var(---tx-2); + font-style: italic; + margin: 0 2rem; +} + +footer { + text-align: center; + color: var(---tx-4); +} + +footer a { + color: var(---tx-3); +} + +section img { + display: block; + margin-left: auto; + margin-right: auto; +} + +section img.shadow { + box-shadow: 0 28px 50px rgba(0,0,0,0.16); +} + +div.drawer:before { + content: ""; + position: absolute; + top: 0; + bottom: 0; + left: 4.4rem; + width: 1px; + background-color: #555; +} + +div.drawer { + display: flex; + padding: 0; + margin: 1rem; + padding-top: 1rem; + position: relative; + color: #555; +} + +div.drawer h6 { + box-sizing: border-box; + font-style: italic; + flex: 0 0 5rem; + margin:0; + padding:0; + align-self: center; + text-align: right; + padding-right: 1.2rem; +} + +div.drawer.info:before { + background-color: #00529B; +} +div.drawer.info { + color: #00529B; +} + +div.drawer.note:before { + background-color: #9F6000; +} +div.drawer.note { + color: #9F6000; +} +div.drawer.tip:before { + background-color: #4F8A10; +} +div.drawer.tip { + color: #4F8A10; +} +div.drawer.warning:before { + background-color: #D8000C; +} +div.drawer.warning { + color: #D8000C; +} +div.drawer.results:before { + background-color: black; +} +div.drawer.results { + font-family:monospace,monospace; + font-family: var(---font-mono); + color: black; + width: 100% !important; +} +div.drawer.results p { + display: block; + unicode-bidi: embed; + white-space: pre; + margin-top: 0; +} +div.drawer.results pre { + background-color: inherit; + border: 0; +} +div.drawer.results pre.src:before { + background-color: #eee; + color: #555; +} + +.tag { + font-family:var(---font-mono); + font-size: 1rem; +} +.tag span { + padding:.3em; + float:right; + margin-right:.5em; + border:1px solid #bbb; + border-radius:3px; + background-clip:padding-box; + color:#333; + background-color:#eee; + line-height:1rem; +} + +.todo { + font-family: monospace; color: red; +} +.done { + font-family: monospace; color: green; +} +.priority { font-family: monospace; color: orange; } +.timestamp { color: #bebebe; } +.timestamp-kwd { color: #5f9ea0; } diff --git a/www/vincent.demeester.fr/.fancyindex/showdown.min.js b/www/vincent.demeester.fr/.fancyindex/showdown.min.js @@ -0,0 +1,3 @@ +/*! showdown v 1.8.6 - 22-12-2017 */ +(function(){function g(g){"use strict";var A={omitExtraWLInCodeBlocks:{defaultValue:!1,describe:"Omit the default extra whiteline added to code blocks",type:"boolean"},noHeaderId:{defaultValue:!1,describe:"Turn on/off generated header id",type:"boolean"},prefixHeaderId:{defaultValue:!1,describe:"Add a prefix to the generated header ids. Passing a string will prefix that string to the header id. Setting to true will add a generic 'section-' prefix",type:"string"},rawPrefixHeaderId:{defaultValue:!1,describe:'Setting this option to true will prevent showdown from modifying the prefix. This might result in malformed IDs (if, for instance, the " char is used in the prefix)',type:"boolean"},ghCompatibleHeaderId:{defaultValue:!1,describe:"Generate header ids compatible with github style (spaces are replaced with dashes, a bunch of non alphanumeric chars are removed)",type:"boolean"},rawHeaderId:{defaultValue:!1,describe:"Remove only spaces, ' and \" from generated header ids (including prefixes), replacing them with dashes (-). WARNING: This might result in malformed ids",type:"boolean"},headerLevelStart:{defaultValue:!1,describe:"The header blocks level start",type:"integer"},parseImgDimensions:{defaultValue:!1,describe:"Turn on/off image dimension parsing",type:"boolean"},simplifiedAutoLink:{defaultValue:!1,describe:"Turn on/off GFM autolink style",type:"boolean"},excludeTrailingPunctuationFromURLs:{defaultValue:!1,describe:"Excludes trailing punctuation from links generated with autoLinking",type:"boolean"},literalMidWordUnderscores:{defaultValue:!1,describe:"Parse midword underscores as literal underscores",type:"boolean"},literalMidWordAsterisks:{defaultValue:!1,describe:"Parse midword asterisks as literal asterisks",type:"boolean"},strikethrough:{defaultValue:!1,describe:"Turn on/off strikethrough support",type:"boolean"},tables:{defaultValue:!1,describe:"Turn on/off tables support",type:"boolean"},tablesHeaderId:{defaultValue:!1,describe:"Add an id to table headers",type:"boolean"},ghCodeBlocks:{defaultValue:!0,describe:"Turn on/off GFM fenced code blocks support",type:"boolean"},tasklists:{defaultValue:!1,describe:"Turn on/off GFM tasklist support",type:"boolean"},smoothLivePreview:{defaultValue:!1,describe:"Prevents weird effects in live previews due to incomplete input",type:"boolean"},smartIndentationFix:{defaultValue:!1,description:"Tries to smartly fix indentation in es6 strings",type:"boolean"},disableForced4SpacesIndentedSublists:{defaultValue:!1,description:"Disables the requirement of indenting nested sublists by 4 spaces",type:"boolean"},simpleLineBreaks:{defaultValue:!1,description:"Parses simple line breaks as <br> (GFM Style)",type:"boolean"},requireSpaceBeforeHeadingText:{defaultValue:!1,description:"Makes adding a space between `#` and the header text mandatory (GFM Style)",type:"boolean"},ghMentions:{defaultValue:!1,description:"Enables github @mentions",type:"boolean"},ghMentionsLink:{defaultValue:"https://github.com/{u}",description:"Changes the link generated by @mentions. Only applies if ghMentions option is enabled.",type:"string"},encodeEmails:{defaultValue:!0,description:"Encode e-mail addresses through the use of Character Entities, transforming ASCII e-mail addresses into its equivalent decimal entities",type:"boolean"},openLinksInNewWindow:{defaultValue:!1,description:"Open all links in new windows",type:"boolean"},backslashEscapesHTMLTags:{defaultValue:!1,description:"Support for HTML Tag escaping. ex: <div>foo</div>",type:"boolean"},emoji:{defaultValue:!1,description:"Enable emoji support. Ex: `this is a :smile: emoji`",type:"boolean"},underline:{defaultValue:!1,description:"Enable support for underline. Syntax is double or triple underscores: `__underline word__`. With this option enabled, underscores no longer parses into `<em>` and `<strong>`",type:"boolean"},completeHTMLDocument:{defaultValue:!1,description:"Outputs a complete html document, including `<html>`, `<head>` and `<body>` tags",type:"boolean"},metadata:{defaultValue:!1,description:"Enable support for document metadata (defined at the top of the document between `«««` and `»»»` or between `---` and `---`).",type:"boolean"},splitAdjacentBlockquotes:{defaultValue:!1,description:"Split adjacent blockquote blocks",type:"boolean"}};if(!1===g)return JSON.parse(JSON.stringify(A));var C={};for(var I in A)A.hasOwnProperty(I)&&(C[I]=A[I].defaultValue);return C}function A(g,A){"use strict";var C=A?"Error in "+A+" extension->":"Error in unnamed extension",e={valid:!0,error:""};I.helper.isArray(g)||(g=[g]);for(var r=0;r<g.length;++r){var t=C+" sub-extension "+r+": ",a=g[r];if("object"!=typeof a)return e.valid=!1,e.error=t+"must be an object, but "+typeof a+" given",e;if(!I.helper.isString(a.type))return e.valid=!1,e.error=t+'property "type" must be a string, but '+typeof a.type+" given",e;var n=a.type=a.type.toLowerCase();if("language"===n&&(n=a.type="lang"),"html"===n&&(n=a.type="output"),"lang"!==n&&"output"!==n&&"listener"!==n)return e.valid=!1,e.error=t+"type "+n+' is not recognized. Valid values: "lang/language", "output/html" or "listener"',e;if("listener"===n){if(I.helper.isUndefined(a.listeners))return e.valid=!1,e.error=t+'. Extensions of type "listener" must have a property called "listeners"',e}else if(I.helper.isUndefined(a.filter)&&I.helper.isUndefined(a.regex))return e.valid=!1,e.error=t+n+' extensions must define either a "regex" property or a "filter" method',e;if(a.listeners){if("object"!=typeof a.listeners)return e.valid=!1,e.error=t+'"listeners" property must be an object but '+typeof a.listeners+" given",e;for(var o in a.listeners)if(a.listeners.hasOwnProperty(o)&&"function"!=typeof a.listeners[o])return e.valid=!1,e.error=t+'"listeners" property must be an hash of [event name]: [callback]. listeners.'+o+" must be a function but "+typeof a.listeners[o]+" given",e}if(a.filter){if("function"!=typeof a.filter)return e.valid=!1,e.error=t+'"filter" must be a function, but '+typeof a.filter+" given",e}else if(a.regex){if(I.helper.isString(a.regex)&&(a.regex=new RegExp(a.regex,"g")),!(a.regex instanceof RegExp))return e.valid=!1,e.error=t+'"regex" property must either be a string or a RegExp object, but '+typeof a.regex+" given",e;if(I.helper.isUndefined(a.replace))return e.valid=!1,e.error=t+'"regex" extensions must implement a replace string or function',e}}return e}function C(g,A){"use strict";return"¨E"+A.charCodeAt(0)+"E"}var I={},e={},r={},t=g(!0),a="vanilla",n={github:{omitExtraWLInCodeBlocks:!0,simplifiedAutoLink:!0,excludeTrailingPunctuationFromURLs:!0,literalMidWordUnderscores:!0,strikethrough:!0,tables:!0,tablesHeaderId:!0,ghCodeBlocks:!0,tasklists:!0,disableForced4SpacesIndentedSublists:!0,simpleLineBreaks:!0,requireSpaceBeforeHeadingText:!0,ghCompatibleHeaderId:!0,ghMentions:!0,backslashEscapesHTMLTags:!0,emoji:!0,splitAdjacentBlockquotes:!0},original:{noHeaderId:!0,ghCodeBlocks:!1},ghost:{omitExtraWLInCodeBlocks:!0,parseImgDimensions:!0,simplifiedAutoLink:!0,excludeTrailingPunctuationFromURLs:!0,literalMidWordUnderscores:!0,strikethrough:!0,tables:!0,tablesHeaderId:!0,ghCodeBlocks:!0,tasklists:!0,smoothLivePreview:!0,simpleLineBreaks:!0,requireSpaceBeforeHeadingText:!0,ghMentions:!1,encodeEmails:!0},vanilla:g(!0),allOn:function(){"use strict";var A=g(!0),C={};for(var I in A)A.hasOwnProperty(I)&&(C[I]=!0);return C}()};I.helper={},I.extensions={},I.setOption=function(g,A){"use strict";return t[g]=A,this},I.getOption=function(g){"use strict";return t[g]},I.getOptions=function(){"use strict";return t},I.resetOptions=function(){"use strict";t=g(!0)},I.setFlavor=function(g){"use strict";if(!n.hasOwnProperty(g))throw Error(g+" flavor was not found");I.resetOptions();var A=n[g];a=g;for(var C in A)A.hasOwnProperty(C)&&(t[C]=A[C])},I.getFlavor=function(){"use strict";return a},I.getFlavorOptions=function(g){"use strict";if(n.hasOwnProperty(g))return n[g]},I.getDefaultOptions=function(A){"use strict";return g(A)},I.subParser=function(g,A){"use strict";if(I.helper.isString(g)){if(void 0===A){if(e.hasOwnProperty(g))return e[g];throw Error("SubParser named "+g+" not registered!")}e[g]=A}},I.extension=function(g,C){"use strict";if(!I.helper.isString(g))throw Error("Extension 'name' must be a string");if(g=I.helper.stdExtName(g),I.helper.isUndefined(C)){if(!r.hasOwnProperty(g))throw Error("Extension named "+g+" is not registered!");return r[g]}"function"==typeof C&&(C=C()),I.helper.isArray(C)||(C=[C]);var e=A(C,g);if(!e.valid)throw Error(e.error);r[g]=C},I.getAllExtensions=function(){"use strict";return r},I.removeExtension=function(g){"use strict";delete r[g]},I.resetExtensions=function(){"use strict";r={}},I.validateExtension=function(g){"use strict";var C=A(g,null);return!!C.valid||(console.warn(C.error),!1)},I.hasOwnProperty("helper")||(I.helper={}),I.helper.isString=function(g){"use strict";return"string"==typeof g||g instanceof String},I.helper.isFunction=function(g){"use strict";return g&&"[object Function]"==={}.toString.call(g)},I.helper.isArray=function(g){"use strict";return Array.isArray(g)},I.helper.isUndefined=function(g){"use strict";return void 0===g},I.helper.forEach=function(g,A){"use strict";if(I.helper.isUndefined(g))throw new Error("obj param is required");if(I.helper.isUndefined(A))throw new Error("callback param is required");if(!I.helper.isFunction(A))throw new Error("callback param must be a function/closure");if("function"==typeof g.forEach)g.forEach(A);else if(I.helper.isArray(g))for(var C=0;C<g.length;C++)A(g[C],C,g);else{if("object"!=typeof g)throw new Error("obj does not seem to be an array or an iterable object");for(var e in g)g.hasOwnProperty(e)&&A(g[e],e,g)}},I.helper.stdExtName=function(g){"use strict";return g.replace(/[_?*+\/\\.^-]/g,"").replace(/\s/g,"").toLowerCase()},I.helper.escapeCharactersCallback=C,I.helper.escapeCharacters=function(g,A,I){"use strict";var e="(["+A.replace(/([\[\]\\])/g,"\\$1")+"])";I&&(e="\\\\"+e);var r=new RegExp(e,"g");return g=g.replace(r,C)};var o=function(g,A,C,I){"use strict";var e,r,t,a,n,o=I||"",s=o.indexOf("g")>-1,i=new RegExp(A+"|"+C,"g"+o.replace(/g/g,"")),l=new RegExp(A,o.replace(/g/g,"")),c=[];do{for(e=0;t=i.exec(g);)if(l.test(t[0]))e++||(a=(r=i.lastIndex)-t[0].length);else if(e&&!--e){n=t.index+t[0].length;var u={left:{start:a,end:r},match:{start:r,end:t.index},right:{start:t.index,end:n},wholeMatch:{start:a,end:n}};if(c.push(u),!s)return c}}while(e&&(i.lastIndex=r));return c};I.helper.matchRecursiveRegExp=function(g,A,C,I){"use strict";for(var e=o(g,A,C,I),r=[],t=0;t<e.length;++t)r.push([g.slice(e[t].wholeMatch.start,e[t].wholeMatch.end),g.slice(e[t].match.start,e[t].match.end),g.slice(e[t].left.start,e[t].left.end),g.slice(e[t].right.start,e[t].right.end)]);return r},I.helper.replaceRecursiveRegExp=function(g,A,C,e,r){"use strict";if(!I.helper.isFunction(A)){var t=A;A=function(){return t}}var a=o(g,C,e,r),n=g,s=a.length;if(s>0){var i=[];0!==a[0].wholeMatch.start&&i.push(g.slice(0,a[0].wholeMatch.start));for(var l=0;l<s;++l)i.push(A(g.slice(a[l].wholeMatch.start,a[l].wholeMatch.end),g.slice(a[l].match.start,a[l].match.end),g.slice(a[l].left.start,a[l].left.end),g.slice(a[l].right.start,a[l].right.end))),l<s-1&&i.push(g.slice(a[l].wholeMatch.end,a[l+1].wholeMatch.start));a[s-1].wholeMatch.end<g.length&&i.push(g.slice(a[s-1].wholeMatch.end)),n=i.join("")}return n},I.helper.regexIndexOf=function(g,A,C){"use strict";if(!I.helper.isString(g))throw"InvalidArgumentError: first parameter of showdown.helper.regexIndexOf function must be a string";if(A instanceof RegExp==!1)throw"InvalidArgumentError: second parameter of showdown.helper.regexIndexOf function must be an instance of RegExp";var e=g.substring(C||0).search(A);return e>=0?e+(C||0):e},I.helper.splitAtIndex=function(g,A){"use strict";if(!I.helper.isString(g))throw"InvalidArgumentError: first parameter of showdown.helper.regexIndexOf function must be a string";return[g.substring(0,A),g.substring(A)]},I.helper.encodeEmailAddress=function(g){"use strict";var A=[function(g){return"&#"+g.charCodeAt(0)+";"},function(g){return"&#x"+g.charCodeAt(0).toString(16)+";"},function(g){return g}];return g=g.replace(/./g,function(g){if("@"===g)g=A[Math.floor(2*Math.random())](g);else{var C=Math.random();g=C>.9?A[2](g):C>.45?A[1](g):A[0](g)}return g})},"undefined"==typeof console&&(console={warn:function(g){"use strict";alert(g)},log:function(g){"use strict";alert(g)},error:function(g){"use strict";throw g}}),I.helper.regexes={asteriskDashAndColon:/([*_:~])/g},I.helper.emojis={"+1":"👍","-1":"👎",100:"💯",1234:"🔢","1st_place_medal":"🥇","2nd_place_medal":"🥈","3rd_place_medal":"🥉","8ball":"🎱",a:"🅰️",ab:"🆎",abc:"🔤",abcd:"🔡",accept:"🉑",aerial_tramway:"🚡",airplane:"✈️",alarm_clock:"⏰",alembic:"⚗️",alien:"👽",ambulance:"🚑",amphora:"🏺",anchor:"⚓️",angel:"👼",anger:"💢",angry:"😠",anguished:"😧",ant:"🐜",apple:"🍎",aquarius:"♒️",aries:"♈️",arrow_backward:"◀️",arrow_double_down:"⏬",arrow_double_up:"⏫",arrow_down:"⬇️",arrow_down_small:"🔽",arrow_forward:"▶️",arrow_heading_down:"⤵️",arrow_heading_up:"⤴️",arrow_left:"⬅️",arrow_lower_left:"↙️",arrow_lower_right:"↘️",arrow_right:"➡️",arrow_right_hook:"↪️",arrow_up:"⬆️",arrow_up_down:"↕️",arrow_up_small:"🔼",arrow_upper_left:"↖️",arrow_upper_right:"↗️",arrows_clockwise:"🔃",arrows_counterclockwise:"🔄",art:"🎨",articulated_lorry:"🚛",artificial_satellite:"🛰",astonished:"😲",athletic_shoe:"👟",atm:"🏧",atom_symbol:"⚛️",avocado:"🥑",b:"🅱️",baby:"👶",baby_bottle:"🍼",baby_chick:"🐤",baby_symbol:"🚼",back:"🔙",bacon:"🥓",badminton:"🏸",baggage_claim:"🛄",baguette_bread:"🥖",balance_scale:"⚖️",balloon:"🎈",ballot_box:"🗳",ballot_box_with_check:"☑️",bamboo:"🎍",banana:"🍌",bangbang:"‼️",bank:"🏦",bar_chart:"📊",barber:"💈",baseball:"⚾️",basketball:"🏀",basketball_man:"⛹️",basketball_woman:"⛹️‍♀️",bat:"🦇",bath:"🛀",bathtub:"🛁",battery:"🔋",beach_umbrella:"🏖",bear:"🐻",bed:"🛏",bee:"🐝",beer:"🍺",beers:"🍻",beetle:"🐞",beginner:"🔰",bell:"🔔",bellhop_bell:"🛎",bento:"🍱",biking_man:"🚴",bike:"🚲",biking_woman:"🚴‍♀️",bikini:"👙",biohazard:"☣️",bird:"🐦",birthday:"🎂",black_circle:"⚫️",black_flag:"🏴",black_heart:"🖤",black_joker:"🃏",black_large_square:"⬛️",black_medium_small_square:"◾️",black_medium_square:"◼️",black_nib:"✒️",black_small_square:"▪️",black_square_button:"🔲",blonde_man:"👱",blonde_woman:"👱‍♀️",blossom:"🌼",blowfish:"🐡",blue_book:"📘",blue_car:"🚙",blue_heart:"💙",blush:"😊",boar:"🐗",boat:"⛵️",bomb:"💣",book:"📖",bookmark:"🔖",bookmark_tabs:"📑",books:"📚",boom:"💥",boot:"👢",bouquet:"💐",bowing_man:"🙇",bow_and_arrow:"🏹",bowing_woman:"🙇‍♀️",bowling:"🎳",boxing_glove:"🥊",boy:"👦",bread:"🍞",bride_with_veil:"👰",bridge_at_night:"🌉",briefcase:"💼",broken_heart:"💔",bug:"🐛",building_construction:"🏗",bulb:"💡",bullettrain_front:"🚅",bullettrain_side:"🚄",burrito:"🌯",bus:"🚌",business_suit_levitating:"🕴",busstop:"🚏",bust_in_silhouette:"👤",busts_in_silhouette:"👥",butterfly:"🦋",cactus:"🌵",cake:"🍰",calendar:"📆",call_me_hand:"🤙",calling:"📲",camel:"🐫",camera:"📷",camera_flash:"📸",camping:"🏕",cancer:"♋️",candle:"🕯",candy:"🍬",canoe:"🛶",capital_abcd:"🔠",capricorn:"♑️",car:"🚗",card_file_box:"🗃",card_index:"📇",card_index_dividers:"🗂",carousel_horse:"🎠",carrot:"🥕",cat:"🐱",cat2:"🐈",cd:"💿",chains:"⛓",champagne:"🍾",chart:"💹",chart_with_downwards_trend:"📉",chart_with_upwards_trend:"📈",checkered_flag:"🏁",cheese:"🧀",cherries:"🍒",cherry_blossom:"🌸",chestnut:"🌰",chicken:"🐔",children_crossing:"🚸",chipmunk:"🐿",chocolate_bar:"🍫",christmas_tree:"🎄",church:"⛪️",cinema:"🎦",circus_tent:"🎪",city_sunrise:"🌇",city_sunset:"🌆",cityscape:"🏙",cl:"🆑",clamp:"🗜",clap:"👏",clapper:"🎬",classical_building:"🏛",clinking_glasses:"🥂",clipboard:"📋",clock1:"🕐",clock10:"🕙",clock1030:"🕥",clock11:"🕚",clock1130:"🕦",clock12:"🕛",clock1230:"🕧",clock130:"🕜",clock2:"🕑",clock230:"🕝",clock3:"🕒",clock330:"🕞",clock4:"🕓",clock430:"🕟",clock5:"🕔",clock530:"🕠",clock6:"🕕",clock630:"🕡",clock7:"🕖",clock730:"🕢",clock8:"🕗",clock830:"🕣",clock9:"🕘",clock930:"🕤",closed_book:"📕",closed_lock_with_key:"🔐",closed_umbrella:"🌂",cloud:"☁️",cloud_with_lightning:"🌩",cloud_with_lightning_and_rain:"⛈",cloud_with_rain:"🌧",cloud_with_snow:"🌨",clown_face:"🤡",clubs:"♣️",cocktail:"🍸",coffee:"☕️",coffin:"⚰️",cold_sweat:"😰",comet:"☄️",computer:"💻",computer_mouse:"🖱",confetti_ball:"🎊",confounded:"😖",confused:"😕",congratulations:"㊗️",construction:"🚧",construction_worker_man:"👷",construction_worker_woman:"👷‍♀️",control_knobs:"🎛",convenience_store:"🏪",cookie:"🍪",cool:"🆒",policeman:"👮",copyright:"©️",corn:"🌽",couch_and_lamp:"🛋",couple:"👫",couple_with_heart_woman_man:"💑",couple_with_heart_man_man:"👨‍❤️‍👨",couple_with_heart_woman_woman:"👩‍❤️‍👩",couplekiss_man_man:"👨‍❤️‍💋‍👨",couplekiss_man_woman:"💏",couplekiss_woman_woman:"👩‍❤️‍💋‍👩",cow:"🐮",cow2:"🐄",cowboy_hat_face:"🤠",crab:"🦀",crayon:"🖍",credit_card:"💳",crescent_moon:"🌙",cricket:"🏏",crocodile:"🐊",croissant:"🥐",crossed_fingers:"🤞",crossed_flags:"🎌",crossed_swords:"⚔️",crown:"👑",cry:"😢",crying_cat_face:"😿",crystal_ball:"🔮",cucumber:"🥒",cupid:"💘",curly_loop:"➰",currency_exchange:"💱",curry:"🍛",custard:"🍮",customs:"🛃",cyclone:"🌀",dagger:"🗡",dancer:"💃",dancing_women:"👯",dancing_men:"👯‍♂️",dango:"🍡",dark_sunglasses:"🕶",dart:"🎯",dash:"💨",date:"📅",deciduous_tree:"🌳",deer:"🦌",department_store:"🏬",derelict_house:"🏚",desert:"🏜",desert_island:"🏝",desktop_computer:"🖥",male_detective:"🕵️",diamond_shape_with_a_dot_inside:"💠",diamonds:"♦️",disappointed:"😞",disappointed_relieved:"😥",dizzy:"💫",dizzy_face:"😵",do_not_litter:"🚯",dog:"🐶",dog2:"🐕",dollar:"💵",dolls:"🎎",dolphin:"🐬",door:"🚪",doughnut:"🍩",dove:"🕊",dragon:"🐉",dragon_face:"🐲",dress:"👗",dromedary_camel:"🐪",drooling_face:"🤤",droplet:"💧",drum:"🥁",duck:"🦆",dvd:"📀","e-mail":"📧",eagle:"🦅",ear:"👂",ear_of_rice:"🌾",earth_africa:"🌍",earth_americas:"🌎",earth_asia:"🌏",egg:"🥚",eggplant:"🍆",eight_pointed_black_star:"✴️",eight_spoked_asterisk:"✳️",electric_plug:"🔌",elephant:"🐘",email:"✉️",end:"🔚",envelope_with_arrow:"📩",euro:"💶",european_castle:"🏰",european_post_office:"🏤",evergreen_tree:"🌲",exclamation:"❗️",expressionless:"😑",eye:"👁",eye_speech_bubble:"👁‍🗨",eyeglasses:"👓",eyes:"👀",face_with_head_bandage:"🤕",face_with_thermometer:"🤒",fist_oncoming:"👊",factory:"🏭",fallen_leaf:"🍂",family_man_woman_boy:"👪",family_man_boy:"👨‍👦",family_man_boy_boy:"👨‍👦‍👦",family_man_girl:"👨‍👧",family_man_girl_boy:"👨‍👧‍👦",family_man_girl_girl:"👨‍👧‍👧",family_man_man_boy:"👨‍👨‍👦",family_man_man_boy_boy:"👨‍👨‍👦‍👦",family_man_man_girl:"👨‍👨‍👧",family_man_man_girl_boy:"👨‍👨‍👧‍👦",family_man_man_girl_girl:"👨‍👨‍👧‍👧",family_man_woman_boy_boy:"👨‍👩‍👦‍👦",family_man_woman_girl:"👨‍👩‍👧",family_man_woman_girl_boy:"👨‍👩‍👧‍👦",family_man_woman_girl_girl:"👨‍👩‍👧‍👧",family_woman_boy:"👩‍👦",family_woman_boy_boy:"👩‍👦‍👦",family_woman_girl:"👩‍👧",family_woman_girl_boy:"👩‍👧‍👦",family_woman_girl_girl:"👩‍👧‍👧",family_woman_woman_boy:"👩‍👩‍👦",family_woman_woman_boy_boy:"👩‍👩‍👦‍👦",family_woman_woman_girl:"👩‍👩‍👧",family_woman_woman_girl_boy:"👩‍👩‍👧‍👦",family_woman_woman_girl_girl:"👩‍👩‍👧‍👧",fast_forward:"⏩",fax:"📠",fearful:"😨",feet:"🐾",female_detective:"🕵️‍♀️",ferris_wheel:"🎡",ferry:"⛴",field_hockey:"🏑",file_cabinet:"🗄",file_folder:"📁",film_projector:"📽",film_strip:"🎞",fire:"🔥",fire_engine:"🚒",fireworks:"🎆",first_quarter_moon:"🌓",first_quarter_moon_with_face:"🌛",fish:"🐟",fish_cake:"🍥",fishing_pole_and_fish:"🎣",fist_raised:"✊",fist_left:"🤛",fist_right:"🤜",flags:"🎏",flashlight:"🔦",fleur_de_lis:"⚜️",flight_arrival:"🛬",flight_departure:"🛫",floppy_disk:"💾",flower_playing_cards:"🎴",flushed:"😳",fog:"🌫",foggy:"🌁",football:"🏈",footprints:"👣",fork_and_knife:"🍴",fountain:"⛲️",fountain_pen:"🖋",four_leaf_clover:"🍀",fox_face:"🦊",framed_picture:"🖼",free:"🆓",fried_egg:"🍳",fried_shrimp:"🍤",fries:"🍟",frog:"🐸",frowning:"😦",frowning_face:"☹️",frowning_man:"🙍‍♂️",frowning_woman:"🙍",middle_finger:"🖕",fuelpump:"⛽️",full_moon:"🌕",full_moon_with_face:"🌝",funeral_urn:"⚱️",game_die:"🎲",gear:"⚙️",gem:"💎",gemini:"♊️",ghost:"👻",gift:"🎁",gift_heart:"💝",girl:"👧",globe_with_meridians:"🌐",goal_net:"🥅",goat:"🐐",golf:"⛳️",golfing_man:"🏌️",golfing_woman:"🏌️‍♀️",gorilla:"🦍",grapes:"🍇",green_apple:"🍏",green_book:"📗",green_heart:"💚",green_salad:"🥗",grey_exclamation:"❕",grey_question:"❔",grimacing:"😬",grin:"😁",grinning:"😀",guardsman:"💂",guardswoman:"💂‍♀️",guitar:"🎸",gun:"🔫",haircut_woman:"💇",haircut_man:"💇‍♂️",hamburger:"🍔",hammer:"🔨",hammer_and_pick:"⚒",hammer_and_wrench:"🛠",hamster:"🐹",hand:"✋",handbag:"👜",handshake:"🤝",hankey:"💩",hatched_chick:"🐥",hatching_chick:"🐣",headphones:"🎧",hear_no_evil:"🙉",heart:"❤️",heart_decoration:"💟",heart_eyes:"😍",heart_eyes_cat:"😻",heartbeat:"💓",heartpulse:"💗",hearts:"♥️",heavy_check_mark:"✔️",heavy_division_sign:"➗",heavy_dollar_sign:"💲",heavy_heart_exclamation:"❣️",heavy_minus_sign:"➖",heavy_multiplication_x:"✖️",heavy_plus_sign:"➕",helicopter:"🚁",herb:"🌿",hibiscus:"🌺",high_brightness:"🔆",high_heel:"👠",hocho:"🔪",hole:"🕳",honey_pot:"🍯",horse:"🐴",horse_racing:"🏇",hospital:"🏥",hot_pepper:"🌶",hotdog:"🌭",hotel:"🏨",hotsprings:"♨️",hourglass:"⌛️",hourglass_flowing_sand:"⏳",house:"🏠",house_with_garden:"🏡",houses:"🏘",hugs:"🤗",hushed:"😯",ice_cream:"🍨",ice_hockey:"🏒",ice_skate:"⛸",icecream:"🍦",id:"🆔",ideograph_advantage:"🉐",imp:"👿",inbox_tray:"📥",incoming_envelope:"📨",tipping_hand_woman:"💁",information_source:"ℹ️",innocent:"😇",interrobang:"⁉️",iphone:"📱",izakaya_lantern:"🏮",jack_o_lantern:"🎃",japan:"🗾",japanese_castle:"🏯",japanese_goblin:"👺",japanese_ogre:"👹",jeans:"👖",joy:"😂",joy_cat:"😹",joystick:"🕹",kaaba:"🕋",key:"🔑",keyboard:"⌨️",keycap_ten:"🔟",kick_scooter:"🛴",kimono:"👘",kiss:"💋",kissing:"😗",kissing_cat:"😽",kissing_closed_eyes:"😚",kissing_heart:"😘",kissing_smiling_eyes:"😙",kiwi_fruit:"🥝",koala:"🐨",koko:"🈁",label:"🏷",large_blue_circle:"🔵",large_blue_diamond:"🔷",large_orange_diamond:"🔶",last_quarter_moon:"🌗",last_quarter_moon_with_face:"🌜",latin_cross:"✝️",laughing:"😆",leaves:"🍃",ledger:"📒",left_luggage:"🛅",left_right_arrow:"↔️",leftwards_arrow_with_hook:"↩️",lemon:"🍋",leo:"♌️",leopard:"🐆",level_slider:"🎚",libra:"♎️",light_rail:"🚈",link:"🔗",lion:"🦁",lips:"👄",lipstick:"💄",lizard:"🦎",lock:"🔒",lock_with_ink_pen:"🔏",lollipop:"🍭",loop:"➿",loud_sound:"🔊",loudspeaker:"📢",love_hotel:"🏩",love_letter:"💌",low_brightness:"🔅",lying_face:"🤥",m:"Ⓜ️",mag:"🔍",mag_right:"🔎",mahjong:"🀄️",mailbox:"📫",mailbox_closed:"📪",mailbox_with_mail:"📬",mailbox_with_no_mail:"📭",man:"👨",man_artist:"👨‍🎨",man_astronaut:"👨‍🚀",man_cartwheeling:"🤸‍♂️",man_cook:"👨‍🍳",man_dancing:"🕺",man_facepalming:"🤦‍♂️",man_factory_worker:"👨‍🏭",man_farmer:"👨‍🌾",man_firefighter:"👨‍🚒",man_health_worker:"👨‍⚕️",man_in_tuxedo:"🤵",man_judge:"👨‍⚖️",man_juggling:"🤹‍♂️",man_mechanic:"👨‍🔧",man_office_worker:"👨‍💼",man_pilot:"👨‍✈️",man_playing_handball:"🤾‍♂️",man_playing_water_polo:"🤽‍♂️",man_scientist:"👨‍🔬",man_shrugging:"🤷‍♂️",man_singer:"👨‍🎤",man_student:"👨‍🎓",man_teacher:"👨‍🏫",man_technologist:"👨‍💻",man_with_gua_pi_mao:"👲",man_with_turban:"👳",tangerine:"🍊",mans_shoe:"👞",mantelpiece_clock:"🕰",maple_leaf:"🍁",martial_arts_uniform:"🥋",mask:"😷",massage_woman:"💆",massage_man:"💆‍♂️",meat_on_bone:"🍖",medal_military:"🎖",medal_sports:"🏅",mega:"📣",melon:"🍈",memo:"📝",men_wrestling:"🤼‍♂️",menorah:"🕎",mens:"🚹",metal:"🤘",metro:"🚇",microphone:"🎤",microscope:"🔬",milk_glass:"🥛",milky_way:"🌌",minibus:"🚐",minidisc:"💽",mobile_phone_off:"📴",money_mouth_face:"🤑",money_with_wings:"💸",moneybag:"💰",monkey:"🐒",monkey_face:"🐵",monorail:"🚝",moon:"🌔",mortar_board:"🎓",mosque:"🕌",motor_boat:"🛥",motor_scooter:"🛵",motorcycle:"🏍",motorway:"🛣",mount_fuji:"🗻",mountain:"⛰",mountain_biking_man:"🚵",mountain_biking_woman:"🚵‍♀️",mountain_cableway:"🚠",mountain_railway:"🚞",mountain_snow:"🏔",mouse:"🐭",mouse2:"🐁",movie_camera:"🎥",moyai:"🗿",mrs_claus:"🤶",muscle:"💪",mushroom:"🍄",musical_keyboard:"🎹",musical_note:"🎵",musical_score:"🎼",mute:"🔇",nail_care:"💅",name_badge:"📛",national_park:"🏞",nauseated_face:"🤢",necktie:"👔",negative_squared_cross_mark:"❎",nerd_face:"🤓",neutral_face:"😐",new:"🆕",new_moon:"🌑",new_moon_with_face:"🌚",newspaper:"📰",newspaper_roll:"🗞",next_track_button:"⏭",ng:"🆖",no_good_man:"🙅‍♂️",no_good_woman:"🙅",night_with_stars:"🌃",no_bell:"🔕",no_bicycles:"🚳",no_entry:"⛔️",no_entry_sign:"🚫",no_mobile_phones:"📵",no_mouth:"😶",no_pedestrians:"🚷",no_smoking:"🚭","non-potable_water":"🚱",nose:"👃",notebook:"📓",notebook_with_decorative_cover:"📔",notes:"🎶",nut_and_bolt:"🔩",o:"⭕️",o2:"🅾️",ocean:"🌊",octopus:"🐙",oden:"🍢",office:"🏢",oil_drum:"🛢",ok:"🆗",ok_hand:"👌",ok_man:"🙆‍♂️",ok_woman:"🙆",old_key:"🗝",older_man:"👴",older_woman:"👵",om:"🕉",on:"🔛",oncoming_automobile:"🚘",oncoming_bus:"🚍",oncoming_police_car:"🚔",oncoming_taxi:"🚖",open_file_folder:"📂",open_hands:"👐",open_mouth:"😮",open_umbrella:"☂️",ophiuchus:"⛎",orange_book:"📙",orthodox_cross:"☦️",outbox_tray:"📤",owl:"🦉",ox:"🐂",package:"📦",page_facing_up:"📄",page_with_curl:"📃",pager:"📟",paintbrush:"🖌",palm_tree:"🌴",pancakes:"🥞",panda_face:"🐼",paperclip:"📎",paperclips:"🖇",parasol_on_ground:"⛱",parking:"🅿️",part_alternation_mark:"〽️",partly_sunny:"⛅️",passenger_ship:"🛳",passport_control:"🛂",pause_button:"⏸",peace_symbol:"☮️",peach:"🍑",peanuts:"🥜",pear:"🍐",pen:"🖊",pencil2:"✏️",penguin:"🐧",pensive:"😔",performing_arts:"🎭",persevere:"😣",person_fencing:"🤺",pouting_woman:"🙎",phone:"☎️",pick:"⛏",pig:"🐷",pig2:"🐖",pig_nose:"🐽",pill:"💊",pineapple:"🍍",ping_pong:"🏓",pisces:"♓️",pizza:"🍕",place_of_worship:"🛐",plate_with_cutlery:"🍽",play_or_pause_button:"⏯",point_down:"👇",point_left:"👈",point_right:"👉",point_up:"☝️",point_up_2:"👆",police_car:"🚓",policewoman:"👮‍♀️",poodle:"🐩",popcorn:"🍿",post_office:"🏣",postal_horn:"📯",postbox:"📮",potable_water:"🚰",potato:"🥔",pouch:"👝",poultry_leg:"🍗",pound:"💷",rage:"😡",pouting_cat:"😾",pouting_man:"🙎‍♂️",pray:"🙏",prayer_beads:"📿",pregnant_woman:"🤰",previous_track_button:"⏮",prince:"🤴",princess:"👸",printer:"🖨",purple_heart:"💜",purse:"👛",pushpin:"📌",put_litter_in_its_place:"🚮",question:"❓",rabbit:"🐰",rabbit2:"🐇",racehorse:"🐎",racing_car:"🏎",radio:"📻",radio_button:"🔘",radioactive:"☢️",railway_car:"🚃",railway_track:"🛤",rainbow:"🌈",rainbow_flag:"🏳️‍🌈",raised_back_of_hand:"🤚",raised_hand_with_fingers_splayed:"🖐",raised_hands:"🙌",raising_hand_woman:"🙋",raising_hand_man:"🙋‍♂️",ram:"🐏",ramen:"🍜",rat:"🐀",record_button:"⏺",recycle:"♻️",red_circle:"🔴",registered:"®️",relaxed:"☺️",relieved:"😌",reminder_ribbon:"🎗",repeat:"🔁",repeat_one:"🔂",rescue_worker_helmet:"⛑",restroom:"🚻",revolving_hearts:"💞",rewind:"⏪",rhinoceros:"🦏",ribbon:"🎀",rice:"🍚",rice_ball:"🍙",rice_cracker:"🍘",rice_scene:"🎑",right_anger_bubble:"🗯",ring:"💍",robot:"🤖",rocket:"🚀",rofl:"🤣",roll_eyes:"🙄",roller_coaster:"🎢",rooster:"🐓",rose:"🌹",rosette:"🏵",rotating_light:"🚨",round_pushpin:"📍",rowing_man:"🚣",rowing_woman:"🚣‍♀️",rugby_football:"🏉",running_man:"🏃",running_shirt_with_sash:"🎽",running_woman:"🏃‍♀️",sa:"🈂️",sagittarius:"♐️",sake:"🍶",sandal:"👡",santa:"🎅",satellite:"📡",saxophone:"🎷",school:"🏫",school_satchel:"🎒",scissors:"✂️",scorpion:"🦂",scorpius:"♏️",scream:"😱",scream_cat:"🙀",scroll:"📜",seat:"💺",secret:"㊙️",see_no_evil:"🙈",seedling:"🌱",selfie:"🤳",shallow_pan_of_food:"🥘",shamrock:"☘️",shark:"🦈",shaved_ice:"🍧",sheep:"🐑",shell:"🐚",shield:"🛡",shinto_shrine:"⛩",ship:"🚢",shirt:"👕",shopping:"🛍",shopping_cart:"🛒",shower:"🚿",shrimp:"🦐",signal_strength:"📶",six_pointed_star:"🔯",ski:"🎿",skier:"⛷",skull:"💀",skull_and_crossbones:"☠️",sleeping:"😴",sleeping_bed:"🛌",sleepy:"😪",slightly_frowning_face:"🙁",slightly_smiling_face:"🙂",slot_machine:"🎰",small_airplane:"🛩",small_blue_diamond:"🔹",small_orange_diamond:"🔸",small_red_triangle:"🔺",small_red_triangle_down:"🔻",smile:"😄",smile_cat:"😸",smiley:"😃",smiley_cat:"😺",smiling_imp:"😈",smirk:"😏",smirk_cat:"😼",smoking:"🚬",snail:"🐌",snake:"🐍",sneezing_face:"🤧",snowboarder:"🏂",snowflake:"❄️",snowman:"⛄️",snowman_with_snow:"☃️",sob:"😭",soccer:"⚽️",soon:"🔜",sos:"🆘",sound:"🔉",space_invader:"👾",spades:"♠️",spaghetti:"🍝",sparkle:"❇️",sparkler:"🎇",sparkles:"✨",sparkling_heart:"💖",speak_no_evil:"🙊",speaker:"🔈",speaking_head:"🗣",speech_balloon:"💬",speedboat:"🚤",spider:"🕷",spider_web:"🕸",spiral_calendar:"🗓",spiral_notepad:"🗒",spoon:"🥄",squid:"🦑",stadium:"🏟",star:"⭐️",star2:"🌟",star_and_crescent:"☪️",star_of_david:"✡️",stars:"🌠",station:"🚉",statue_of_liberty:"🗽",steam_locomotive:"🚂",stew:"🍲",stop_button:"⏹",stop_sign:"🛑",stopwatch:"⏱",straight_ruler:"📏",strawberry:"🍓",stuck_out_tongue:"😛",stuck_out_tongue_closed_eyes:"😝",stuck_out_tongue_winking_eye:"😜",studio_microphone:"🎙",stuffed_flatbread:"🥙",sun_behind_large_cloud:"🌥",sun_behind_rain_cloud:"🌦",sun_behind_small_cloud:"🌤",sun_with_face:"🌞",sunflower:"🌻",sunglasses:"😎",sunny:"☀️",sunrise:"🌅",sunrise_over_mountains:"🌄",surfing_man:"🏄",surfing_woman:"🏄‍♀️",sushi:"🍣",suspension_railway:"🚟",sweat:"😓",sweat_drops:"💦",sweat_smile:"😅",sweet_potato:"🍠",swimming_man:"🏊",swimming_woman:"🏊‍♀️",symbols:"🔣",synagogue:"🕍",syringe:"💉",taco:"🌮",tada:"🎉",tanabata_tree:"🎋",taurus:"♉️",taxi:"🚕",tea:"🍵",telephone_receiver:"📞",telescope:"🔭",tennis:"🎾",tent:"⛺️",thermometer:"🌡",thinking:"🤔",thought_balloon:"💭",ticket:"🎫",tickets:"🎟",tiger:"🐯",tiger2:"🐅",timer_clock:"⏲",tipping_hand_man:"💁‍♂️",tired_face:"😫",tm:"™️",toilet:"🚽",tokyo_tower:"🗼",tomato:"🍅",tongue:"👅",top:"🔝",tophat:"🎩",tornado:"🌪",trackball:"🖲",tractor:"🚜",traffic_light:"🚥",train:"🚋",train2:"🚆",tram:"🚊",triangular_flag_on_post:"🚩",triangular_ruler:"📐",trident:"🔱",triumph:"😤",trolleybus:"🚎",trophy:"🏆",tropical_drink:"🍹",tropical_fish:"🐠",truck:"🚚",trumpet:"🎺",tulip:"🌷",tumbler_glass:"🥃",turkey:"🦃",turtle:"🐢",tv:"📺",twisted_rightwards_arrows:"🔀",two_hearts:"💕",two_men_holding_hands:"👬",two_women_holding_hands:"👭",u5272:"🈹",u5408:"🈴",u55b6:"🈺",u6307:"🈯️",u6708:"🈷️",u6709:"🈶",u6e80:"🈵",u7121:"🈚️",u7533:"🈸",u7981:"🈲",u7a7a:"🈳",umbrella:"☔️",unamused:"😒",underage:"🔞",unicorn:"🦄",unlock:"🔓",up:"🆙",upside_down_face:"🙃",v:"✌️",vertical_traffic_light:"🚦",vhs:"📼",vibration_mode:"📳",video_camera:"📹",video_game:"🎮",violin:"🎻",virgo:"♍️",volcano:"🌋",volleyball:"🏐",vs:"🆚",vulcan_salute:"🖖",walking_man:"🚶",walking_woman:"🚶‍♀️",waning_crescent_moon:"🌘",waning_gibbous_moon:"🌖",warning:"⚠️",wastebasket:"🗑",watch:"⌚️",water_buffalo:"🐃",watermelon:"🍉",wave:"👋",wavy_dash:"〰️",waxing_crescent_moon:"🌒",wc:"🚾",weary:"😩",wedding:"💒",weight_lifting_man:"🏋️",weight_lifting_woman:"🏋️‍♀️",whale:"🐳",whale2:"🐋",wheel_of_dharma:"☸️",wheelchair:"♿️",white_check_mark:"✅",white_circle:"⚪️",white_flag:"🏳️",white_flower:"💮",white_large_square:"⬜️",white_medium_small_square:"◽️",white_medium_square:"◻️",white_small_square:"▫️",white_square_button:"🔳",wilted_flower:"🥀",wind_chime:"🎐",wind_face:"🌬",wine_glass:"🍷",wink:"😉",wolf:"🐺",woman:"👩",woman_artist:"👩‍🎨",woman_astronaut:"👩‍🚀",woman_cartwheeling:"🤸‍♀️",woman_cook:"👩‍🍳",woman_facepalming:"🤦‍♀️",woman_factory_worker:"👩‍🏭",woman_farmer:"👩‍🌾",woman_firefighter:"👩‍🚒",woman_health_worker:"👩‍⚕️",woman_judge:"👩‍⚖️",woman_juggling:"🤹‍♀️",woman_mechanic:"👩‍🔧",woman_office_worker:"👩‍💼",woman_pilot:"👩‍✈️",woman_playing_handball:"🤾‍♀️",woman_playing_water_polo:"🤽‍♀️",woman_scientist:"👩‍🔬",woman_shrugging:"🤷‍♀️",woman_singer:"👩‍🎤",woman_student:"👩‍🎓",woman_teacher:"👩‍🏫",woman_technologist:"👩‍💻",woman_with_turban:"👳‍♀️",womans_clothes:"👚",womans_hat:"👒",women_wrestling:"🤼‍♀️",womens:"🚺",world_map:"🗺",worried:"😟",wrench:"🔧",writing_hand:"✍️",x:"❌",yellow_heart:"💛",yen:"💴",yin_yang:"☯️",yum:"😋",zap:"⚡️",zipper_mouth_face:"🤐",zzz:"💤",octocat:'<img width="20" height="20" align="absmiddle" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAOwUlEQVR42uVbCVyO6RbPmn0sw9gZS0aZO4y5GTEUE2ObxjZjrbHEJVy3sWS5pkaWxjLEkCVDSbSgFLdESaWSLIVUSIi4kvb9f895vi/zbbR+yZ339/tbnu99n/ec/3Oe85xznufV0CjDBaAdwZqwnzCJ0FXjHV70/i8J5oQDhCFV8cJdq1atwqxZs+Ds7Iz4+HhqwgXCLELNKlK6G2Ej4e6lS5ewZcsWzJgxA+fOnWNZFqvzxT1v3boF/qcsBg0ahP3796OwsJAFWKYuIqjfPoS9cXFxWL58Obp06SInh5aWFr//jjoJWLlu3TolAorRuXNn7Ny5k4W4Spgj81xrgj5hLmED4RDhlNRygglBhADCSakpWxFMCHoETUJTwrYHDx7A1NT0je9nPHz4kN/fXl0EeI0aNeqtAjB69+4NPz8/FsSdlXvy5An8/f1hZ2cHCwsLGBsbY/To0cJy9PT0MGDAAAwePBhGRkbClNesWYODBw8iODgYOTk53M/d9evXo27duiW++8iRI3z/ZHURENOjR48ShSjGuHHjhHJ16tQp9TOKaNWqlZKpvw1MHluQOpSvk5eXh5YtW5ZbmarAvHnzmIBd6iCgXnZ2Npo1a1atCWAfwY5SHQTUKCoqQocOHao1AebmHBJgi7p8QBDP6epMwKFDvMDAWF0ELLS1ta3WBNy9e5cJMFIXAdvt7e2rNQHDhw9nAv5D+KKylV9y8+bNCi1pVYWZM2cyCfaVTcDdsqzH7xpBQRxcwqyylLdi5/K+KM/Q0dFhAqIri4Bn1T0AUgVpdmhYUeVHnD59+r1TnjF27Fgm4HhFCThoYmLyXhLQoEGD4mRKsyIE3OrZs+d7SQCDCyZcNSqv8k1evXoFTU3NUr+wzUcfYqRBf8yb/C2WzfoBFoTF08fBdMIITDD8CsP1+kL30x7Q6dYZH7drjfZ0f4fWLdG1Q1t81qMLBvTRwejB/TBl1BDMnzQGS2dMxKo5k7Fs9iSY/jAaBvR8Pc26pZaH02quLZSXgO6xsbGlelGnli1wZKcVMqN8gKcRwItrf+K/VB95doXaLwOJIVSzOU/+2Re5kV7IuuyJrIhTyLt6mmztLBBPNZLHoUAy9fE8UvJ8ikxfj8PwJPQErJeYlkquTZs2MQFLykuANgc/Jb2kn3Z3ZMaQUrmxwO1zyAo7gfRAJ6RfOIyMEFdkXj5F8BTK5lzxQv610yi8QcFatI8gQoCIK7x+hojwRnaE5H4JTiEj9Pjr/rJDqcZyn9b4ovu45LYbdWvXeqtsXMHiSlZ5CegRExPz1hd83PYj5POo0QinXyLFg48hnZTOiQ1Dzr1IZEaeQRoJn0HKZIR7lA2kfHrQUerXHTlx4ZL+rnjjFRGRGeYB5MUj2GnbW+XbuJFrp1heXgI6JCYmvvUFN1x3Aek3SWkapRAXMeJFGS8ge2Xfuog0toaykED3Mpk8+shOk+sv68Y50V9WuKewBKt5094o39atW/mRf5WXgIYZGRlo3Lixys4nj6A6Z1YMcqRCpwU4ouDlUyHk/QA/hNttR25Wlvh/ZthJUsil9ATQ/axkYbqEzDgfL0Ts/x35+aLyTES7IY36Q6w/+Q4/tP6wuUoZ9+7dy7ebVmQZjO/atavKzn32rAdeXkd6KCkXdAxZ13yFcLFnvPD73zrDVrsdTs6eggKSuSjjORHkUGoC0i86Iyc6QPQX7eqMnTodYNuzHU4vnosiaitMSUSavwMy6d3IvEUrzViVMrq5uXEX4ytCgL++vr5Sx7Vr1cIDX0dKkQJfj37Rs3jw1sBxkwlwGD4Ax3+ciN1faCHW76xQRFgAOcjSEMBkIe0x8nLzcez7kTg8Rh/uxuOxR/cTJISFSfq7eATpZCk8CAfXLVFJwIULXHnHoIoQYLtw4UKljps2aogXQcQuef/XAiMDKY+S4DhyEFwpDnCj9f+Afl8EbbWRTANaAdihlYoAMn8aZzyNuYODX/eD29TvRH/7v+qN8H27JdOAyWQfQQ74xPafVRLAPox9WUlK6hIGEgx4f00Kg2JcvHhRqeP6FIwknXemyen/2gLIIeC/CYk49M0AuE4xgtu0sThg8AUCN62TEuBdRgJo2Y+Kxh9D/k59SQiwH9QHobt3SAk4KSGA4oWjm1YqyVi8U6Soj4yOrHM/jTAyKVby/PnzIoNi8L+L4eXlpXoFcLcTgc1rAlISkJeXDxeK2A6P1hdTwI6mQPTJE+WbAlnJyE7PhNO3Q3BkrKGYWtxfHMkkmQLO0ilwA7+vXqAkn66urtBLUZ9iHfm30NBQaPAf165dA0d9vP2UlJSEp0+f4vHjx3j06JH4e+rUqUovcNmyGkiNEkLwklXsBG+ecMUOnfbYod1emG5uboFKJ8jPFVD0l0dBUHqoPDHpQeQEb0qc4FUHe3KAbYUT9JgzDbwOFL5MfN0fXkXhJ5PxSvLt2LFD1Ah5u4z1YJ14l4qnBe8v3rhxAzz4PAVG8nLHivIP0dHRiIiIQGRkpEgmrl69ClW1QBMjQ7LDW8hmU+RRI69ckJIkhL7jfRJBm62R+TJVYq6h0jhBRslsivqenT2MF/7OyI70VmkFhWnPJaS6OyPkt43IycqR9EfWlH7JDQUUTuNhCHR7Ke9YcRp/5coVoQPrcvnyZURFRYmBZlLS0kR8MVLD29sbnp6e8PHxQUBAgCgn8YO8E3z79m3BGKeVc+bMkXuBZt06SA12F/F5Go0gR4C8HBalPZMPXKL8lQKhPAqF+f97KXFyNx6HQsoPsshJ/kmAp2TKkJLISpXvjyxNhMYcDVLOEO+lPDi8B5mamipkZx1YF9YpJCRErAy+vr5CZ9ZdWABhDGEYYTBhAOFz3g4nfMJelNCbkNCpUye5F034mvxIPi1/FM+zQCw0k5B9O0iEr5kRXkqhMJOVf9NXIHjtT7hmaymSoBzKETimkAuFpaF1dkwI9RcmIYaXv3BJXoGCuyIgk5WpefPmKCgoYK46SmX/RKoL69Sfl0WuFEl1HlmWJXE5z6WmTZvKJxxmxkIQ3AuU5APk6NICj4hRT6eITTEEzqWk55HHPjz3cxJhNF5cxeNT9kj2cRDTQjEkzpDtjyyCic5l5fEA7uSHFEefR5pPsahrb2B9QkICFHeJ51HunkdLIg0VLY0BFKdLwllVHp4dHyvst3QuEiiju21vA/+VZkiluIKt4I3RIfWXQ4QgKUxkni47LJWUP3PmjHo2RxVI+CebmKJP6EiFDVurxUgmExe5PHlnPAkn8w4QqW62NCVmYopozid5H0CI9RKE21ggJeAYEeMnfitOnRn5XCfgeJ+VTosWQU8MOc6ZE0cqnUm4fv165SrPBVHCfMI4TowUfmOfsIcdJh92kBWmUcP6GDt8EDZbzIffH5tx3/ewSFjw5LKk0MEFEkZenDBjgew7Yiog5brkt+QrknvJmhIp4Apw/A1bVpjhG/0v5d7Vrl07bNu2TelUSqUoz8uI3Z49OEtBAy+TdP1CqKtwHzvQUxxgTJs2TeX5gdq1a0ObSmCjh+jB+NuvRamL1+3ls77HCip1rTSdJP5eNnMizKndjMLoH42G4bthX+FzHS3UVVEC69evH3799VeKMXJZrlWKclUGAZ5jxoxB02ZNsNlxH74aagBHZyex986HlVTczyGmI58h4CjL2toa48ePFxsUPEotWrQoc0GT0/C2bduiY8eO4ISMcxLeoOFYhS6qm2EpoZG65jmbv+dPSyRZlt5QfVjvtX19AOFNL+aDFNI4m0eFc9Ho5ORkaGtrl5kAVp6DMOk88efEjLe++ZhclZwHTJHEHbs4YOCmLj2645fdvwnTK42zoXtaEHwNDQ3LXdZm5yad3/2r+gQmDsRnIF5KAldX6zdsgG/GG8F44Vzcu3eP2y1K6GPr2rVrK1zbnz59Or/LoaoJCPZ4kCZsjw9GECL79OmDj9q2wb+320C3/5fgPQO6Vrzh+fpcDqxXr16lbHBwgkZXm6okYJr0ECMrX5vraiJ1lArEjrEnzWuOqemiYj9spGd2ee478XkiPsJakmJ83qA05/8qXNurJFLiunXrhpo1a6LxB02wyHIFZpovgOHwYfjZ0hK2lH5u2rwZ5suWYv5ycyUlmjRpgl69eimlrFy3kwuoyOvXr19frm3RokVMwPZ3TYC57E6xVq+e6KzVDSaL/oEp82Zh8IhhWLjGAp/p9oX5ujVKBNjY2MDV1VWuzd3dXaTesm2biUQuZ8u28elSPmKr8a4vdog8GnJpcT1N1KHUuBbt0jSgWuGbzJh3mVhh2TYHBwdxjFa2jVcZnvPVlQBOLXdZWlqW2ZFxNYYVlm07fPgwAgMD5dr4OD5HeHLFFxM+O42DGtXhIkFaMQlcUjIzM0P37t1Ro0YNpZPjPJcVK7SOjo5ybU5OTqIAo0gAh97VlgAZIj4l8Pn4WFaO64ocuXG6zJtDbMqySnC7IgF8uptLVrJtq1evFuWqak+A4j4i4TNpltiJ8LPiNFFFwNGjRyWFyfedAFUny/joekkEuLi4KK0CfykCeFnkiu1flgBeFtl3/D8SsMbKykpOifv37ysRcPz4cVHKUiSA8wwNdR9/VTMBSh9Y8S4Nf2qnSICiBbDzVCRg9uzZTMC+94kAv6FDh8opwRsVHPjItnl4eEDxHNLKlStFXV+2javQ/M1SpZe+1KA4L4G7WDG57fSm/OUbXiqG0ewAFYOeYcN4fwZhvLkp2y4tftrxcltdlf/w+fPn4qNGxTCYU2m6nrRu3VqunT/EoiuZvw6TTZHpyuNNmEaNGsndP3fu3OJAq1N1JOAHDmyKheVtNP4OkE2crULRAW7fvl20EyyLy24a8p+/7WISFixYIMLt4t82bNhQYjXqXREgPq3j74mlX3AmSL8E1eOPIBXnuVT5OsVZpuLnOMeOHeN7vifwiYhYzhC5IpwlOXj1QXWdBmy/XWU/X+UqMZfKBw4cKAobHPlJlZe9h6tOu+7cuSN2dg0MDMSSyZUpmXvaSD+crq/xvl0k9BTCRa7qEPq+5T4t6ffF52WVV+f1P6zyLG30bsU4AAAAAElFTkSuQmCC">',showdown:'<img width="20" height="20" align="absmiddle" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAECtaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzA2NyA3OS4xNTc3NDcsIDIwMTUvMDMvMzAtMjM6NDA6NDIgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIgogICAgICAgICAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICAgICAgICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgICAgICAgICB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIgogICAgICAgICAgICB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIj4KICAgICAgICAgPHhtcDpDcmVhdG9yVG9vbD5BZG9iZSBQaG90b3Nob3AgQ0MgMjAxNSAoV2luZG93cyk8L3htcDpDcmVhdG9yVG9vbD4KICAgICAgICAgPHhtcDpDcmVhdGVEYXRlPjIwMTUtMDEtMTVUMjE6MDE6MTlaPC94bXA6Q3JlYXRlRGF0ZT4KICAgICAgICAgPHhtcDpNZXRhZGF0YURhdGU+MjAxNy0xMC0yNFQxMzozMTozMCswMTowMDwveG1wOk1ldGFkYXRhRGF0ZT4KICAgICAgICAgPHhtcDpNb2RpZnlEYXRlPjIwMTctMTAtMjRUMTM6MzE6MzArMDE6MDA8L3htcDpNb2RpZnlEYXRlPgogICAgICAgICA8cGhvdG9zaG9wOkNvbG9yTW9kZT4zPC9waG90b3Nob3A6Q29sb3JNb2RlPgogICAgICAgICA8cGhvdG9zaG9wOklDQ1Byb2ZpbGU+c1JHQiBJRUM2MTk2Ni0yLjE8L3Bob3Rvc2hvcDpJQ0NQcm9maWxlPgogICAgICAgICA8cGhvdG9zaG9wOlRleHRMYXllcnM+CiAgICAgICAgICAgIDxyZGY6QmFnPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHBob3Rvc2hvcDpMYXllck5hbWU+UyAtPC9waG90b3Nob3A6TGF5ZXJOYW1lPgogICAgICAgICAgICAgICAgICA8cGhvdG9zaG9wOkxheWVyVGV4dD5TIC08L3Bob3Rvc2hvcDpMYXllclRleHQ+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICA8L3JkZjpCYWc+CiAgICAgICAgIDwvcGhvdG9zaG9wOlRleHRMYXllcnM+CiAgICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2UvcG5nPC9kYzpmb3JtYXQ+CiAgICAgICAgIDx4bXBNTTpJbnN0YW5jZUlEPnhtcC5paWQ6N2NkMzQxNzctOWYyZi0yNDRiLWEyYjQtMzU1MzJkY2Y1MWJiPC94bXBNTTpJbnN0YW5jZUlEPgogICAgICAgICA8eG1wTU06RG9jdW1lbnRJRD5hZG9iZTpkb2NpZDpwaG90b3Nob3A6M2E1YzgxYmYtYjhiNy0xMWU3LTk0NDktYTQ2MzdlZjJkNjMzPC94bXBNTTpEb2N1bWVudElEPgogICAgICAgICA8eG1wTU06T3JpZ2luYWxEb2N1bWVudElEPnhtcC5kaWQ6NjBDNUFFNjVGNjlDRTQxMTk0NUE4NTVFM0JDQTdFRUI8L3htcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOkhpc3Rvcnk+CiAgICAgICAgICAgIDxyZGY6U2VxPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5jcmVhdGVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6NjBDNUFFNjVGNjlDRTQxMTk0NUE4NTVFM0JDQTdFRUI8L3N0RXZ0Omluc3RhbmNlSUQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDp3aGVuPjIwMTUtMDEtMTVUMjE6MDE6MTlaPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgQ1M2IChXaW5kb3dzKTwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPnNhdmVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6ODZjNjBkMGQtOGY0Yy01ZTRlLWEwMjQtODI4ZWQyNTIwZDc3PC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE3LTEwLTI0VDEzOjMxOjMwKzAxOjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgQ0MgMjAxNSAoV2luZG93cyk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpjaGFuZ2VkPi88L3N0RXZ0OmNoYW5nZWQ+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5jb252ZXJ0ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnBhcmFtZXRlcnM+ZnJvbSBhcHBsaWNhdGlvbi92bmQuYWRvYmUucGhvdG9zaG9wIHRvIGltYWdlL3BuZzwvc3RFdnQ6cGFyYW1ldGVycz4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPmRlcml2ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnBhcmFtZXRlcnM+Y29udmVydGVkIGZyb20gYXBwbGljYXRpb24vdm5kLmFkb2JlLnBob3Rvc2hvcCB0byBpbWFnZS9wbmc8L3N0RXZ0OnBhcmFtZXRlcnM+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjdjZDM0MTc3LTlmMmYtMjQ0Yi1hMmI0LTM1NTMyZGNmNTFiYjwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAxNy0xMC0yNFQxMzozMTozMCswMTowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKFdpbmRvd3MpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgPC9yZGY6U2VxPgogICAgICAgICA8L3htcE1NOkhpc3Rvcnk+CiAgICAgICAgIDx4bXBNTTpEZXJpdmVkRnJvbSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgIDxzdFJlZjppbnN0YW5jZUlEPnhtcC5paWQ6ODZjNjBkMGQtOGY0Yy01ZTRlLWEwMjQtODI4ZWQyNTIwZDc3PC9zdFJlZjppbnN0YW5jZUlEPgogICAgICAgICAgICA8c3RSZWY6ZG9jdW1lbnRJRD54bXAuZGlkOjYwQzVBRTY1RjY5Q0U0MTE5NDVBODU1RTNCQ0E3RUVCPC9zdFJlZjpkb2N1bWVudElEPgogICAgICAgICAgICA8c3RSZWY6b3JpZ2luYWxEb2N1bWVudElEPnhtcC5kaWQ6NjBDNUFFNjVGNjlDRTQxMTk0NUE4NTVFM0JDQTdFRUI8L3N0UmVmOm9yaWdpbmFsRG9jdW1lbnRJRD4KICAgICAgICAgPC94bXBNTTpEZXJpdmVkRnJvbT4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgICAgPHRpZmY6WFJlc29sdXRpb24+NzIwMDAwLzEwMDAwPC90aWZmOlhSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpZUmVzb2x1dGlvbj43MjAwMDAvMTAwMDA8L3RpZmY6WVJlc29sdXRpb24+CiAgICAgICAgIDx0aWZmOlJlc29sdXRpb25Vbml0PjI8L3RpZmY6UmVzb2x1dGlvblVuaXQ+CiAgICAgICAgIDxleGlmOkNvbG9yU3BhY2U+MTwvZXhpZjpDb2xvclNwYWNlPgogICAgICAgICA8ZXhpZjpQaXhlbFhEaW1lbnNpb24+NjQ8L2V4aWY6UGl4ZWxYRGltZW5zaW9uPgogICAgICAgICA8ZXhpZjpQaXhlbFlEaW1lbnNpb24+NjQ8L2V4aWY6UGl4ZWxZRGltZW5zaW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0idyI/Pse7bzcAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAA1JJREFUeNrsm1+OmlAUhz+aeS9dwZggJn1AnRUMO6jpBgZXULuC2hWUWUGZBTSxKyiuoA4mfUBMnB04K5g+9DihRBHlyh/lJLwIXLgf99xzzu9etZeXFy7Z3nDh1gBoAFy4XeVtQNO0zNcapmUDfUBPnFoBfhQGq6IBaHmjwD4Ahmk5wAD4kKG5J8CNwsAFaHe6DvA9cc0wCgOv8gDka3vA9RHNPgo0D7hNnJtGYWBXxgV2dH4MfMnRRA+Y1WIO2NJ5F/ikoKm3tYsChmkNFHW+fmHQMC1dfHaXPQP3wM1yMdc2B/AOGALTWobBmI1Shu0UGCwX83XyRBQGawHntTtdG5gUNfxVu4CTNqNv6/wWGL7kCc+1AmCYVisl3I2ydD4GYZUCs7IjoLXrxHIx9w9tLAqDCfBwDrXAY457x+cAoCfuwRGjYFUnAGk+PsjR7s8Dn1VeLWCYVlpDw+VivjVHSHt+u9PVJbzGzZXQWTkAkz0V31fATUaEsjVJlQBs4FeGcteLgzgbAALBA+4y3voAeJL8nA0AHfClnM1qm1HhnYUidCSE+KzvSSJUTwAxCOMcpfETMFYpfRUKIAbCFhC3OTJJJwqDWS0BxED0JZ4Pjix1P2+E0loCSMBwyK4S/xc1ojBwag8gMU84cvTKGgmlAYhngu1O9xAXuVE5J1QCQCz3bwHuHvdQui5QKQAxEO6eEKpsFCgTRSXkvdoxSlBMCxhJJbgrrbZRtHCiShN0pRB6PeQ3ckBw2K0oKXMBVYJIP+Nvh9qulFivGoBt1lLQxowT2ykBXCfnhZIglgYACWmqXQv+baioBYCeiCQHm+QEg1O7RhF7hO4OhSAhcJKSFU7qBGADwZeqMMuXn6TUBw8qlaMrirNb4LdhWlP+SWD+cjFfxTpuS2GUpik+o3jFSEkqbJiWn0P0OMSGqlWiOu0TvD+FRHZKAE+oW+cfRmEwqlsesJJEJs8y91QqP+9UL6lqEtz2gpuNEY5sm9sIHln2DRa2aFKGJtiXkZEMiWtgVvRKUSUFkSKt2S7fAGgAXLYpmQQXf36MUChTZdUa2u8/rkvPA6Tz30r4eH3ybcBS5gJ6SaNXb+aABkA1AMxKenclBZLW/He4cYEGwEXb3wEASelexk6LIIIAAAAASUVORK5CYII=">'},I.Converter=function(g){"use strict";function C(g,C){if(C=C||null,I.helper.isString(g)){if(g=I.helper.stdExtName(g),C=g,I.extensions[g])return console.warn("DEPRECATION WARNING: "+g+" is an old extension that uses a deprecated loading method.Please inform the developer that the extension should be updated!"),void function(g,C){"function"==typeof g&&(g=g(new I.Converter));I.helper.isArray(g)||(g=[g]);var e=A(g,C);if(!e.valid)throw Error(e.error);for(var r=0;r<g.length;++r)switch(g[r].type){case"lang":s.push(g[r]);break;case"output":i.push(g[r]);break;default:throw Error("Extension loader error: Type unrecognized!!!")}}(I.extensions[g],g);if(I.helper.isUndefined(r[g]))throw Error('Extension "'+g+'" could not be loaded. It was either not found or is not a valid extension.');g=r[g]}"function"==typeof g&&(g=g()),I.helper.isArray(g)||(g=[g]);var t=A(g,C);if(!t.valid)throw Error(t.error);for(var a=0;a<g.length;++a){switch(g[a].type){case"lang":s.push(g[a]);break;case"output":i.push(g[a])}if(g[a].hasOwnProperty("listeners"))for(var n in g[a].listeners)g[a].listeners.hasOwnProperty(n)&&e(n,g[a].listeners[n])}}function e(g,A){if(!I.helper.isString(g))throw Error("Invalid argument in converter.listen() method: name must be a string, but "+typeof g+" given");if("function"!=typeof A)throw Error("Invalid argument in converter.listen() method: callback must be a function, but "+typeof A+" given");l.hasOwnProperty(g)||(l[g]=[]),l[g].push(A)}var o={},s=[],i=[],l={},c=a,u={parsed:{},raw:"",format:""};!function(){g=g||{};for(var A in t)t.hasOwnProperty(A)&&(o[A]=t[A]);if("object"!=typeof g)throw Error("Converter expects the passed parameter to be an object, but "+typeof g+" was passed instead.");for(var e in g)g.hasOwnProperty(e)&&(o[e]=g[e]);o.extensions&&I.helper.forEach(o.extensions,C)}(),this._dispatch=function(g,A,C,I){if(l.hasOwnProperty(g))for(var e=0;e<l[g].length;++e){var r=l[g][e](g,A,this,C,I);r&&void 0!==r&&(A=r)}return A},this.listen=function(g,A){return e(g,A),this},this.makeHtml=function(g){if(!g)return g;var A={gHtmlBlocks:[],gHtmlMdBlocks:[],gHtmlSpans:[],gUrls:{},gTitles:{},gDimensions:{},gListLevel:0,hashLinkCounts:{},langExtensions:s,outputModifiers:i,converter:this,ghCodeBlocks:[],metadata:{parsed:{},raw:"",format:""}};return g=g.replace(/¨/g,"¨T"),g=g.replace(/\$/g,"¨D"),g=g.replace(/\r\n/g,"\n"),g=g.replace(/\r/g,"\n"),g=g.replace(/\u00A0/g," "),o.smartIndentationFix&&(g=function(g){var A=g.match(/^\s*/)[0].length,C=new RegExp("^\\s{0,"+A+"}","gm");return g.replace(C,"")}(g)),g="\n\n"+g+"\n\n",g=I.subParser("detab")(g,o,A),g=g.replace(/^[ \t]+$/gm,""),I.helper.forEach(s,function(C){g=I.subParser("runExtension")(C,g,o,A)}),g=I.subParser("metadata")(g,o,A),g=I.subParser("hashPreCodeTags")(g,o,A),g=I.subParser("githubCodeBlocks")(g,o,A),g=I.subParser("hashHTMLBlocks")(g,o,A),g=I.subParser("hashCodeTags")(g,o,A),g=I.subParser("stripLinkDefinitions")(g,o,A),g=I.subParser("blockGamut")(g,o,A),g=I.subParser("unhashHTMLSpans")(g,o,A),g=I.subParser("unescapeSpecialChars")(g,o,A),g=g.replace(/¨D/g,"$$"),g=g.replace(/¨T/g,"¨"),g=I.subParser("completeHTMLDocument")(g,o,A),I.helper.forEach(i,function(C){g=I.subParser("runExtension")(C,g,o,A)}),u=A.metadata,g},this.setOption=function(g,A){o[g]=A},this.getOption=function(g){return o[g]},this.getOptions=function(){return o},this.addExtension=function(g,A){C(g,A=A||null)},this.useExtension=function(g){C(g)},this.setFlavor=function(g){if(!n.hasOwnProperty(g))throw Error(g+" flavor was not found");var A=n[g];c=g;for(var C in A)A.hasOwnProperty(C)&&(o[C]=A[C])},this.getFlavor=function(){return c},this.removeExtension=function(g){I.helper.isArray(g)||(g=[g]);for(var A=0;A<g.length;++A){for(var C=g[A],e=0;e<s.length;++e)s[e]===C&&s[e].splice(e,1);for(;0<i.length;++e)i[0]===C&&i[0].splice(e,1)}},this.getAllExtensions=function(){return{language:s,output:i}},this.getMetadata=function(g){return g?u.raw:u.parsed},this.getMetadataFormat=function(){return u.format},this._setMetadataPair=function(g,A){u.parsed[g]=A},this._setMetadataFormat=function(g){u.format=g},this._setMetadataRaw=function(g){u.raw=g}},I.subParser("anchors",function(g,A,C){"use strict";var e=function(g,e,r,t,a,n,o){if(I.helper.isUndefined(o)&&(o=""),r=r.toLowerCase(),g.search(/\(<?\s*>? ?(['"].*['"])?\)$/m)>-1)t="";else if(!t){if(r||(r=e.toLowerCase().replace(/ ?\n/g," ")),t="#"+r,I.helper.isUndefined(C.gUrls[r]))return g;t=C.gUrls[r],I.helper.isUndefined(C.gTitles[r])||(o=C.gTitles[r])}var s='<a href="'+(t=t.replace(I.helper.regexes.asteriskDashAndColon,I.helper.escapeCharactersCallback))+'"';return""!==o&&null!==o&&(s+=' title="'+(o=(o=o.replace(/"/g,""")).replace(I.helper.regexes.asteriskDashAndColon,I.helper.escapeCharactersCallback))+'"'),A.openLinksInNewWindow&&!/^#/.test(t)&&(s+=' target="¨E95Eblank"'),s+=">"+e+"</a>"};return g=(g=C.converter._dispatch("anchors.before",g,A,C)).replace(/\[((?:\[[^\]]*]|[^\[\]])*)] ?(?:\n *)?\[(.*?)]()()()()/g,e),g=g.replace(/\[((?:\[[^\]]*]|[^\[\]])*)]()[ \t]*\([ \t]?<([^>]*)>(?:[ \t]*((["'])([^"]*?)\5))?[ \t]?\)/g,e),g=g.replace(/\[((?:\[[^\]]*]|[^\[\]])*)]()[ \t]*\([ \t]?<?([\S]+?(?:\([\S]*?\)[\S]*?)?)>?(?:[ \t]*((["'])([^"]*?)\5))?[ \t]?\)/g,e),g=g.replace(/\[([^\[\]]+)]()()()()()/g,e),A.ghMentions&&(g=g.replace(/(^|\s)(\\)?(@([a-z\d\-]+))(?=[.!?;,[\]()]|\s|$)/gim,function(g,C,e,r,t){if("\\"===e)return C+r;if(!I.helper.isString(A.ghMentionsLink))throw new Error("ghMentionsLink option must be a string");var a=A.ghMentionsLink.replace(/\{u}/g,t),n="";return A.openLinksInNewWindow&&(n=' target="¨E95Eblank"'),C+'<a href="'+a+'"'+n+">"+r+"</a>"})),g=C.converter._dispatch("anchors.after",g,A,C)});var s=/([*~_]+|\b)(((https?|ftp|dict):\/\/|www\.)[^'">\s]+?\.[^'">\s]+?)()(\1)?(?=\s|$)(?!["<>])/gi,i=/([*~_]+|\b)(((https?|ftp|dict):\/\/|www\.)[^'">\s]+\.[^'">\s]+?)([.!?,()\[\]])?(\1)?(?=\s|$)(?!["<>])/gi,l=/()<(((https?|ftp|dict):\/\/|www\.)[^'">\s]+)()>()/gi,c=/(^|\s)(?:mailto:)?([A-Za-z0-9!#$%&'*+-/=?^_`{|}~.]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)(?=$|\s)/gim,u=/<()(?:mailto:)?([-.\w]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,d=function(g){"use strict";return function(A,C,e,r,t,a,n){var o=e=e.replace(I.helper.regexes.asteriskDashAndColon,I.helper.escapeCharactersCallback),s="",i="",l=C||"",c=n||"";return/^www\./i.test(e)&&(e=e.replace(/^www\./i,"http://www.")),g.excludeTrailingPunctuationFromURLs&&a&&(s=a),g.openLinksInNewWindow&&(i=' target="¨E95Eblank"'),l+'<a href="'+e+'"'+i+">"+o+"</a>"+s+c}},p=function(g,A){"use strict";return function(C,e,r){var t="mailto:";return e=e||"",r=I.subParser("unescapeSpecialChars")(r,g,A),g.encodeEmails?(t=I.helper.encodeEmailAddress(t+r),r=I.helper.encodeEmailAddress(r)):t+=r,e+'<a href="'+t+'">'+r+"</a>"}};I.subParser("autoLinks",function(g,A,C){"use strict";return g=C.converter._dispatch("autoLinks.before",g,A,C),g=g.replace(l,d(A)),g=g.replace(u,p(A,C)),g=C.converter._dispatch("autoLinks.after",g,A,C)}),I.subParser("simplifiedAutoLinks",function(g,A,C){"use strict";return A.simplifiedAutoLink?(g=C.converter._dispatch("simplifiedAutoLinks.before",g,A,C),g=A.excludeTrailingPunctuationFromURLs?g.replace(i,d(A)):g.replace(s,d(A)),g=g.replace(c,p(A,C)),g=C.converter._dispatch("simplifiedAutoLinks.after",g,A,C)):g}),I.subParser("blockGamut",function(g,A,C){"use strict";return g=C.converter._dispatch("blockGamut.before",g,A,C),g=I.subParser("blockQuotes")(g,A,C),g=I.subParser("headers")(g,A,C),g=I.subParser("horizontalRule")(g,A,C),g=I.subParser("lists")(g,A,C),g=I.subParser("codeBlocks")(g,A,C),g=I.subParser("tables")(g,A,C),g=I.subParser("hashHTMLBlocks")(g,A,C),g=I.subParser("paragraphs")(g,A,C),g=C.converter._dispatch("blockGamut.after",g,A,C)}),I.subParser("blockQuotes",function(g,A,C){"use strict";g=C.converter._dispatch("blockQuotes.before",g,A,C),g+="\n\n";var e=/(^ {0,3}>[ \t]?.+\n(.+\n)*\n*)+/gm;return A.splitAdjacentBlockquotes&&(e=/^ {0,3}>[\s\S]*?(?:\n\n)/gm),g=g.replace(e,function(g){return g=g.replace(/^[ \t]*>[ \t]?/gm,""),g=g.replace(/¨0/g,""),g=g.replace(/^[ \t]+$/gm,""),g=I.subParser("githubCodeBlocks")(g,A,C),g=I.subParser("blockGamut")(g,A,C),g=g.replace(/(^|\n)/g,"$1 "),g=g.replace(/(\s*<pre>[^\r]+?<\/pre>)/gm,function(g,A){var C=A;return C=C.replace(/^ /gm,"¨0"),C=C.replace(/¨0/g,"")}),I.subParser("hashBlock")("<blockquote>\n"+g+"\n</blockquote>",A,C)}),g=C.converter._dispatch("blockQuotes.after",g,A,C)}),I.subParser("codeBlocks",function(g,A,C){"use strict";g=C.converter._dispatch("codeBlocks.before",g,A,C);return g=(g+="¨0").replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=¨0))/g,function(g,e,r){var t=e,a=r,n="\n";return t=I.subParser("outdent")(t,A,C),t=I.subParser("encodeCode")(t,A,C),t=I.subParser("detab")(t,A,C),t=t.replace(/^\n+/g,""),t=t.replace(/\n+$/g,""),A.omitExtraWLInCodeBlocks&&(n=""),t="<pre><code>"+t+n+"</code></pre>",I.subParser("hashBlock")(t,A,C)+a}),g=g.replace(/¨0/,""),g=C.converter._dispatch("codeBlocks.after",g,A,C)}),I.subParser("codeSpans",function(g,A,C){"use strict";return void 0===(g=C.converter._dispatch("codeSpans.before",g,A,C))&&(g=""),g=g.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,function(g,e,r,t){var a=t;return a=a.replace(/^([ \t]*)/g,""),a=a.replace(/[ \t]*$/g,""),a=I.subParser("encodeCode")(a,A,C),a=e+"<code>"+a+"</code>",a=I.subParser("hashHTMLSpans")(a,A,C)}),g=C.converter._dispatch("codeSpans.after",g,A,C)}),I.subParser("completeHTMLDocument",function(g,A,C){"use strict";if(!A.completeHTMLDocument)return g;g=C.converter._dispatch("completeHTMLDocument.before",g,A,C);var I="html",e="<!DOCTYPE HTML>\n",r="",t='<meta charset="utf-8">\n',a="",n="";void 0!==C.metadata.parsed.doctype&&(e="<!DOCTYPE "+C.metadata.parsed.doctype+">\n","html"!==(I=C.metadata.parsed.doctype.toString().toLowerCase())&&"html5"!==I||(t='<meta charset="utf-8">'));for(var o in C.metadata.parsed)if(C.metadata.parsed.hasOwnProperty(o))switch(o.toLowerCase()){case"doctype":break;case"title":r="<title>"+C.metadata.parsed.title+"</title>\n";break;case"charset":t="html"===I||"html5"===I?'<meta charset="'+C.metadata.parsed.charset+'">\n':'<meta name="charset" content="'+C.metadata.parsed.charset+'">\n';break;case"language":case"lang":a=' lang="'+C.metadata.parsed[o]+'"',n+='<meta name="'+o+'" content="'+C.metadata.parsed[o]+'">\n';break;default:n+='<meta name="'+o+'" content="'+C.metadata.parsed[o]+'">\n'}return g=e+"<html"+a+">\n<head>\n"+r+t+n+"</head>\n<body>\n"+g.trim()+"\n</body>\n</html>",g=C.converter._dispatch("completeHTMLDocument.after",g,A,C)}),I.subParser("detab",function(g,A,C){"use strict";return g=C.converter._dispatch("detab.before",g,A,C),g=g.replace(/\t(?=\t)/g," "),g=g.replace(/\t/g,"¨A¨B"),g=g.replace(/¨B(.+?)¨A/g,function(g,A){for(var C=A,I=4-C.length%4,e=0;e<I;e++)C+=" ";return C}),g=g.replace(/¨A/g," "),g=g.replace(/¨B/g,""),g=C.converter._dispatch("detab.after",g,A,C)}),I.subParser("ellipsis",function(g,A,C){"use strict";return g=C.converter._dispatch("ellipsis.before",g,A,C),g=g.replace(/\.\.\./g,"…"),g=C.converter._dispatch("ellipsis.after",g,A,C)}),I.subParser("emoji",function(g,A,C){"use strict";if(!A.emoji)return g;return g=(g=C.converter._dispatch("emoji.before",g,A,C)).replace(/:([\S]+?):/g,function(g,A){return I.helper.emojis.hasOwnProperty(A)?I.helper.emojis[A]:g}),g=C.converter._dispatch("emoji.after",g,A,C)}),I.subParser("encodeAmpsAndAngles",function(g,A,C){"use strict";return g=C.converter._dispatch("encodeAmpsAndAngles.before",g,A,C),g=g.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&"),g=g.replace(/<(?![a-z\/?$!])/gi,"<"),g=g.replace(/</g,"<"),g=g.replace(/>/g,">"),g=C.converter._dispatch("encodeAmpsAndAngles.after",g,A,C)}),I.subParser("encodeBackslashEscapes",function(g,A,C){"use strict";return g=C.converter._dispatch("encodeBackslashEscapes.before",g,A,C),g=g.replace(/\\(\\)/g,I.helper.escapeCharactersCallback),g=g.replace(/\\([`*_{}\[\]()>#+.!~=|-])/g,I.helper.escapeCharactersCallback),g=C.converter._dispatch("encodeBackslashEscapes.after",g,A,C)}),I.subParser("encodeCode",function(g,A,C){"use strict";return g=C.converter._dispatch("encodeCode.before",g,A,C),g=g.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/([*_{}\[\]\\=~-])/g,I.helper.escapeCharactersCallback),g=C.converter._dispatch("encodeCode.after",g,A,C)}),I.subParser("escapeSpecialCharsWithinTagAttributes",function(g,A,C){"use strict";return g=(g=C.converter._dispatch("escapeSpecialCharsWithinTagAttributes.before",g,A,C)).replace(/<\/?[a-z\d_:-]+(?:[\s]+[\s\S]+?)?>/gi,function(g){return g.replace(/(.)<\/?code>(?=.)/g,"$1`").replace(/([\\`*_~=|])/g,I.helper.escapeCharactersCallback)}),g=g.replace(/<!(--(?:(?:[^>-]|-[^>])(?:[^-]|-[^-])*)--)>/gi,function(g){return g.replace(/([\\`*_~=|])/g,I.helper.escapeCharactersCallback)}),g=C.converter._dispatch("escapeSpecialCharsWithinTagAttributes.after",g,A,C)}),I.subParser("githubCodeBlocks",function(g,A,C){"use strict";return A.ghCodeBlocks?(g=C.converter._dispatch("githubCodeBlocks.before",g,A,C),g+="¨0",g=g.replace(/(?:^|\n)(```+|~~~+)([^\s`~]*)\n([\s\S]*?)\n\1/g,function(g,e,r,t){var a=A.omitExtraWLInCodeBlocks?"":"\n";return t=I.subParser("encodeCode")(t,A,C),t=I.subParser("detab")(t,A,C),t=t.replace(/^\n+/g,""),t=t.replace(/\n+$/g,""),t="<pre><code"+(r?' class="'+r+" language-"+r+'"':"")+">"+t+a+"</code></pre>",t=I.subParser("hashBlock")(t,A,C),"\n\n¨G"+(C.ghCodeBlocks.push({text:g,codeblock:t})-1)+"G\n\n"}),g=g.replace(/¨0/,""),C.converter._dispatch("githubCodeBlocks.after",g,A,C)):g}),I.subParser("hashBlock",function(g,A,C){"use strict";return g=C.converter._dispatch("hashBlock.before",g,A,C),g=g.replace(/(^\n+|\n+$)/g,""),g="\n\n¨K"+(C.gHtmlBlocks.push(g)-1)+"K\n\n",g=C.converter._dispatch("hashBlock.after",g,A,C)}),I.subParser("hashCodeTags",function(g,A,C){"use strict";g=C.converter._dispatch("hashCodeTags.before",g,A,C);return g=I.helper.replaceRecursiveRegExp(g,function(g,e,r,t){var a=r+I.subParser("encodeCode")(e,A,C)+t;return"¨C"+(C.gHtmlSpans.push(a)-1)+"C"},"<code\\b[^>]*>","</code>","gim"),g=C.converter._dispatch("hashCodeTags.after",g,A,C)}),I.subParser("hashElement",function(g,A,C){"use strict";return function(g,A){var I=A;return I=I.replace(/\n\n/g,"\n"),I=I.replace(/^\n/,""),I=I.replace(/\n+$/g,""),I="\n\n¨K"+(C.gHtmlBlocks.push(I)-1)+"K\n\n"}}),I.subParser("hashHTMLBlocks",function(g,A,C){"use strict";g=C.converter._dispatch("hashHTMLBlocks.before",g,A,C);var e=["pre","div","h1","h2","h3","h4","h5","h6","blockquote","table","dl","ol","ul","script","noscript","form","fieldset","iframe","math","style","section","header","footer","nav","article","aside","address","audio","canvas","figure","hgroup","output","video","p"],r=function(g,A,I,e){var r=g;return-1!==I.search(/\bmarkdown\b/)&&(r=I+C.converter.makeHtml(A)+e),"\n\n¨K"+(C.gHtmlBlocks.push(r)-1)+"K\n\n"};A.backslashEscapesHTMLTags&&(g=g.replace(/\\<(\/?[^>]+?)>/g,function(g,A){return"<"+A+">"}));for(var t=0;t<e.length;++t)for(var a,n=new RegExp("^ {0,3}(<"+e[t]+"\\b[^>]*>)","im"),o="<"+e[t]+"\\b[^>]*>",s="</"+e[t]+">";-1!==(a=I.helper.regexIndexOf(g,n));){var i=I.helper.splitAtIndex(g,a),l=I.helper.replaceRecursiveRegExp(i[1],r,o,s,"im");if(l===i[1])break;g=i[0].concat(l)}return g=g.replace(/(\n {0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,I.subParser("hashElement")(g,A,C)),g=I.helper.replaceRecursiveRegExp(g,function(g){return"\n\n¨K"+(C.gHtmlBlocks.push(g)-1)+"K\n\n"},"^ {0,3}\x3c!--","--\x3e","gm"),g=g.replace(/(?:\n\n)( {0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,I.subParser("hashElement")(g,A,C)),g=C.converter._dispatch("hashHTMLBlocks.after",g,A,C)}),I.subParser("hashHTMLSpans",function(g,A,C){"use strict";function I(g){return"¨C"+(C.gHtmlSpans.push(g)-1)+"C"}return g=C.converter._dispatch("hashHTMLSpans.before",g,A,C),g=g.replace(/<[^>]+?\/>/gi,function(g){return I(g)}),g=g.replace(/<([^>]+?)>[\s\S]*?<\/\1>/g,function(g){return I(g)}),g=g.replace(/<([^>]+?)\s[^>]+?>[\s\S]*?<\/\1>/g,function(g){return I(g)}),g=g.replace(/<[^>]+?>/gi,function(g){return I(g)}),g=C.converter._dispatch("hashHTMLSpans.after",g,A,C)}),I.subParser("unhashHTMLSpans",function(g,A,C){"use strict";g=C.converter._dispatch("unhashHTMLSpans.before",g,A,C);for(var I=0;I<C.gHtmlSpans.length;++I){for(var e=C.gHtmlSpans[I],r=0;/¨C(\d+)C/.test(e);){var t=RegExp.$1;if(e=e.replace("¨C"+t+"C",C.gHtmlSpans[t]),10===r){console.error("maximum nesting of 10 spans reached!!!");break}++r}g=g.replace("¨C"+I+"C",e)}return g=C.converter._dispatch("unhashHTMLSpans.after",g,A,C)}),I.subParser("hashPreCodeTags",function(g,A,C){"use strict";g=C.converter._dispatch("hashPreCodeTags.before",g,A,C);return g=I.helper.replaceRecursiveRegExp(g,function(g,e,r,t){var a=r+I.subParser("encodeCode")(e,A,C)+t;return"\n\n¨G"+(C.ghCodeBlocks.push({text:g,codeblock:a})-1)+"G\n\n"},"^ {0,3}<pre\\b[^>]*>\\s*<code\\b[^>]*>","^ {0,3}</code>\\s*</pre>","gim"),g=C.converter._dispatch("hashPreCodeTags.after",g,A,C)}),I.subParser("headers",function(g,A,C){"use strict";function e(g){var e,r;if(A.customizedHeaderId){var t=g.match(/\{([^{]+?)}\s*$/);t&&t[1]&&(g=t[1])}return e=g,r=I.helper.isString(A.prefixHeaderId)?A.prefixHeaderId:!0===A.prefixHeaderId?"section-":"",A.rawPrefixHeaderId||(e=r+e),e=A.ghCompatibleHeaderId?e.replace(/ /g,"-").replace(/&/g,"").replace(/¨T/g,"").replace(/¨D/g,"").replace(/[&+$,\/:;=?@"#{}|^¨~\[\]`\\*)(%.!'<>]/g,"").toLowerCase():A.rawHeaderId?e.replace(/ /g,"-").replace(/&/g,"&").replace(/¨T/g,"¨").replace(/¨D/g,"$").replace(/["']/g,"-").toLowerCase():e.replace(/[^\w]/g,"").toLowerCase(),A.rawPrefixHeaderId&&(e=r+e),C.hashLinkCounts[e]?e=e+"-"+C.hashLinkCounts[e]++:C.hashLinkCounts[e]=1,e}g=C.converter._dispatch("headers.before",g,A,C);var r=isNaN(parseInt(A.headerLevelStart))?1:parseInt(A.headerLevelStart),t=A.smoothLivePreview?/^(.+)[ \t]*\n={2,}[ \t]*\n+/gm:/^(.+)[ \t]*\n=+[ \t]*\n+/gm,a=A.smoothLivePreview?/^(.+)[ \t]*\n-{2,}[ \t]*\n+/gm:/^(.+)[ \t]*\n-+[ \t]*\n+/gm;g=(g=g.replace(t,function(g,t){var a=I.subParser("spanGamut")(t,A,C),n=A.noHeaderId?"":' id="'+e(t)+'"',o="<h"+r+n+">"+a+"</h"+r+">";return I.subParser("hashBlock")(o,A,C)})).replace(a,function(g,t){var a=I.subParser("spanGamut")(t,A,C),n=A.noHeaderId?"":' id="'+e(t)+'"',o=r+1,s="<h"+o+n+">"+a+"</h"+o+">";return I.subParser("hashBlock")(s,A,C)});var n=A.requireSpaceBeforeHeadingText?/^(#{1,6})[ \t]+(.+?)[ \t]*#*\n+/gm:/^(#{1,6})[ \t]*(.+?)[ \t]*#*\n+/gm;return g=g.replace(n,function(g,t,a){var n=a;A.customizedHeaderId&&(n=a.replace(/\s?\{([^{]+?)}\s*$/,""));var o=I.subParser("spanGamut")(n,A,C),s=A.noHeaderId?"":' id="'+e(a)+'"',i=r-1+t.length,l="<h"+i+s+">"+o+"</h"+i+">";return I.subParser("hashBlock")(l,A,C)}),g=C.converter._dispatch("headers.after",g,A,C)}),I.subParser("horizontalRule",function(g,A,C){"use strict";g=C.converter._dispatch("horizontalRule.before",g,A,C);var e=I.subParser("hashBlock")("<hr />",A,C);return g=g.replace(/^ {0,2}( ?-){3,}[ \t]*$/gm,e),g=g.replace(/^ {0,2}( ?\*){3,}[ \t]*$/gm,e),g=g.replace(/^ {0,2}( ?_){3,}[ \t]*$/gm,e),g=C.converter._dispatch("horizontalRule.after",g,A,C)}),I.subParser("images",function(g,A,C){"use strict";function e(g,A,e,r,t,a,n,o){var s=C.gUrls,i=C.gTitles,l=C.gDimensions;if(e=e.toLowerCase(),o||(o=""),g.search(/\(<?\s*>? ?(['"].*['"])?\)$/m)>-1)r="";else if(""===r||null===r){if(""!==e&&null!==e||(e=A.toLowerCase().replace(/ ?\n/g," ")),r="#"+e,I.helper.isUndefined(s[e]))return g;r=s[e],I.helper.isUndefined(i[e])||(o=i[e]),I.helper.isUndefined(l[e])||(t=l[e].width,a=l[e].height)}A=A.replace(/"/g,""").replace(I.helper.regexes.asteriskDashAndColon,I.helper.escapeCharactersCallback);var c='<img src="'+(r=r.replace(I.helper.regexes.asteriskDashAndColon,I.helper.escapeCharactersCallback))+'" alt="'+A+'"';return o&&(c+=' title="'+(o=o.replace(/"/g,""").replace(I.helper.regexes.asteriskDashAndColon,I.helper.escapeCharactersCallback))+'"'),t&&a&&(c+=' width="'+(t="*"===t?"auto":t)+'"',c+=' height="'+(a="*"===a?"auto":a)+'"'),c+=" />"}return g=(g=C.converter._dispatch("images.before",g,A,C)).replace(/!\[([^\]]*?)] ?(?:\n *)?\[([\s\S]*?)]()()()()()/g,e),g=g.replace(/!\[([^\]]*?)][ \t]*()\([ \t]?<?(data:.+?\/.+?;base64,[A-Za-z0-9+/=\n]+?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(["'])([^"]*?)\6)?[ \t]?\)/g,function(g,A,C,I,r,t,a,n){return I=I.replace(/\s/g,""),e(g,A,C,I,r,t,0,n)}),g=g.replace(/!\[([^\]]*?)][ \t]*()\([ \t]?<([^>]*)>(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(?:(["'])([^"]*?)\6))?[ \t]?\)/g,e),g=g.replace(/!\[([^\]]*?)][ \t]*()\([ \t]?<?([\S]+?(?:\([\S]*?\)[\S]*?)?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(["'])([^"]*?)\6)?[ \t]?\)/g,e),g=g.replace(/!\[([^\[\]]+)]()()()()()/g,e),g=C.converter._dispatch("images.after",g,A,C)}),I.subParser("italicsAndBold",function(g,A,C){"use strict";function I(g,A,C){return A+g+C}return g=C.converter._dispatch("italicsAndBold.before",g,A,C),g=A.literalMidWordUnderscores?(g=(g=g.replace(/\b___(\S[\s\S]*)___\b/g,function(g,A){return I(A,"<strong><em>","</em></strong>")})).replace(/\b__(\S[\s\S]*)__\b/g,function(g,A){return I(A,"<strong>","</strong>")})).replace(/\b_(\S[\s\S]*?)_\b/g,function(g,A){return I(A,"<em>","</em>")}):(g=(g=g.replace(/___(\S[\s\S]*?)___/g,function(g,A){return/\S$/.test(A)?I(A,"<strong><em>","</em></strong>"):g})).replace(/__(\S[\s\S]*?)__/g,function(g,A){return/\S$/.test(A)?I(A,"<strong>","</strong>"):g})).replace(/_([^\s_][\s\S]*?)_/g,function(g,A){return/\S$/.test(A)?I(A,"<em>","</em>"):g}),g=A.literalMidWordAsterisks?(g=(g=g.replace(/([^*]|^)\B\*\*\*(\S[\s\S]+?)\*\*\*\B(?!\*)/g,function(g,A,C){return I(C,A+"<strong><em>","</em></strong>")})).replace(/([^*]|^)\B\*\*(\S[\s\S]+?)\*\*\B(?!\*)/g,function(g,A,C){return I(C,A+"<strong>","</strong>")})).replace(/([^*]|^)\B\*(\S[\s\S]+?)\*\B(?!\*)/g,function(g,A,C){return I(C,A+"<em>","</em>")}):(g=(g=g.replace(/\*\*\*(\S[\s\S]*?)\*\*\*/g,function(g,A){return/\S$/.test(A)?I(A,"<strong><em>","</em></strong>"):g})).replace(/\*\*(\S[\s\S]*?)\*\*/g,function(g,A){return/\S$/.test(A)?I(A,"<strong>","</strong>"):g})).replace(/\*([^\s*][\s\S]*?)\*/g,function(g,A){return/\S$/.test(A)?I(A,"<em>","</em>"):g}),g=C.converter._dispatch("italicsAndBold.after",g,A,C)}),I.subParser("lists",function(g,A,C){"use strict";function e(g,e){C.gListLevel++,g=g.replace(/\n{2,}$/,"\n");var r=/(\n)?(^ {0,3})([*+-]|\d+[.])[ \t]+((\[(x|X| )?])?[ \t]*[^\r]+?(\n{1,2}))(?=\n*(¨0| {0,3}([*+-]|\d+[.])[ \t]+))/gm,t=/\n[ \t]*\n(?!¨0)/.test(g+="¨0");return A.disableForced4SpacesIndentedSublists&&(r=/(\n)?(^ {0,3})([*+-]|\d+[.])[ \t]+((\[(x|X| )?])?[ \t]*[^\r]+?(\n{1,2}))(?=\n*(¨0|\2([*+-]|\d+[.])[ \t]+))/gm),g=g.replace(r,function(g,e,r,a,n,o,s){s=s&&""!==s.trim();var i=I.subParser("outdent")(n,A,C),l="";return o&&A.tasklists&&(l=' class="task-list-item" style="list-style-type: none;"',i=i.replace(/^[ \t]*\[(x|X| )?]/m,function(){var g='<input type="checkbox" disabled style="margin: 0px 0.35em 0.25em -1.6em; vertical-align: middle;"';return s&&(g+=" checked"),g+=">"})),i=i.replace(/^([-*+]|\d\.)[ \t]+[\S\n ]*/g,function(g){return"¨A"+g}),e||i.search(/\n{2,}/)>-1?(i=I.subParser("githubCodeBlocks")(i,A,C),i=I.subParser("blockGamut")(i,A,C)):(i=(i=I.subParser("lists")(i,A,C)).replace(/\n$/,""),i=(i=I.subParser("hashHTMLBlocks")(i,A,C)).replace(/\n\n+/g,"\n\n"),i=t?I.subParser("paragraphs")(i,A,C):I.subParser("spanGamut")(i,A,C)),i=i.replace("¨A",""),i="<li"+l+">"+i+"</li>\n"}),g=g.replace(/¨0/g,""),C.gListLevel--,e&&(g=g.replace(/\s+$/,"")),g}function r(g,A){if("ol"===A){var C=g.match(/^ *(\d+)\./);if(C&&"1"!==C[1])return' start="'+C[1]+'"'}return""}function t(g,C,I){var t=A.disableForced4SpacesIndentedSublists?/^ ?\d+\.[ \t]/gm:/^ {0,3}\d+\.[ \t]/gm,a=A.disableForced4SpacesIndentedSublists?/^ ?[*+-][ \t]/gm:/^ {0,3}[*+-][ \t]/gm,n="ul"===C?t:a,o="";if(-1!==g.search(n))!function A(s){var i=s.search(n),l=r(g,C);-1!==i?(o+="\n\n<"+C+l+">\n"+e(s.slice(0,i),!!I)+"</"+C+">\n",n="ul"===(C="ul"===C?"ol":"ul")?t:a,A(s.slice(i))):o+="\n\n<"+C+l+">\n"+e(s,!!I)+"</"+C+">\n"}(g);else{var s=r(g,C);o="\n\n<"+C+s+">\n"+e(g,!!I)+"</"+C+">\n"}return o}return g=C.converter._dispatch("lists.before",g,A,C),g+="¨0",g=C.gListLevel?g.replace(/^(( {0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(¨0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm,function(g,A,C){return t(A,C.search(/[*+-]/g)>-1?"ul":"ol",!0)}):g.replace(/(\n\n|^\n?)(( {0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(¨0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm,function(g,A,C,I){return t(C,I.search(/[*+-]/g)>-1?"ul":"ol",!1)}),g=g.replace(/¨0/,""),g=C.converter._dispatch("lists.after",g,A,C)}),I.subParser("metadata",function(g,A,C){"use strict";function I(g){C.metadata.raw=g,(g=(g=g.replace(/&/g,"&").replace(/"/g,""")).replace(/\n {4}/g," ")).replace(/^([\S ]+): +([\s\S]+?)$/gm,function(g,A,I){return C.metadata.parsed[A]=I,""})}return A.metadata?(g=C.converter._dispatch("metadata.before",g,A,C),g=g.replace(/^\s*«««+(\S*?)\n([\s\S]+?)\n»»»+\n/,function(g,A,C){return I(C),"¨M"}),g=g.replace(/^\s*---+(\S*?)\n([\s\S]+?)\n---+\n/,function(g,A,e){return A&&(C.metadata.format=A),I(e),"¨M"}),g=g.replace(/¨M/g,""),g=C.converter._dispatch("metadata.after",g,A,C)):g}),I.subParser("outdent",function(g,A,C){"use strict";return g=C.converter._dispatch("outdent.before",g,A,C),g=g.replace(/^(\t|[ ]{1,4})/gm,"¨0"),g=g.replace(/¨0/g,""),g=C.converter._dispatch("outdent.after",g,A,C)}),I.subParser("paragraphs",function(g,A,C){"use strict";for(var e=(g=(g=(g=C.converter._dispatch("paragraphs.before",g,A,C)).replace(/^\n+/g,"")).replace(/\n+$/g,"")).split(/\n{2,}/g),r=[],t=e.length,a=0;a<t;a++){var n=e[a];n.search(/¨(K|G)(\d+)\1/g)>=0?r.push(n):n.search(/\S/)>=0&&(n=(n=I.subParser("spanGamut")(n,A,C)).replace(/^([ \t]*)/g,"<p>"),n+="</p>",r.push(n))}for(t=r.length,a=0;a<t;a++){for(var o="",s=r[a],i=!1;/¨(K|G)(\d+)\1/.test(s);){var l=RegExp.$1,c=RegExp.$2;o=(o="K"===l?C.gHtmlBlocks[c]:i?I.subParser("encodeCode")(C.ghCodeBlocks[c].text,A,C):C.ghCodeBlocks[c].codeblock).replace(/\$/g,"$$$$"),s=s.replace(/(\n\n)?¨(K|G)\d+\2(\n\n)?/,o),/^<pre\b[^>]*>\s*<code\b[^>]*>/.test(s)&&(i=!0)}r[a]=s}return g=r.join("\n"),g=g.replace(/^\n+/g,""),g=g.replace(/\n+$/g,""),C.converter._dispatch("paragraphs.after",g,A,C)}),I.subParser("runExtension",function(g,A,C,I){"use strict";if(g.filter)A=g.filter(A,I.converter,C);else if(g.regex){var e=g.regex;e instanceof RegExp||(e=new RegExp(e,"g")),A=A.replace(e,g.replace)}return A}),I.subParser("spanGamut",function(g,A,C){"use strict";return g=C.converter._dispatch("spanGamut.before",g,A,C),g=I.subParser("codeSpans")(g,A,C),g=I.subParser("escapeSpecialCharsWithinTagAttributes")(g,A,C),g=I.subParser("encodeBackslashEscapes")(g,A,C),g=I.subParser("images")(g,A,C),g=I.subParser("anchors")(g,A,C),g=I.subParser("autoLinks")(g,A,C),g=I.subParser("simplifiedAutoLinks")(g,A,C),g=I.subParser("emoji")(g,A,C),g=I.subParser("underline")(g,A,C),g=I.subParser("italicsAndBold")(g,A,C),g=I.subParser("strikethrough")(g,A,C),g=I.subParser("ellipsis")(g,A,C),g=I.subParser("hashHTMLSpans")(g,A,C),g=I.subParser("encodeAmpsAndAngles")(g,A,C),A.simpleLineBreaks?/\n\n¨K/.test(g)||(g=g.replace(/\n+/g,"<br />\n")):g=g.replace(/ +\n/g,"<br />\n"),g=C.converter._dispatch("spanGamut.after",g,A,C)}),I.subParser("strikethrough",function(g,A,C){"use strict";return A.strikethrough&&(g=(g=C.converter._dispatch("strikethrough.before",g,A,C)).replace(/(?:~){2}([\s\S]+?)(?:~){2}/g,function(g,e){return function(g){return A.simplifiedAutoLink&&(g=I.subParser("simplifiedAutoLinks")(g,A,C)),"<del>"+g+"</del>"}(e)}),g=C.converter._dispatch("strikethrough.after",g,A,C)),g}),I.subParser("stripLinkDefinitions",function(g,A,C){"use strict";var e=function(g,e,r,t,a,n,o){return e=e.toLowerCase(),r.match(/^data:.+?\/.+?;base64,/)?C.gUrls[e]=r.replace(/\s/g,""):C.gUrls[e]=I.subParser("encodeAmpsAndAngles")(r,A,C),n?n+o:(o&&(C.gTitles[e]=o.replace(/"|'/g,""")),A.parseImgDimensions&&t&&a&&(C.gDimensions[e]={width:t,height:a}),"")};return g=(g+="¨0").replace(/^ {0,3}\[(.+)]:[ \t]*\n?[ \t]*<?(data:.+?\/.+?;base64,[A-Za-z0-9+/=\n]+?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*\n?[ \t]*(?:(\n*)["|'(](.+?)["|')][ \t]*)?(?:\n\n|(?=¨0)|(?=\n\[))/gm,e),g=g.replace(/^ {0,3}\[(.+)]:[ \t]*\n?[ \t]*<?([^>\s]+)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*\n?[ \t]*(?:(\n*)["|'(](.+?)["|')][ \t]*)?(?:\n+|(?=¨0))/gm,e),g=g.replace(/¨0/,"")}),I.subParser("tables",function(g,A,C){"use strict";function e(g){return/^:[ \t]*--*$/.test(g)?' style="text-align:left;"':/^--*[ \t]*:[ \t]*$/.test(g)?' style="text-align:right;"':/^:[ \t]*--*[ \t]*:$/.test(g)?' style="text-align:center;"':""}function r(g,e){var r="";return g=g.trim(),(A.tablesHeaderId||A.tableHeaderId)&&(r=' id="'+g.replace(/ /g,"_").toLowerCase()+'"'),g=I.subParser("spanGamut")(g,A,C),"<th"+r+e+">"+g+"</th>\n"}function t(g,e){return"<td"+e+">"+I.subParser("spanGamut")(g,A,C)+"</td>\n"}function a(g){var a,n=g.split("\n");for(a=0;a<n.length;++a)/^ {0,3}\|/.test(n[a])&&(n[a]=n[a].replace(/^ {0,3}\|/,"")),/\|[ \t]*$/.test(n[a])&&(n[a]=n[a].replace(/\|[ \t]*$/,"")),n[a]=I.subParser("codeSpans")(n[a],A,C);var o=n[0].split("|").map(function(g){return g.trim()}),s=n[1].split("|").map(function(g){return g.trim()}),i=[],l=[],c=[],u=[];for(n.shift(),n.shift(),a=0;a<n.length;++a)""!==n[a].trim()&&i.push(n[a].split("|").map(function(g){return g.trim()}));if(o.length<s.length)return g;for(a=0;a<s.length;++a)c.push(e(s[a]));for(a=0;a<o.length;++a)I.helper.isUndefined(c[a])&&(c[a]=""),l.push(r(o[a],c[a]));for(a=0;a<i.length;++a){for(var d=[],p=0;p<l.length;++p)I.helper.isUndefined(i[a][p]),d.push(t(i[a][p],c[p]));u.push(d)}return function(g,A){for(var C="<table>\n<thead>\n<tr>\n",I=g.length,e=0;e<I;++e)C+=g[e];for(C+="</tr>\n</thead>\n<tbody>\n",e=0;e<A.length;++e){C+="<tr>\n";for(var r=0;r<I;++r)C+=A[e][r];C+="</tr>\n"}return C+="</tbody>\n</table>\n"}(l,u)}if(!A.tables)return g;return g=C.converter._dispatch("tables.before",g,A,C),g=g.replace(/\\(\|)/g,I.helper.escapeCharactersCallback),g=g.replace(/^ {0,3}\|?.+\|.+\n {0,3}\|?[ \t]*:?[ \t]*(?:[-=]){2,}[ \t]*:?[ \t]*\|[ \t]*:?[ \t]*(?:[-=]){2,}[\s\S]+?(?:\n\n|¨0)/gm,a),g=g.replace(/^ {0,3}\|.+\|[ \t]*\n {0,3}\|[ \t]*:?[ \t]*(?:[-=]){2,}[ \t]*:?[ \t]*\|[ \t]*\n( {0,3}\|.+\|[ \t]*\n)*(?:\n|¨0)/gm,a),g=C.converter._dispatch("tables.after",g,A,C)}),I.subParser("underline",function(g,A,C){"use strict";return A.underline?(g=C.converter._dispatch("underline.before",g,A,C),g=A.literalMidWordUnderscores?g.replace(/\b_?__(\S[\s\S]*)___?\b/g,function(g,A){return"<u>"+A+"</u>"}):g.replace(/_?__(\S[\s\S]*?)___?/g,function(g,A){return/\S$/.test(A)?"<u>"+A+"</u>":g}),g=g.replace(/(_)/g,I.helper.escapeCharactersCallback),g=C.converter._dispatch("underline.after",g,A,C)):g}),I.subParser("unescapeSpecialChars",function(g,A,C){"use strict";return g=C.converter._dispatch("unescapeSpecialChars.before",g,A,C),g=g.replace(/¨E(\d+)E/g,function(g,A){var C=parseInt(A);return String.fromCharCode(C)}),g=C.converter._dispatch("unescapeSpecialChars.after",g,A,C)});"function"==typeof define&&define.amd?define(function(){"use strict";return I}):"undefined"!=typeof module&&module.exports?module.exports=I:this.showdown=I}).call(this); +//# sourceMappingURL=showdown.min.js.map diff --git a/www/vincent.demeester.fr/.fancyindex/styles.css b/www/vincent.demeester.fr/.fancyindex/styles.css @@ -0,0 +1,196 @@ +/* styles.css + * Better styling for of Nginx FancyIndex page + * © 2015-17, Lilian Besson (Naereen) and contributors, + * open-sourced under the MIT License, https://lbesson.mit-license.org/ + * hosted on GitHub, https://GitHub.com/Naereen/Nginx-Fancyindex-Theme + */ + +* { + font-family: 'Verdana', sans-serif; + margin: 0; + padding: 0; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +html { + color: #61666c; + font-weight: 300; + font-size: 1em; + line-height: 2em; +} + +body { + margin: 0 auto; + padding-top: 20px; + max-width: 800px; +} + +thead { + font-weight: 200; + font-size: 1.2em; +} + +h1 { + font-weight: 200; + text-align: center; + font-size: 1.4em; + line-height: 3em; +} + +a { + color: #5f5f5f; + text-decoration: none; +} + a:hover { + color: #000; + } + a.clear, a.clear:link, a.clear:visited { + color: #666; + padding: 2px 0; + font-weight: 400; + font-size: 14px; + margin: 0 0 0 20px; + line-height: 14px; + display: inline-block; + border-bottom: transparent 1px solid; + vertical-align: -10px; + -webkit-transition: all 300ms ease-in; + -moz-transition: all 300ms ease-in; + -ms-transition: all 300ms ease-in; + -o-transition: all 300ms ease-in; + transition: all 300ms ease-in; + } + +input { + margin: 0 auto; + font-size: 100%; + vertical-align: middle; + *overflow: visible; + line-height: normal; + font-family: 'Open Sans', sans-serif; + font-size: 12px; + font-weight: 300; + line-height: 18px; + color: #555; + display: inline-block; + height: 20px; + padding: 4px 32px 4px 6px; + margin-bottom: 9px; + font-size: 14px; + line-height: 20px; + color: #555; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + width: 196px; + background-color: #fff; + border: 1px solid #ccc; + -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075); + -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075); + box-shadow: inset 0 1px 1px rgba(0,0,0,0.075); + -webkit-transition: border linear .2s,box-shadow linear .2s; + -moz-transition: border linear .2s,box-shadow linear .2s; + -o-transition: border linear .2s,box-shadow linear .2s; + transition: border linear .2s,box-shadow linear .2s; +} + input:focus { + outline: 0; + border-color: rgba(0,0,0,0.8); + -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(0,0,0,0.6); + -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(0,0,0,0.6); + box-shadow: inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(0,0,0,0.6); + } + input::-moz-focus-inner { + padding: 0; + border: 0; + } + +#search { + display: block; + margin-left: auto; + margin-right: auto; + width: 250px; + margin-top: 20px; + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; + -webkit-appearance: textfield; + -webkit-transition: all 300ms ease-in; + -moz-transition: all 300ms ease-in; + -ms-transition: all 300ms ease-in; + -o-transition: all 300ms ease-in; + transition: all 300ms ease-in; +} + +table { + border-collapse: collapse; + font-size: 0.9em; + max-width: 100%; + margin: 20px auto 0; +} + +tr { + outline: 0; + border: 0; +} + tr:hover td { + background: #f6f6f6; + } + tr td:first-of-type { + padding-left: 10px; + padding-right: 10px; + } + tr.parent a { + color: #9099A3; + } + +th { + + text-align: left; + font-size: .75em; + padding-right: 20px; +} + th + th { + width: 25%; + } + th + th + th + th { + width: 5%; + } + +td { + padding: 5px 0; + outline: 0; + border: 0; + border-bottom: 1px solid #edf1f5; + vertical-align: middle; + text-align: left; + -webkit-transition: background 300ms ease-in; + -moz-transition: background 300ms ease-in; + -ms-transition: background 300ms ease-in; + -o-transition: background 300ms ease-in; + transition: background 300ms ease-in; +} + td:last-child,th:last-child { + text-align: right; + padding-right: 0; + } + td a { + display: block; + } + +.parent a:hover { + color: #2a2a2a; +} + +footer { + font-size:12px; + text-align:center; +} +footer a { + text-decoration: underline; + color:#990012; +} + +img { max-width: 100%; } diff --git a/www/vincent.demeester.fr/.gitignore b/www/vincent.demeester.fr/.gitignore @@ -0,0 +1,3 @@ +articles/* +public/* +posts/index.org+ \ No newline at end of file diff --git a/www/vincent.demeester.fr/Makefile b/www/vincent.demeester.fr/Makefile @@ -0,0 +1,116 @@ +# /Make/ it happen +# :PROPERTIES: +# :header-args: :tangle ~/src/home/www/vincent.demeester.fr/Makefile +# :header-args+: :comments org +# :ID: 6191455e-95bc-4abc-a5f5-a62606ab2ea7 +# :END: +# +# In order to publish this website, I am using [[file:make.org][make]]. In a nutshell, I am going to define a +# few target to get the content from my notes, export org files into html and copy more or +# less everything to the =public= folder. I will also define a clean and a publish target. +# +# The first part of my =Makefile= will be to define some constants that I want to use later +# on. Those are mainly to easily change where to look for the notes or where the emacs +# configuration is. + + +EMACS = +ifndef EMACS +EMACS = "emacs" +endif + +DOTEMACS = +ifndef DOTEMACS +DOTEMACS = "~/.config/emacs" +endif + +PUBLISH = +ifndef PUBLISH +PUBLISH = vincent.demeester.fr +endif + +NOTES = ~/desktop/org/notes + + + +# The default target will be name =build=. + + +all: build + +# Building =public/= and publishing it +# :PROPERTIES: +# :ID: 853f3d6b-f385-4091-9f9e-b04d17794e5c +# :END: +# +# To build the website, we will be using [[file:emacs.org][Emacs]] in batch mode, with some shared library *and* +# the actual [[id:631ced7a-f3f7-4a77-81a8-4a1884a6c4d4][publish]] script. + + +.PHONY: build +build: publish.el publish-common.el build-articles + @echo "Publishing... with current Emacs configurations." + ${EMACS} --debug-init --batch --directory $(DOTEMACS)/lisp/ --directory $(DOTEMACS)/lisp/vorg/ \ + --load publish-common.el --load publish.el \ + --funcall org-publish-all + +.PHONY: build-articles +build-articles: $(NOTES) + rsync -arv --delete --copy-links --exclude='*.private.org' --exclude='*.db' $(NOTES)/ articles/ + +$(NOTES): + $(error $(NOTES) doesn't exists…) + + + +# The =publish= target is gonna be really simple: I just need to copy the content to +# =~/desktop/sites= on the current machine, and the rest is automated. + + +.PHONY: publish +publish: build + rsync -a --progress --copy-links --delete public/assets/.fancyindex/ ~/desktop/sites/dl.sbr.pm/.fancyindex/ + rsync -a --progress --copy-links --delete public/ ~/desktop/sites/${PUBLISH}/ + +# Local server +# :PROPERTIES: +# :ID: 0e26a52e-5f66-42c6-934d-b45cfc9745b2 +# :END: +# +# Let's use =miniserve= (using [[file:nix.org][Nix]] with =nix-shell=) to serve the static website locally to +# validate my changes. + + +.PHONY: serve +serve: + nix-shell -p miniserve --command "miniserve --port=8181 --index=index.html public/" + +# Final nits of the =Makefile= +# :PROPERTIES: +# :ID: 5bec22c8-491b-4e03-855a-4b5f859473cf +# :END: +# +# One of the final step is to install the git hooks if any. I tend to have this target in all my +# personal =Makefile= at least. Let's also define a =pre-commit= target that will hold +# anything we need to do at =pre-commit=. + + +.PHONY: install-hooks +install-hooks: + if [ -e .git ]; then nix-shell -p git --run 'git config core.hooksPath .githooks'; fi + +.PHONY: pre-commit +pre-commit: README.md + + + +# And the final target is the =clean= one. This will remove any compile emacs-lisp file +# (=*.elc=), the =public= folder, and some org-mode metadata. + + +.PHONY: clean +clean: + @echo "Cleaning up.." + @-rm -rvf *.elc + @-rm -rvf public + @-rm -rv ~/.org-timestamps/* diff --git a/www/vincent.demeester.fr/about/index.org b/www/vincent.demeester.fr/about/index.org @@ -0,0 +1,26 @@ +#+TITLE: About me + +I develop mainly in Go, a little bit of Java, a tiny bit of Python ; I'm learning Haskell +and LISP with Clojure (and also Emacs Lisp). The more I learn about about Functionnal +languages and their way of thinking, the more I feel enlighten. I'm using Emacs as my main +editor and IntelliJ IDEA for Java-related stuff. I'm a GNU/Linux user, mainly NixOS +GNU/Linux. I no longer use or need Microsoft® Windows® but I'm, sometimes, using Mac OS X +at work (but I don't really like OS X and Apple in general). I'm a FSFE fellow since 2010 +(and previously a FSF fellow), Framasoft and La Quadrature du Net supporter/donator. I +also try to support various free software, like Mediagoblin. + +* Contact + +- Email :: vinSPAMcentAT@demSPAMeesterDOT.fr +- GnuPG :: [[https://vincent.demeester.fr/about/VincentDemeester.asc][5860 2A88]] +- Instant messaging :: + - vdemeester@jabber.fsfe.org + - vdemeester@irc.freenode.net #fsfe #docker #docker-compose #docker-dev ##openweb +- Socials :: + + [[https://twitter.com/vdemeest][@vdemeest]] on Twitter + + [[https://github.com/vdemeester][@vdemeester]] on GitHub + + [[https://gitlab.com/vdemeester][@vdemeester]] on Gitlab + +* Talks + +- TODO :: TBD diff --git a/www/vincent.demeester.fr/assets/.fancyindex/HEADER.md b/www/vincent.demeester.fr/assets/.fancyindex/HEADER.md @@ -0,0 +1,4 @@ +# Test of a HEADER.md file +## Test of a HEADER.md file +### Test of a HEADER.md file +#### Test of a HEADER.md file diff --git a/www/vincent.demeester.fr/assets/.fancyindex/addNginxFancyIndexForm.js b/www/vincent.demeester.fr/assets/.fancyindex/addNginxFancyIndexForm.js @@ -0,0 +1,33 @@ +// addNginxFancyIndexForm.js +// Add a small form to filter through the output of Nginx FancyIndex page +// © 2017, Lilian Besson (Naereen) and contributors, +// open-sourced under the MIT License, https://lbesson.mit-license.org/ +// hosted on GitHub, https://GitHub.com/Naereen/Nginx-Fancyindex-Theme +var form = document.createElement('form'); +var input = document.createElement('input'); + +input.name = 'filter'; +input.id = 'search'; +input.placeholder = 'Type to search...'; + +form.appendChild(input); + +document.querySelector('h1').after(form); + +var listItems = [].slice.call(document.querySelectorAll('#list tbody tr')); + +input.addEventListener('keyup', function () { + var i, + // Word sequence _matching_ to input. All, except last, words must be _complete_. + e = "(^|.*[^\\pL])" + this.value.trim().split(/\s+/).join("([^\\pL]|[^\\pL].*[^\\pL])") + ".*$", + n = RegExp(e, "i"); + listItems.forEach(function(item) { + item.removeAttribute('hidden'); + }); + listItems.filter(function(item) { + i = item.querySelector('td').textContent.replace(/\s+/g, " "); + return !n.test(i); + }).forEach(function(item) { + item.hidden = true; + }); +}); diff --git a/www/vincent.demeester.fr/assets/.fancyindex/footer.html b/www/vincent.demeester.fr/assets/.fancyindex/footer.html @@ -0,0 +1,40 @@ +<section id="raw_include_README_md"></section> +</main> +<footer> + <span class='copyright'> + Content and design by Vincent Demeester + (<a rel='licence' href='http://creativecommons.org/licenses/by-nc-sa/3.0/'>Some rights reserved</a>) + </span> +</footer> +<script src="https://unpkg.com/xregexp/xregexp-all.js"></script> +<script type="text/javascript" src="/.fancyindex/addNginxFancyIndexForm.js"></script> +<script type="text/javascript" src="/.fancyindex/showdown.min.js"></script> +<script type="text/javascript" defer> + var converter = new showdown.Converter(); + $( "#raw_include_HEADER_md" ).load( "HEADER.md", function (){ + var elem = document.querySelector("#raw_include_HEADER_md"); + // strip leading whitespace so it isn't evaluated as code + var text = elem.innerHTML; + // console.log("text =", text); + text = text.replace(/\n[ ]*/g, '\n'); + // console.log("text =", text); + var html = converter.makeHtml(text); + // console.log("html =", html); + // here, have some HTML + elem.innerHTML = html; + }); + $( "#raw_include_README_md" ).load( "README.md", function (){ + var elem = document.querySelector("#raw_include_README_md"); + // strip leading whitespace so it isn't evaluated as code + var text = elem.innerHTML; + // console.log("text =", text); + text = text.replace(/\n[ ]*/g, '\n'); + // console.log("text =", text); + var html = converter.makeHtml(text); + // console.log("html =", html); + // here, have some HTML + elem.innerHTML = html; + }); +</script> +</body> +</html> diff --git a/www/vincent.demeester.fr/assets/.fancyindex/header.html b/www/vincent.demeester.fr/assets/.fancyindex/header.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <meta http-equiv="x-ua-compatible" content="IE=edge"> + <title>Directory</title> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="https://vincent.demeester.fr/css/new.css"> + <script type="text/javascript" src="/.fancyindex/jquery.min.js"></script> + </head> + <body> + <header id="top" class="status"> + <nav> + <img src="https://vincent.demeester.fr/images/favicon.ico" id="sitelogo"/> <a href='https://vincent.demeester.fr/'>home</a> / + <a href='https://vincent.demeester.fr/posts/'>posts</a> (<a href='https://vincent.demeester.fr/index.xml'>rss</a>) / + <a href='https://vincent.demeester.fr/articles/'>articles</a> / + <a href='https://vincent.demeester.fr/configurations/'>configurations</a> / + <a href='https://dl.sbr.pm/'>files</a> / + <a href='https://vincent.demeester.fr/about/'>about</a></li> + </nav> + </header> + <main id="content"> + <header id="raw_include_HEADER_md"> + </header> +<h1>Directory: diff --git a/www/vincent.demeester.fr/assets/.fancyindex/jquery.min.js b/www/vincent.demeester.fr/assets/.fancyindex/jquery.min.js @@ -0,0 +1,5 @@ +/*! jQuery v2.1.0 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */ +/* Used on Lilian Besson's web pages ~ 2011-2014 */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k="".trim,l={},m=a.document,n="2.1.0",o=function(a,b){return new o.fn.init(a,b)},p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};o.fn=o.prototype={jquery:n,constructor:o,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=o.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return o.each(this,a,b)},map:function(a){return this.pushStack(o.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},o.extend=o.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||o.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(o.isPlainObject(d)||(e=o.isArray(d)))?(e?(e=!1,f=c&&o.isArray(c)?c:[]):f=c&&o.isPlainObject(c)?c:{},g[b]=o.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},o.extend({expando:"jQuery"+(n+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===o.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return a-parseFloat(a)>=0},isPlainObject:function(a){if("object"!==o.type(a)||a.nodeType||o.isWindow(a))return!1;try{if(a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(b){return!1}return!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=o.trim(a),a&&(1===a.indexOf("use strict")?(b=m.createElement("script"),b.text=a,m.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":k.call(a)},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?o.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),o.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||o.guid++,f):void 0},now:Date.now,support:l}),o.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=a.length,c=o.type(a);return"function"===c||o.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s="sizzle"+-new Date,t=a.document,u=0,v=0,w=eb(),x=eb(),y=eb(),z=function(a,b){return a===b&&(j=!0),0},A="undefined",B=1<<31,C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=D.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},J="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",K="[\\x20\\t\\r\\n\\f]",L="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",M=L.replace("w","w#"),N="\\["+K+"*("+L+")"+K+"*(?:([*^$|!~]?=)"+K+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+M+")|)|)"+K+"*\\]",O=":("+L+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+N.replace(3,8)+")*)|.*)\\)|)",P=new RegExp("^"+K+"+|((?:^|[^\\\\])(?:\\\\.)*)"+K+"+$","g"),Q=new RegExp("^"+K+"*,"+K+"*"),R=new RegExp("^"+K+"*([>+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(O),U=new RegExp("^"+M+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L.replace("w","w*")+")"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=/'|\\/g,ab=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),bb=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{G.apply(D=H.call(t.childNodes),t.childNodes),D[t.childNodes.length].nodeType}catch(cb){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function db(a,b,d,e){var f,g,h,i,j,m,p,q,u,v;if((b?b.ownerDocument||b:t)!==l&&k(b),b=b||l,d=d||[],!a||"string"!=typeof a)return d;if(1!==(i=b.nodeType)&&9!==i)return[];if(n&&!e){if(f=Z.exec(a))if(h=f[1]){if(9===i){if(g=b.getElementById(h),!g||!g.parentNode)return d;if(g.id===h)return d.push(g),d}else if(b.ownerDocument&&(g=b.ownerDocument.getElementById(h))&&r(b,g)&&g.id===h)return d.push(g),d}else{if(f[2])return G.apply(d,b.getElementsByTagName(a)),d;if((h=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(h)),d}if(c.qsa&&(!o||!o.test(a))){if(q=p=s,u=b,v=9===i&&a,1===i&&"object"!==b.nodeName.toLowerCase()){m=ob(a),(p=b.getAttribute("id"))?q=p.replace(_,"\\$&"):b.setAttribute("id",q),q="[id='"+q+"'] ",j=m.length;while(j--)m[j]=q+pb(m[j]);u=$.test(a)&&mb(b.parentNode)||b,v=m.join(",")}if(v)try{return G.apply(d,u.querySelectorAll(v)),d}catch(w){}finally{p||b.removeAttribute("id")}}}return xb(a.replace(P,"$1"),b,d,e)}function eb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function fb(a){return a[s]=!0,a}function gb(a){var b=l.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function hb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function ib(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||B)-(~a.sourceIndex||B);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function jb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function kb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function lb(a){return fb(function(b){return b=+b,fb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function mb(a){return a&&typeof a.getElementsByTagName!==A&&a}c=db.support={},f=db.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},k=db.setDocument=function(a){var b,e=a?a.ownerDocument||a:t,g=e.defaultView;return e!==l&&9===e.nodeType&&e.documentElement?(l=e,m=e.documentElement,n=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){k()},!1):g.attachEvent&&g.attachEvent("onunload",function(){k()})),c.attributes=gb(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=gb(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(e.getElementsByClassName)&&gb(function(a){return a.innerHTML="<div class='a'></div><div class='a i'></div>",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=gb(function(a){return m.appendChild(a).id=s,!e.getElementsByName||!e.getElementsByName(s).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==A&&n){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ab,bb);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ab,bb);return function(a){var c=typeof a.getAttributeNode!==A&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==A?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==A&&n?b.getElementsByClassName(a):void 0},p=[],o=[],(c.qsa=Y.test(e.querySelectorAll))&&(gb(function(a){a.innerHTML="<select t=''><option selected=''></option></select>",a.querySelectorAll("[t^='']").length&&o.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||o.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll(":checked").length||o.push(":checked")}),gb(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&o.push("name"+K+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||o.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),o.push(",.*:")})),(c.matchesSelector=Y.test(q=m.webkitMatchesSelector||m.mozMatchesSelector||m.oMatchesSelector||m.msMatchesSelector))&&gb(function(a){c.disconnectedMatch=q.call(a,"div"),q.call(a,"[s!='']:x"),p.push("!=",O)}),o=o.length&&new RegExp(o.join("|")),p=p.length&&new RegExp(p.join("|")),b=Y.test(m.compareDocumentPosition),r=b||Y.test(m.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},z=b?function(a,b){if(a===b)return j=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===t&&r(t,a)?-1:b===e||b.ownerDocument===t&&r(t,b)?1:i?I.call(i,a)-I.call(i,b):0:4&d?-1:1)}:function(a,b){if(a===b)return j=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],k=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:i?I.call(i,a)-I.call(i,b):0;if(f===g)return ib(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)k.unshift(c);while(h[d]===k[d])d++;return d?ib(h[d],k[d]):h[d]===t?-1:k[d]===t?1:0},e):l},db.matches=function(a,b){return db(a,null,null,b)},db.matchesSelector=function(a,b){if((a.ownerDocument||a)!==l&&k(a),b=b.replace(S,"='$1']"),!(!c.matchesSelector||!n||p&&p.test(b)||o&&o.test(b)))try{var d=q.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return db(b,l,null,[a]).length>0},db.contains=function(a,b){return(a.ownerDocument||a)!==l&&k(a),r(a,b)},db.attr=function(a,b){(a.ownerDocument||a)!==l&&k(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!n):void 0;return void 0!==f?f:c.attributes||!n?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},db.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},db.uniqueSort=function(a){var b,d=[],e=0,f=0;if(j=!c.detectDuplicates,i=!c.sortStable&&a.slice(0),a.sort(z),j){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return i=null,a},e=db.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=db.selectors={cacheLength:50,createPseudo:fb,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ab,bb),a[3]=(a[4]||a[5]||"").replace(ab,bb),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||db.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&db.error(a[0]),a},PSEUDO:function(a){var b,c=!a[5]&&a[2];return V.CHILD.test(a[0])?null:(a[3]&&void 0!==a[4]?a[2]=a[4]:c&&T.test(c)&&(b=ob(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ab,bb).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=w[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&w(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==A&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=db.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),t=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&t){k=q[s]||(q[s]={}),j=k[a]||[],n=j[0]===u&&j[1],m=j[0]===u&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[u,n,m];break}}else if(t&&(j=(b[s]||(b[s]={}))[a])&&j[0]===u)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(t&&((l[s]||(l[s]={}))[a]=[u,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||db.error("unsupported pseudo: "+a);return e[s]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?fb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:fb(function(a){var b=[],c=[],d=g(a.replace(P,"$1"));return d[s]?fb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:fb(function(a){return function(b){return db(a,b).length>0}}),contains:fb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:fb(function(a){return U.test(a||"")||db.error("unsupported lang: "+a),a=a.replace(ab,bb).toLowerCase(),function(b){var c;do if(c=n?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===m},focus:function(a){return a===l.activeElement&&(!l.hasFocus||l.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:lb(function(){return[0]}),last:lb(function(a,b){return[b-1]}),eq:lb(function(a,b,c){return[0>c?c+b:c]}),even:lb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:lb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:lb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:lb(function(a,b,c){for(var d=0>c?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=jb(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=kb(b);function nb(){}nb.prototype=d.filters=d.pseudos,d.setFilters=new nb;function ob(a,b){var c,e,f,g,h,i,j,k=x[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){(!c||(e=Q.exec(h)))&&(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=R.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(P," ")}),h=h.slice(c.length));for(g in d.filter)!(e=V[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?db.error(a):x(a,i).slice(0)}function pb(a){for(var b=0,c=a.length,d="";c>b;b++)d+=a[b].value;return d}function qb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=v++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[u,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[s]||(b[s]={}),(h=i[d])&&h[0]===u&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function rb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function sb(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function tb(a,b,c,d,e,f){return d&&!d[s]&&(d=tb(d)),e&&!e[s]&&(e=tb(e,f)),fb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||wb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:sb(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=sb(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?I.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=sb(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ub(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],i=g||d.relative[" "],j=g?1:0,k=qb(function(a){return a===b},i,!0),l=qb(function(a){return I.call(b,a)>-1},i,!0),m=[function(a,c,d){return!g&&(d||c!==h)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>j;j++)if(c=d.relative[a[j].type])m=[qb(rb(m),c)];else{if(c=d.filter[a[j].type].apply(null,a[j].matches),c[s]){for(e=++j;f>e;e++)if(d.relative[a[e].type])break;return tb(j>1&&rb(m),j>1&&pb(a.slice(0,j-1).concat({value:" "===a[j-2].type?"*":""})).replace(P,"$1"),c,e>j&&ub(a.slice(j,e)),f>e&&ub(a=a.slice(e)),f>e&&pb(a))}m.push(c)}return rb(m)}function vb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,i,j,k){var m,n,o,p=0,q="0",r=f&&[],s=[],t=h,v=f||e&&d.find.TAG("*",k),w=u+=null==t?1:Math.random()||.1,x=v.length;for(k&&(h=g!==l&&g);q!==x&&null!=(m=v[q]);q++){if(e&&m){n=0;while(o=a[n++])if(o(m,g,i)){j.push(m);break}k&&(u=w)}c&&((m=!o&&m)&&p--,f&&r.push(m))}if(p+=q,c&&q!==p){n=0;while(o=b[n++])o(r,s,g,i);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=E.call(j));s=sb(s)}G.apply(j,s),k&&!f&&s.length>0&&p+b.length>1&&db.uniqueSort(j)}return k&&(u=w,h=t),r};return c?fb(f):f}g=db.compile=function(a,b){var c,d=[],e=[],f=y[a+" "];if(!f){b||(b=ob(a)),c=b.length;while(c--)f=ub(b[c]),f[s]?d.push(f):e.push(f);f=y(a,vb(e,d))}return f};function wb(a,b,c){for(var d=0,e=b.length;e>d;d++)db(a,b[d],c);return c}function xb(a,b,e,f){var h,i,j,k,l,m=ob(a);if(!f&&1===m.length){if(i=m[0]=m[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&c.getById&&9===b.nodeType&&n&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(ab,bb),b)||[])[0],!b)return e;a=a.slice(i.shift().value.length)}h=V.needsContext.test(a)?0:i.length;while(h--){if(j=i[h],d.relative[k=j.type])break;if((l=d.find[k])&&(f=l(j.matches[0].replace(ab,bb),$.test(i[0].type)&&mb(b.parentNode)||b))){if(i.splice(h,1),a=f.length&&pb(i),!a)return G.apply(e,f),e;break}}}return g(a,m)(f,b,!n,e,$.test(a)&&mb(b.parentNode)||b),e}return c.sortStable=s.split("").sort(z).join("")===s,c.detectDuplicates=!!j,k(),c.sortDetached=gb(function(a){return 1&a.compareDocumentPosition(l.createElement("div"))}),gb(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||hb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&gb(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||hb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),gb(function(a){return null==a.getAttribute("disabled")})||hb(J,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),db}(a);o.find=t,o.expr=t.selectors,o.expr[":"]=o.expr.pseudos,o.unique=t.uniqueSort,o.text=t.getText,o.isXMLDoc=t.isXML,o.contains=t.contains;var u=o.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(o.isFunction(b))return o.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return o.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return o.filter(b,a,c);b=o.filter(b,a)}return o.grep(a,function(a){return g.call(b,a)>=0!==c})}o.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?o.find.matchesSelector(d,a)?[d]:[]:o.find.matches(a,o.grep(b,function(a){return 1===a.nodeType}))},o.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(o(a).filter(function(){for(b=0;c>b;b++)if(o.contains(e[b],this))return!0}));for(b=0;c>b;b++)o.find(a,e[b],d);return d=this.pushStack(c>1?o.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?o(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=o.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof o?b[0]:b,o.merge(this,o.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:m,!0)),v.test(c[1])&&o.isPlainObject(b))for(c in b)o.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=m.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=m,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):o.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(o):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),o.makeArray(a,this))};A.prototype=o.fn,y=o(m);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};o.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&o(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),o.fn.extend({has:function(a){var b=o(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(o.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?o(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&o.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?o.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(o(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(o.unique(o.merge(this.get(),o(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}o.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return o.dir(a,"parentNode")},parentsUntil:function(a,b,c){return o.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return o.dir(a,"nextSibling")},prevAll:function(a){return o.dir(a,"previousSibling")},nextUntil:function(a,b,c){return o.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return o.dir(a,"previousSibling",c)},siblings:function(a){return o.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return o.sibling(a.firstChild)},contents:function(a){return a.contentDocument||o.merge([],a.childNodes)}},function(a,b){o.fn[a]=function(c,d){var e=o.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=o.filter(d,e)),this.length>1&&(C[a]||o.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return o.each(a.match(E)||[],function(a,c){b[c]=!0}),b}o.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):o.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){o.each(b,function(b,c){var d=o.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&o.each(arguments,function(a,b){var c;while((c=o.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?o.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},o.extend({Deferred:function(a){var b=[["resolve","done",o.Callbacks("once memory"),"resolved"],["reject","fail",o.Callbacks("once memory"),"rejected"],["notify","progress",o.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return o.Deferred(function(c){o.each(b,function(b,f){var g=o.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&o.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?o.extend(a,d):d}},e={};return d.pipe=d.then,o.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&o.isFunction(a.promise)?e:0,g=1===f?a:o.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&o.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;o.fn.ready=function(a){return o.ready.promise().done(a),this},o.extend({isReady:!1,readyWait:1,holdReady:function(a){a?o.readyWait++:o.ready(!0)},ready:function(a){(a===!0?--o.readyWait:o.isReady)||(o.isReady=!0,a!==!0&&--o.readyWait>0||(H.resolveWith(m,[o]),o.fn.trigger&&o(m).trigger("ready").off("ready")))}});function I(){m.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),o.ready()}o.ready.promise=function(b){return H||(H=o.Deferred(),"complete"===m.readyState?setTimeout(o.ready):(m.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},o.ready.promise();var J=o.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===o.type(c)){e=!0;for(h in c)o.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,o.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(o(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};o.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=o.expando+Math.random()}K.uid=1,K.accepts=o.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,o.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(o.isEmptyObject(f))o.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,o.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{o.isArray(b)?d=b.concat(b.map(o.camelCase)):(e=o.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!o.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?o.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}o.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){return M.access(a,b,c)},removeData:function(a,b){M.remove(a,b)},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),o.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length; +while(c--)d=g[c].name,0===d.indexOf("data-")&&(d=o.camelCase(d.slice(5)),P(f,d,e[d]));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=o.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),o.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||o.isArray(c)?d=L.access(a,b,o.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=o.queue(a,b),d=c.length,e=c.shift(),f=o._queueHooks(a,b),g=function(){o.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:o.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),o.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?o.queue(this[0],a):void 0===b?this:this.each(function(){var c=o.queue(this,a,b);o._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&o.dequeue(this,a)})},dequeue:function(a){return this.each(function(){o.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=o.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=L.get(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var Q=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,R=["Top","Right","Bottom","Left"],S=function(a,b){return a=b||a,"none"===o.css(a,"display")||!o.contains(a.ownerDocument,a)},T=/^(?:checkbox|radio)$/i;!function(){var a=m.createDocumentFragment(),b=a.appendChild(m.createElement("div"));b.innerHTML="<input type='radio' checked='checked' name='t'/>",l.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="<textarea>x</textarea>",l.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";l.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return m.activeElement}catch(a){}}o.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=o.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof o!==U&&o.event.triggered!==b.type?o.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],n=q=h[1],p=(h[2]||"").split(".").sort(),n&&(l=o.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=o.event.special[n]||{},k=o.extend({type:n,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&o.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(n,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),o.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],n=q=h[1],p=(h[2]||"").split(".").sort(),n){l=o.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||o.removeEvent(a,n,r.handle),delete i[n])}else for(n in i)o.event.remove(a,n+b[j],c,d,!0);o.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,n,p=[d||m],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||m,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+o.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[o.expando]?b:new o.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:o.makeArray(c,[b]),n=o.event.special[q]||{},e||!n.trigger||n.trigger.apply(d,c)!==!1)){if(!e&&!n.noBubble&&!o.isWindow(d)){for(i=n.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||m)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:n.bindType||q,l=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),l&&l.apply(g,c),l=k&&g[k],l&&l.apply&&o.acceptData(g)&&(b.result=l.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||n._default&&n._default.apply(p.pop(),c)!==!1||!o.acceptData(d)||k&&o.isFunction(d[q])&&!o.isWindow(d)&&(h=d[k],h&&(d[k]=null),o.event.triggered=q,d[q](),o.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=o.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=o.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=o.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((o.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?o(e,this).index(i)>=0:o.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h<b.length&&g.push({elem:this,handlers:b.slice(h)}),g},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return null==a.which&&(a.which=null!=b.charCode?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,b){var c,d,e,f=b.button;return null==a.pageX&&null!=b.clientX&&(c=a.target.ownerDocument||m,d=c.documentElement,e=c.body,a.pageX=b.clientX+(d&&d.scrollLeft||e&&e.scrollLeft||0)-(d&&d.clientLeft||e&&e.clientLeft||0),a.pageY=b.clientY+(d&&d.scrollTop||e&&e.scrollTop||0)-(d&&d.clientTop||e&&e.clientTop||0)),a.which||void 0===f||(a.which=1&f?1:2&f?3:4&f?2:0),a}},fix:function(a){if(a[o.expando])return a;var b,c,d,e=a.type,f=a,g=this.fixHooks[e];g||(this.fixHooks[e]=g=W.test(e)?this.mouseHooks:V.test(e)?this.keyHooks:{}),d=g.props?this.props.concat(g.props):this.props,a=new o.Event(f),b=d.length;while(b--)c=d[b],a[c]=f[c];return a.target||(a.target=m),3===a.target.nodeType&&(a.target=a.target.parentNode),g.filter?g.filter(a,f):a},special:{load:{noBubble:!0},focus:{trigger:function(){return this!==_()&&this.focus?(this.focus(),!1):void 0},delegateType:"focusin"},blur:{trigger:function(){return this===_()&&this.blur?(this.blur(),!1):void 0},delegateType:"focusout"},click:{trigger:function(){return"checkbox"===this.type&&this.click&&o.nodeName(this,"input")?(this.click(),!1):void 0},_default:function(a){return o.nodeName(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&(a.originalEvent.returnValue=a.result)}}},simulate:function(a,b,c,d){var e=o.extend(new o.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?o.event.trigger(e,null,b):o.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},o.removeEvent=function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)},o.Event=function(a,b){return this instanceof o.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.getPreventDefault&&a.getPreventDefault()?Z:$):this.type=a,b&&o.extend(this,b),this.timeStamp=a&&a.timeStamp||o.now(),void(this[o.expando]=!0)):new o.Event(a,b)},o.Event.prototype={isDefaultPrevented:$,isPropagationStopped:$,isImmediatePropagationStopped:$,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=Z,a&&a.preventDefault&&a.preventDefault()},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=Z,a&&a.stopPropagation&&a.stopPropagation()},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=Z,this.stopPropagation()}},o.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){o.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return(!e||e!==d&&!o.contains(d,e))&&(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),l.focusinBubbles||o.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){o.event.simulate(b,a.target,o.event.fix(a),!0)};o.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=L.access(d,b);e||d.addEventListener(a,c,!0),L.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=L.access(d,b)-1;e?L.access(d,b,e):(d.removeEventListener(a,c,!0),L.remove(d,b))}}}),o.fn.extend({on:function(a,b,c,d,e){var f,g;if("object"==typeof a){"string"!=typeof b&&(c=c||b,b=void 0);for(g in a)this.on(g,b,c,a[g],e);return this}if(null==c&&null==d?(d=b,c=b=void 0):null==d&&("string"==typeof b?(d=c,c=void 0):(d=c,c=b,b=void 0)),d===!1)d=$;else if(!d)return this;return 1===e&&(f=d,d=function(a){return o().off(a),f.apply(this,arguments)},d.guid=f.guid||(f.guid=o.guid++)),this.each(function(){o.event.add(this,a,d,c,b)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,o(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return(b===!1||"function"==typeof b)&&(c=b,b=void 0),c===!1&&(c=$),this.each(function(){o.event.remove(this,a,c,b)})},trigger:function(a,b){return this.each(function(){o.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];return c?o.event.trigger(a,b,c,!0):void 0}});var ab=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bb=/<([\w:]+)/,cb=/<|&#?\w+;/,db=/<(?:script|style|link)/i,eb=/checked\s*(?:[^=]|=\s*.checked.)/i,fb=/^$|\/(?:java|ecma)script/i,gb=/^true\/(.*)/,hb=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,ib={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};ib.optgroup=ib.option,ib.tbody=ib.tfoot=ib.colgroup=ib.caption=ib.thead,ib.th=ib.td;function jb(a,b){return o.nodeName(a,"table")&&o.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function kb(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function lb(a){var b=gb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function mb(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function nb(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)o.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=o.extend({},h),M.set(b,i))}}function ob(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&o.nodeName(a,b)?o.merge([a],c):c}function pb(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}o.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=o.contains(a.ownerDocument,a);if(!(l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||o.isXMLDoc(a)))for(g=ob(h),f=ob(a),d=0,e=f.length;e>d;d++)pb(f[d],g[d]);if(b)if(c)for(f=f||ob(a),g=g||ob(h),d=0,e=f.length;e>d;d++)nb(f[d],g[d]);else nb(a,h);return g=ob(h,"script"),g.length>0&&mb(g,!i&&ob(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,n=a.length;n>m;m++)if(e=a[m],e||0===e)if("object"===o.type(e))o.merge(l,e.nodeType?[e]:e);else if(cb.test(e)){f=f||k.appendChild(b.createElement("div")),g=(bb.exec(e)||["",""])[1].toLowerCase(),h=ib[g]||ib._default,f.innerHTML=h[1]+e.replace(ab,"<$1></$2>")+h[2],j=h[0];while(j--)f=f.lastChild;o.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===o.inArray(e,d))&&(i=o.contains(e.ownerDocument,e),f=ob(k.appendChild(e),"script"),i&&mb(f),c)){j=0;while(e=f[j++])fb.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f,g,h=o.event.special,i=0;void 0!==(c=a[i]);i++){if(o.acceptData(c)&&(f=c[L.expando],f&&(b=L.cache[f]))){if(d=Object.keys(b.events||{}),d.length)for(g=0;void 0!==(e=d[g]);g++)h[e]?o.event.remove(c,e):o.removeEvent(c,e,b.handle);L.cache[f]&&delete L.cache[f]}delete M.cache[c[M.expando]]}}}),o.fn.extend({text:function(a){return J(this,function(a){return void 0===a?o.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?o.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||o.cleanData(ob(c)),c.parentNode&&(b&&o.contains(c.ownerDocument,c)&&mb(ob(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(o.cleanData(ob(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return o.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!db.test(a)&&!ib[(bb.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(ab,"<$1></$2>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(o.cleanData(ob(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,o.cleanData(ob(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,k=this.length,m=this,n=k-1,p=a[0],q=o.isFunction(p);if(q||k>1&&"string"==typeof p&&!l.checkClone&&eb.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(k&&(c=o.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=o.map(ob(c,"script"),kb),g=f.length;k>j;j++)h=c,j!==n&&(h=o.clone(h,!0,!0),g&&o.merge(f,ob(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,o.map(f,lb),j=0;g>j;j++)h=f[j],fb.test(h.type||"")&&!L.access(h,"globalEval")&&o.contains(i,h)&&(h.src?o._evalUrl&&o._evalUrl(h.src):o.globalEval(h.textContent.replace(hb,"")))}return this}}),o.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){o.fn[a]=function(a){for(var c,d=[],e=o(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),o(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qb,rb={};function sb(b,c){var d=o(c.createElement(b)).appendTo(c.body),e=a.getDefaultComputedStyle?a.getDefaultComputedStyle(d[0]).display:o.css(d[0],"display");return d.detach(),e}function tb(a){var b=m,c=rb[a];return c||(c=sb(a,b),"none"!==c&&c||(qb=(qb||o("<iframe frameborder='0' width='0' height='0'/>")).appendTo(b.documentElement),b=qb[0].contentDocument,b.write(),b.close(),c=sb(a,b),qb.detach()),rb[a]=c),c}var ub=/^margin/,vb=new RegExp("^("+Q+")(?!px)[a-z%]+$","i"),wb=function(a){return a.ownerDocument.defaultView.getComputedStyle(a,null)};function xb(a,b,c){var d,e,f,g,h=a.style;return c=c||wb(a),c&&(g=c.getPropertyValue(b)||c[b]),c&&(""!==g||o.contains(a.ownerDocument,a)||(g=o.style(a,b)),vb.test(g)&&ub.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0!==g?g+"":g}function yb(a,b){return{get:function(){return a()?void delete this.get:(this.get=b).apply(this,arguments)}}}!function(){var b,c,d="padding:0;margin:0;border:0;display:block;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box",e=m.documentElement,f=m.createElement("div"),g=m.createElement("div");g.style.backgroundClip="content-box",g.cloneNode(!0).style.backgroundClip="",l.clearCloneStyle="content-box"===g.style.backgroundClip,f.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",f.appendChild(g);function h(){g.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%",e.appendChild(f);var d=a.getComputedStyle(g,null);b="1%"!==d.top,c="4px"===d.width,e.removeChild(f)}a.getComputedStyle&&o.extend(l,{pixelPosition:function(){return h(),b},boxSizingReliable:function(){return null==c&&h(),c},reliableMarginRight:function(){var b,c=g.appendChild(m.createElement("div"));return c.style.cssText=g.style.cssText=d,c.style.marginRight=c.style.width="0",g.style.width="1px",e.appendChild(f),b=!parseFloat(a.getComputedStyle(c,null).marginRight),e.removeChild(f),g.innerHTML="",b}})}(),o.swap=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};var zb=/^(none|table(?!-c[ea]).+)/,Ab=new RegExp("^("+Q+")(.*)$","i"),Bb=new RegExp("^([+-])=("+Q+")","i"),Cb={position:"absolute",visibility:"hidden",display:"block"},Db={letterSpacing:0,fontWeight:400},Eb=["Webkit","O","Moz","ms"];function Fb(a,b){if(b in a)return b;var c=b[0].toUpperCase()+b.slice(1),d=b,e=Eb.length;while(e--)if(b=Eb[e]+c,b in a)return b;return d}function Gb(a,b,c){var d=Ab.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function Hb(a,b,c,d,e){for(var f=c===(d?"border":"content")?4:"width"===b?1:0,g=0;4>f;f+=2)"margin"===c&&(g+=o.css(a,c+R[f],!0,e)),d?("content"===c&&(g-=o.css(a,"padding"+R[f],!0,e)),"margin"!==c&&(g-=o.css(a,"border"+R[f]+"Width",!0,e))):(g+=o.css(a,"padding"+R[f],!0,e),"padding"!==c&&(g+=o.css(a,"border"+R[f]+"Width",!0,e)));return g}function Ib(a,b,c){var d=!0,e="width"===b?a.offsetWidth:a.offsetHeight,f=wb(a),g="border-box"===o.css(a,"boxSizing",!1,f);if(0>=e||null==e){if(e=xb(a,b,f),(0>e||null==e)&&(e=a.style[b]),vb.test(e))return e;d=g&&(l.boxSizingReliable()||e===a.style[b]),e=parseFloat(e)||0}return e+Hb(a,b,c||(g?"border":"content"),d,f)+"px"}function Jb(a,b){for(var c,d,e,f=[],g=0,h=a.length;h>g;g++)d=a[g],d.style&&(f[g]=L.get(d,"olddisplay"),c=d.style.display,b?(f[g]||"none"!==c||(d.style.display=""),""===d.style.display&&S(d)&&(f[g]=L.access(d,"olddisplay",tb(d.nodeName)))):f[g]||(e=S(d),(c&&"none"!==c||!e)&&L.set(d,"olddisplay",e?c:o.css(d,"display"))));for(g=0;h>g;g++)d=a[g],d.style&&(b&&"none"!==d.style.display&&""!==d.style.display||(d.style.display=b?f[g]||"":"none"));return a}o.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=xb(a,"opacity");return""===c?"1":c}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=o.camelCase(b),i=a.style;return b=o.cssProps[h]||(o.cssProps[h]=Fb(i,h)),g=o.cssHooks[b]||o.cssHooks[h],void 0===c?g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:i[b]:(f=typeof c,"string"===f&&(e=Bb.exec(c))&&(c=(e[1]+1)*e[2]+parseFloat(o.css(a,b)),f="number"),null!=c&&c===c&&("number"!==f||o.cssNumber[h]||(c+="px"),l.clearCloneStyle||""!==c||0!==b.indexOf("background")||(i[b]="inherit"),g&&"set"in g&&void 0===(c=g.set(a,c,d))||(i[b]="",i[b]=c)),void 0)}},css:function(a,b,c,d){var e,f,g,h=o.camelCase(b);return b=o.cssProps[h]||(o.cssProps[h]=Fb(a.style,h)),g=o.cssHooks[b]||o.cssHooks[h],g&&"get"in g&&(e=g.get(a,!0,c)),void 0===e&&(e=xb(a,b,d)),"normal"===e&&b in Db&&(e=Db[b]),""===c||c?(f=parseFloat(e),c===!0||o.isNumeric(f)?f||0:e):e}}),o.each(["height","width"],function(a,b){o.cssHooks[b]={get:function(a,c,d){return c?0===a.offsetWidth&&zb.test(o.css(a,"display"))?o.swap(a,Cb,function(){return Ib(a,b,d)}):Ib(a,b,d):void 0},set:function(a,c,d){var e=d&&wb(a);return Gb(a,c,d?Hb(a,b,d,"border-box"===o.css(a,"boxSizing",!1,e),e):0)}}}),o.cssHooks.marginRight=yb(l.reliableMarginRight,function(a,b){return b?o.swap(a,{display:"inline-block"},xb,[a,"marginRight"]):void 0}),o.each({margin:"",padding:"",border:"Width"},function(a,b){o.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];4>d;d++)e[a+R[d]+b]=f[d]||f[d-2]||f[0];return e}},ub.test(a)||(o.cssHooks[a+b].set=Gb)}),o.fn.extend({css:function(a,b){return J(this,function(a,b,c){var d,e,f={},g=0;if(o.isArray(b)){for(d=wb(a),e=b.length;e>g;g++)f[b[g]]=o.css(a,b[g],!1,d);return f}return void 0!==c?o.style(a,b,c):o.css(a,b)},a,b,arguments.length>1)},show:function(){return Jb(this,!0)},hide:function(){return Jb(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){S(this)?o(this).show():o(this).hide()})}});function Kb(a,b,c,d,e){return new Kb.prototype.init(a,b,c,d,e)}o.Tween=Kb,Kb.prototype={constructor:Kb,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(o.cssNumber[c]?"":"px")},cur:function(){var a=Kb.propHooks[this.prop];return a&&a.get?a.get(this):Kb.propHooks._default.get(this)},run:function(a){var b,c=Kb.propHooks[this.prop];return this.pos=b=this.options.duration?o.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Kb.propHooks._default.set(this),this}},Kb.prototype.init.prototype=Kb.prototype,Kb.propHooks={_default:{get:function(a){var b;return null==a.elem[a.prop]||a.elem.style&&null!=a.elem.style[a.prop]?(b=o.css(a.elem,a.prop,""),b&&"auto"!==b?b:0):a.elem[a.prop]},set:function(a){o.fx.step[a.prop]?o.fx.step[a.prop](a):a.elem.style&&(null!=a.elem.style[o.cssProps[a.prop]]||o.cssHooks[a.prop])?o.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},Kb.propHooks.scrollTop=Kb.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},o.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},o.fx=Kb.prototype.init,o.fx.step={};var Lb,Mb,Nb=/^(?:toggle|show|hide)$/,Ob=new RegExp("^(?:([+-])=|)("+Q+")([a-z%]*)$","i"),Pb=/queueHooks$/,Qb=[Vb],Rb={"*":[function(a,b){var c=this.createTween(a,b),d=c.cur(),e=Ob.exec(b),f=e&&e[3]||(o.cssNumber[a]?"":"px"),g=(o.cssNumber[a]||"px"!==f&&+d)&&Ob.exec(o.css(c.elem,a)),h=1,i=20;if(g&&g[3]!==f){f=f||g[3],e=e||[],g=+d||1;do h=h||".5",g/=h,o.style(c.elem,a,g+f);while(h!==(h=c.cur()/d)&&1!==h&&--i)}return e&&(g=c.start=+g||+d||0,c.unit=f,c.end=e[1]?g+(e[1]+1)*e[2]:+e[2]),c}]};function Sb(){return setTimeout(function(){Lb=void 0}),Lb=o.now()}function Tb(a,b){var c,d=0,e={height:a};for(b=b?1:0;4>d;d+=2-b)c=R[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function Ub(a,b,c){for(var d,e=(Rb[b]||[]).concat(Rb["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function Vb(a,b,c){var d,e,f,g,h,i,j,k=this,l={},m=a.style,n=a.nodeType&&S(a),p=L.get(a,"fxshow");c.queue||(h=o._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,k.always(function(){k.always(function(){h.unqueued--,o.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[m.overflow,m.overflowX,m.overflowY],j=o.css(a,"display"),"none"===j&&(j=tb(a.nodeName)),"inline"===j&&"none"===o.css(a,"float")&&(m.display="inline-block")),c.overflow&&(m.overflow="hidden",k.always(function(){m.overflow=c.overflow[0],m.overflowX=c.overflow[1],m.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],Nb.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(n?"hide":"show")){if("show"!==e||!p||void 0===p[d])continue;n=!0}l[d]=p&&p[d]||o.style(a,d)}if(!o.isEmptyObject(l)){p?"hidden"in p&&(n=p.hidden):p=L.access(a,"fxshow",{}),f&&(p.hidden=!n),n?o(a).show():k.done(function(){o(a).hide()}),k.done(function(){var b;L.remove(a,"fxshow");for(b in l)o.style(a,b,l[b])});for(d in l)g=Ub(n?p[d]:0,d,k),d in p||(p[d]=g.start,n&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function Wb(a,b){var c,d,e,f,g;for(c in a)if(d=o.camelCase(c),e=b[d],f=a[c],o.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=o.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function Xb(a,b,c){var d,e,f=0,g=Qb.length,h=o.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=Lb||Sb(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;i>g;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),1>f&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:o.extend({},b),opts:o.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:Lb||Sb(),duration:c.duration,tweens:[],createTween:function(b,c){var d=o.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;d>c;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;for(Wb(k,j.opts.specialEasing);g>f;f++)if(d=Qb[f].call(j,a,k,j.opts))return d;return o.map(k,Ub,j),o.isFunction(j.opts.start)&&j.opts.start.call(a,j),o.fx.timer(o.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}o.Animation=o.extend(Xb,{tweener:function(a,b){o.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");for(var c,d=0,e=a.length;e>d;d++)c=a[d],Rb[c]=Rb[c]||[],Rb[c].unshift(b)},prefilter:function(a,b){b?Qb.unshift(a):Qb.push(a)}}),o.speed=function(a,b,c){var d=a&&"object"==typeof a?o.extend({},a):{complete:c||!c&&b||o.isFunction(a)&&a,duration:a,easing:c&&b||b&&!o.isFunction(b)&&b};return d.duration=o.fx.off?0:"number"==typeof d.duration?d.duration:d.duration in o.fx.speeds?o.fx.speeds[d.duration]:o.fx.speeds._default,(null==d.queue||d.queue===!0)&&(d.queue="fx"),d.old=d.complete,d.complete=function(){o.isFunction(d.old)&&d.old.call(this),d.queue&&o.dequeue(this,d.queue)},d},o.fn.extend({fadeTo:function(a,b,c,d){return this.filter(S).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=o.isEmptyObject(a),f=o.speed(b,c,d),g=function(){var b=Xb(this,o.extend({},a),f);(e||L.get(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=o.timers,g=L.get(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&Pb.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));(b||!c)&&o.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=L.get(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=o.timers,g=d?d.length:0;for(c.finish=!0,o.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;g>b;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),o.each(["toggle","show","hide"],function(a,b){var c=o.fn[b];o.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(Tb(b,!0),a,d,e)}}),o.each({slideDown:Tb("show"),slideUp:Tb("hide"),slideToggle:Tb("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){o.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),o.timers=[],o.fx.tick=function(){var a,b=0,c=o.timers;for(Lb=o.now();b<c.length;b++)a=c[b],a()||c[b]!==a||c.splice(b--,1);c.length||o.fx.stop(),Lb=void 0},o.fx.timer=function(a){o.timers.push(a),a()?o.fx.start():o.timers.pop()},o.fx.interval=13,o.fx.start=function(){Mb||(Mb=setInterval(o.fx.tick,o.fx.interval))},o.fx.stop=function(){clearInterval(Mb),Mb=null},o.fx.speeds={slow:600,fast:200,_default:400},o.fn.delay=function(a,b){return a=o.fx?o.fx.speeds[a]||a:a,b=b||"fx",this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},function(){var a=m.createElement("input"),b=m.createElement("select"),c=b.appendChild(m.createElement("option"));a.type="checkbox",l.checkOn=""!==a.value,l.optSelected=c.selected,b.disabled=!0,l.optDisabled=!c.disabled,a=m.createElement("input"),a.value="t",a.type="radio",l.radioValue="t"===a.value}();var Yb,Zb,$b=o.expr.attrHandle;o.fn.extend({attr:function(a,b){return J(this,o.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){o.removeAttr(this,a)})}}),o.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(a&&3!==f&&8!==f&&2!==f)return typeof a.getAttribute===U?o.prop(a,b,c):(1===f&&o.isXMLDoc(a)||(b=b.toLowerCase(),d=o.attrHooks[b]||(o.expr.match.bool.test(b)?Zb:Yb)),void 0===c?d&&"get"in d&&null!==(e=d.get(a,b))?e:(e=o.find.attr(a,b),null==e?void 0:e):null!==c?d&&"set"in d&&void 0!==(e=d.set(a,c,b))?e:(a.setAttribute(b,c+""),c):void o.removeAttr(a,b))},removeAttr:function(a,b){var c,d,e=0,f=b&&b.match(E);if(f&&1===a.nodeType)while(c=f[e++])d=o.propFix[c]||c,o.expr.match.bool.test(c)&&(a[d]=!1),a.removeAttribute(c)},attrHooks:{type:{set:function(a,b){if(!l.radioValue&&"radio"===b&&o.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}}}),Zb={set:function(a,b,c){return b===!1?o.removeAttr(a,c):a.setAttribute(c,c),c}},o.each(o.expr.match.bool.source.match(/\w+/g),function(a,b){var c=$b[b]||o.find.attr;$b[b]=function(a,b,d){var e,f; +return d||(f=$b[b],$b[b]=e,e=null!=c(a,b,d)?b.toLowerCase():null,$b[b]=f),e}});var _b=/^(?:input|select|textarea|button)$/i;o.fn.extend({prop:function(a,b){return J(this,o.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[o.propFix[a]||a]})}}),o.extend({propFix:{"for":"htmlFor","class":"className"},prop:function(a,b,c){var d,e,f,g=a.nodeType;if(a&&3!==g&&8!==g&&2!==g)return f=1!==g||!o.isXMLDoc(a),f&&(b=o.propFix[b]||b,e=o.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){return a.hasAttribute("tabindex")||_b.test(a.nodeName)||a.href?a.tabIndex:-1}}}}),l.optSelected||(o.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null}}),o.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){o.propFix[this.toLowerCase()]=this});var ac=/[\t\r\n\f]/g;o.fn.extend({addClass:function(a){var b,c,d,e,f,g,h="string"==typeof a&&a,i=0,j=this.length;if(o.isFunction(a))return this.each(function(b){o(this).addClass(a.call(this,b,this.className))});if(h)for(b=(a||"").match(E)||[];j>i;i++)if(c=this[i],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(ac," "):" ")){f=0;while(e=b[f++])d.indexOf(" "+e+" ")<0&&(d+=e+" ");g=o.trim(d),c.className!==g&&(c.className=g)}return this},removeClass:function(a){var b,c,d,e,f,g,h=0===arguments.length||"string"==typeof a&&a,i=0,j=this.length;if(o.isFunction(a))return this.each(function(b){o(this).removeClass(a.call(this,b,this.className))});if(h)for(b=(a||"").match(E)||[];j>i;i++)if(c=this[i],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(ac," "):"")){f=0;while(e=b[f++])while(d.indexOf(" "+e+" ")>=0)d=d.replace(" "+e+" "," ");g=a?o.trim(d):"",c.className!==g&&(c.className=g)}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):this.each(o.isFunction(a)?function(c){o(this).toggleClass(a.call(this,c,this.className,b),b)}:function(){if("string"===c){var b,d=0,e=o(this),f=a.match(E)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else(c===U||"boolean"===c)&&(this.className&&L.set(this,"__className__",this.className),this.className=this.className||a===!1?"":L.get(this,"__className__")||"")})},hasClass:function(a){for(var b=" "+a+" ",c=0,d=this.length;d>c;c++)if(1===this[c].nodeType&&(" "+this[c].className+" ").replace(ac," ").indexOf(b)>=0)return!0;return!1}});var bc=/\r/g;o.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=o.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,o(this).val()):a,null==e?e="":"number"==typeof e?e+="":o.isArray(e)&&(e=o.map(e,function(a){return null==a?"":a+""})),b=o.valHooks[this.type]||o.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=o.valHooks[e.type]||o.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(bc,""):null==c?"":c)}}}),o.extend({valHooks:{select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type||0>e,g=f?null:[],h=f?e+1:d.length,i=0>e?h:f?e:0;h>i;i++)if(c=d[i],!(!c.selected&&i!==e||(l.optDisabled?c.disabled:null!==c.getAttribute("disabled"))||c.parentNode.disabled&&o.nodeName(c.parentNode,"optgroup"))){if(b=o(c).val(),f)return b;g.push(b)}return g},set:function(a,b){var c,d,e=a.options,f=o.makeArray(b),g=e.length;while(g--)d=e[g],(d.selected=o.inArray(o(d).val(),f)>=0)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),o.each(["radio","checkbox"],function(){o.valHooks[this]={set:function(a,b){return o.isArray(b)?a.checked=o.inArray(o(a).val(),b)>=0:void 0}},l.checkOn||(o.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})}),o.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){o.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),o.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}});var cc=o.now(),dc=/\?/;o.parseJSON=function(a){return JSON.parse(a+"")},o.parseXML=function(a){var b,c;if(!a||"string"!=typeof a)return null;try{c=new DOMParser,b=c.parseFromString(a,"text/xml")}catch(d){b=void 0}return(!b||b.getElementsByTagName("parsererror").length)&&o.error("Invalid XML: "+a),b};var ec,fc,gc=/#.*$/,hc=/([?&])_=[^&]*/,ic=/^(.*?):[ \t]*([^\r\n]*)$/gm,jc=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,kc=/^(?:GET|HEAD)$/,lc=/^\/\//,mc=/^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,nc={},oc={},pc="*/".concat("*");try{fc=location.href}catch(qc){fc=m.createElement("a"),fc.href="",fc=fc.href}ec=mc.exec(fc.toLowerCase())||[];function rc(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(E)||[];if(o.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function sc(a,b,c,d){var e={},f=a===oc;function g(h){var i;return e[h]=!0,o.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function tc(a,b){var c,d,e=o.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&o.extend(!0,a,d),a}function uc(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}return f?(f!==i[0]&&i.unshift(f),c[f]):void 0}function vc(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}o.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:fc,type:"GET",isLocal:jc.test(ec[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":pc,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":o.parseJSON,"text xml":o.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?tc(tc(a,o.ajaxSettings),b):tc(o.ajaxSettings,a)},ajaxPrefilter:rc(nc),ajaxTransport:rc(oc),ajax:function(a,b){"object"==typeof a&&(b=a,a=void 0),b=b||{};var c,d,e,f,g,h,i,j,k=o.ajaxSetup({},b),l=k.context||k,m=k.context&&(l.nodeType||l.jquery)?o(l):o.event,n=o.Deferred(),p=o.Callbacks("once memory"),q=k.statusCode||{},r={},s={},t=0,u="canceled",v={readyState:0,getResponseHeader:function(a){var b;if(2===t){if(!f){f={};while(b=ic.exec(e))f[b[1].toLowerCase()]=b[2]}b=f[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return 2===t?e:null},setRequestHeader:function(a,b){var c=a.toLowerCase();return t||(a=s[c]=s[c]||a,r[a]=b),this},overrideMimeType:function(a){return t||(k.mimeType=a),this},statusCode:function(a){var b;if(a)if(2>t)for(b in a)q[b]=[q[b],a[b]];else v.always(a[v.status]);return this},abort:function(a){var b=a||u;return c&&c.abort(b),x(0,b),this}};if(n.promise(v).complete=p.add,v.success=v.done,v.error=v.fail,k.url=((a||k.url||fc)+"").replace(gc,"").replace(lc,ec[1]+"//"),k.type=b.method||b.type||k.method||k.type,k.dataTypes=o.trim(k.dataType||"*").toLowerCase().match(E)||[""],null==k.crossDomain&&(h=mc.exec(k.url.toLowerCase()),k.crossDomain=!(!h||h[1]===ec[1]&&h[2]===ec[2]&&(h[3]||("http:"===h[1]?"80":"443"))===(ec[3]||("http:"===ec[1]?"80":"443")))),k.data&&k.processData&&"string"!=typeof k.data&&(k.data=o.param(k.data,k.traditional)),sc(nc,k,b,v),2===t)return v;i=k.global,i&&0===o.active++&&o.event.trigger("ajaxStart"),k.type=k.type.toUpperCase(),k.hasContent=!kc.test(k.type),d=k.url,k.hasContent||(k.data&&(d=k.url+=(dc.test(d)?"&":"?")+k.data,delete k.data),k.cache===!1&&(k.url=hc.test(d)?d.replace(hc,"$1_="+cc++):d+(dc.test(d)?"&":"?")+"_="+cc++)),k.ifModified&&(o.lastModified[d]&&v.setRequestHeader("If-Modified-Since",o.lastModified[d]),o.etag[d]&&v.setRequestHeader("If-None-Match",o.etag[d])),(k.data&&k.hasContent&&k.contentType!==!1||b.contentType)&&v.setRequestHeader("Content-Type",k.contentType),v.setRequestHeader("Accept",k.dataTypes[0]&&k.accepts[k.dataTypes[0]]?k.accepts[k.dataTypes[0]]+("*"!==k.dataTypes[0]?", "+pc+"; q=0.01":""):k.accepts["*"]);for(j in k.headers)v.setRequestHeader(j,k.headers[j]);if(k.beforeSend&&(k.beforeSend.call(l,v,k)===!1||2===t))return v.abort();u="abort";for(j in{success:1,error:1,complete:1})v[j](k[j]);if(c=sc(oc,k,b,v)){v.readyState=1,i&&m.trigger("ajaxSend",[v,k]),k.async&&k.timeout>0&&(g=setTimeout(function(){v.abort("timeout")},k.timeout));try{t=1,c.send(r,x)}catch(w){if(!(2>t))throw w;x(-1,w)}}else x(-1,"No Transport");function x(a,b,f,h){var j,r,s,u,w,x=b;2!==t&&(t=2,g&&clearTimeout(g),c=void 0,e=h||"",v.readyState=a>0?4:0,j=a>=200&&300>a||304===a,f&&(u=uc(k,v,f)),u=vc(k,u,v,j),j?(k.ifModified&&(w=v.getResponseHeader("Last-Modified"),w&&(o.lastModified[d]=w),w=v.getResponseHeader("etag"),w&&(o.etag[d]=w)),204===a||"HEAD"===k.type?x="nocontent":304===a?x="notmodified":(x=u.state,r=u.data,s=u.error,j=!s)):(s=x,(a||!x)&&(x="error",0>a&&(a=0))),v.status=a,v.statusText=(b||x)+"",j?n.resolveWith(l,[r,x,v]):n.rejectWith(l,[v,x,s]),v.statusCode(q),q=void 0,i&&m.trigger(j?"ajaxSuccess":"ajaxError",[v,k,j?r:s]),p.fireWith(l,[v,x]),i&&(m.trigger("ajaxComplete",[v,k]),--o.active||o.event.trigger("ajaxStop")))}return v},getJSON:function(a,b,c){return o.get(a,b,c,"json")},getScript:function(a,b){return o.get(a,void 0,b,"script")}}),o.each(["get","post"],function(a,b){o[b]=function(a,c,d,e){return o.isFunction(c)&&(e=e||d,d=c,c=void 0),o.ajax({url:a,type:b,dataType:e,data:c,success:d})}}),o.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){o.fn[b]=function(a){return this.on(b,a)}}),o._evalUrl=function(a){return o.ajax({url:a,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})},o.fn.extend({wrapAll:function(a){var b;return o.isFunction(a)?this.each(function(b){o(this).wrapAll(a.call(this,b))}):(this[0]&&(b=o(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this)},wrapInner:function(a){return this.each(o.isFunction(a)?function(b){o(this).wrapInner(a.call(this,b))}:function(){var b=o(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=o.isFunction(a);return this.each(function(c){o(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){o.nodeName(this,"body")||o(this).replaceWith(this.childNodes)}).end()}}),o.expr.filters.hidden=function(a){return a.offsetWidth<=0&&a.offsetHeight<=0},o.expr.filters.visible=function(a){return!o.expr.filters.hidden(a)};var wc=/%20/g,xc=/\[\]$/,yc=/\r?\n/g,zc=/^(?:submit|button|image|reset|file)$/i,Ac=/^(?:input|select|textarea|keygen)/i;function Bc(a,b,c,d){var e;if(o.isArray(b))o.each(b,function(b,e){c||xc.test(a)?d(a,e):Bc(a+"["+("object"==typeof e?b:"")+"]",e,c,d)});else if(c||"object"!==o.type(b))d(a,b);else for(e in b)Bc(a+"["+e+"]",b[e],c,d)}o.param=function(a,b){var c,d=[],e=function(a,b){b=o.isFunction(b)?b():null==b?"":b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};if(void 0===b&&(b=o.ajaxSettings&&o.ajaxSettings.traditional),o.isArray(a)||a.jquery&&!o.isPlainObject(a))o.each(a,function(){e(this.name,this.value)});else for(c in a)Bc(c,a[c],b,e);return d.join("&").replace(wc,"+")},o.fn.extend({serialize:function(){return o.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=o.prop(this,"elements");return a?o.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!o(this).is(":disabled")&&Ac.test(this.nodeName)&&!zc.test(a)&&(this.checked||!T.test(a))}).map(function(a,b){var c=o(this).val();return null==c?null:o.isArray(c)?o.map(c,function(a){return{name:b.name,value:a.replace(yc,"\r\n")}}):{name:b.name,value:c.replace(yc,"\r\n")}}).get()}}),o.ajaxSettings.xhr=function(){try{return new XMLHttpRequest}catch(a){}};var Cc=0,Dc={},Ec={0:200,1223:204},Fc=o.ajaxSettings.xhr();a.ActiveXObject&&o(a).on("unload",function(){for(var a in Dc)Dc[a]()}),l.cors=!!Fc&&"withCredentials"in Fc,l.ajax=Fc=!!Fc,o.ajaxTransport(function(a){var b;return l.cors||Fc&&!a.crossDomain?{send:function(c,d){var e,f=a.xhr(),g=++Cc;if(f.open(a.type,a.url,a.async,a.username,a.password),a.xhrFields)for(e in a.xhrFields)f[e]=a.xhrFields[e];a.mimeType&&f.overrideMimeType&&f.overrideMimeType(a.mimeType),a.crossDomain||c["X-Requested-With"]||(c["X-Requested-With"]="XMLHttpRequest");for(e in c)f.setRequestHeader(e,c[e]);b=function(a){return function(){b&&(delete Dc[g],b=f.onload=f.onerror=null,"abort"===a?f.abort():"error"===a?d(f.status,f.statusText):d(Ec[f.status]||f.status,f.statusText,"string"==typeof f.responseText?{text:f.responseText}:void 0,f.getAllResponseHeaders()))}},f.onload=b(),f.onerror=b("error"),b=Dc[g]=b("abort"),f.send(a.hasContent&&a.data||null)},abort:function(){b&&b()}}:void 0}),o.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(a){return o.globalEval(a),a}}}),o.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),o.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(d,e){b=o("<script>").prop({async:!0,charset:a.scriptCharset,src:a.url}).on("load error",c=function(a){b.remove(),c=null,a&&e("error"===a.type?404:200,a.type)}),m.head.appendChild(b[0])},abort:function(){c&&c()}}}});var Gc=[],Hc=/(=)\?(?=&|$)|\?\?/;o.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=Gc.pop()||o.expando+"_"+cc++;return this[a]=!0,a}}),o.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(Hc.test(b.url)?"url":"string"==typeof b.data&&!(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&Hc.test(b.data)&&"data");return h||"jsonp"===b.dataTypes[0]?(e=b.jsonpCallback=o.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(Hc,"$1"+e):b.jsonp!==!1&&(b.url+=(dc.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||o.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,Gc.push(e)),g&&o.isFunction(f)&&f(g[0]),g=f=void 0}),"script"):void 0}),o.parseHTML=function(a,b,c){if(!a||"string"!=typeof a)return null;"boolean"==typeof b&&(c=b,b=!1),b=b||m;var d=v.exec(a),e=!c&&[];return d?[b.createElement(d[1])]:(d=o.buildFragment([a],b,e),e&&e.length&&o(e).remove(),o.merge([],d.childNodes))};var Ic=o.fn.load;o.fn.load=function(a,b,c){if("string"!=typeof a&&Ic)return Ic.apply(this,arguments);var d,e,f,g=this,h=a.indexOf(" ");return h>=0&&(d=a.slice(h),a=a.slice(0,h)),o.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(e="POST"),g.length>0&&o.ajax({url:a,type:e,dataType:"html",data:b}).done(function(a){f=arguments,g.html(d?o("<div>").append(o.parseHTML(a)).find(d):a)}).complete(c&&function(a,b){g.each(c,f||[a.responseText,b,a])}),this},o.expr.filters.animated=function(a){return o.grep(o.timers,function(b){return a===b.elem}).length};var Jc=a.document.documentElement;function Kc(a){return o.isWindow(a)?a:9===a.nodeType&&a.defaultView}o.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=o.css(a,"position"),l=o(a),m={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=o.css(a,"top"),i=o.css(a,"left"),j=("absolute"===k||"fixed"===k)&&(f+i).indexOf("auto")>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),o.isFunction(b)&&(b=b.call(a,c,h)),null!=b.top&&(m.top=b.top-h.top+g),null!=b.left&&(m.left=b.left-h.left+e),"using"in b?b.using.call(a,m):l.css(m)}},o.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){o.offset.setOffset(this,a,b)});var b,c,d=this[0],e={top:0,left:0},f=d&&d.ownerDocument;if(f)return b=f.documentElement,o.contains(b,d)?(typeof d.getBoundingClientRect!==U&&(e=d.getBoundingClientRect()),c=Kc(f),{top:e.top+c.pageYOffset-b.clientTop,left:e.left+c.pageXOffset-b.clientLeft}):e},position:function(){if(this[0]){var a,b,c=this[0],d={top:0,left:0};return"fixed"===o.css(c,"position")?b=c.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),o.nodeName(a[0],"html")||(d=a.offset()),d.top+=o.css(a[0],"borderTopWidth",!0),d.left+=o.css(a[0],"borderLeftWidth",!0)),{top:b.top-d.top-o.css(c,"marginTop",!0),left:b.left-d.left-o.css(c,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||Jc;while(a&&!o.nodeName(a,"html")&&"static"===o.css(a,"position"))a=a.offsetParent;return a||Jc})}}),o.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(b,c){var d="pageYOffset"===c;o.fn[b]=function(e){return J(this,function(b,e,f){var g=Kc(b);return void 0===f?g?g[c]:b[e]:void(g?g.scrollTo(d?a.pageXOffset:f,d?f:a.pageYOffset):b[e]=f)},b,e,arguments.length,null)}}),o.each(["top","left"],function(a,b){o.cssHooks[b]=yb(l.pixelPosition,function(a,c){return c?(c=xb(a,b),vb.test(c)?o(a).position()[b]+"px":c):void 0})}),o.each({Height:"height",Width:"width"},function(a,b){o.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){o.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return J(this,function(b,c,d){var e;return o.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?o.css(b,c,g):o.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),o.fn.size=function(){return this.length},o.fn.andSelf=o.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return o});var Lc=a.jQuery,Mc=a.$;return o.noConflict=function(b){return a.$===o&&(a.$=Mc),b&&a.jQuery===o&&(a.jQuery=Lc),o},typeof b===U&&(a.jQuery=a.$=o),o}); diff --git a/www/vincent.demeester.fr/assets/.fancyindex/new.css b/www/vincent.demeester.fr/assets/.fancyindex/new.css @@ -0,0 +1,605 @@ +:root { + ---font-sans: 'Ubuntu Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + ---font-mono: 'Ubuntu Mono', Consolas, monaco, 'Liberation Mono', 'Courier New', Courier, monospace; + ---tx-1: #000000; + ---tx-2: #1A1A1A; + ---tx-3: #666666; + ---tx-4: #999999; + ---bg-1: #FFFFFF; + ---bg-2: #F6F8FA; + ---bg-3: #E5E7EB; + ---lk-1: #0070F3; + ---lk-2: #0366D6; + ---lk-tx: #FFFFFF; + ---ac-1: #79FFE1; + ---ac-tx: #0C4047; + + --content-box-padding: 0.5rem; + --aside-width: 12.5rem; +} + +@media (prefers-color-scheme: dark) { + :root { + ---tx-1: #ffffff; + ---tx-2: #eeeeee; + ---tx-3: #cccccc; + ---tx-4: #aaaaaa; + ---bg-1: #000000; + ---bg-2: #111111; + ---bg-3: #222222; + ---lk-1: #3291FF; + ---lk-2: #0070F3; + ---lk-tx: #FFFFFF; + ---ac-1: #7928CA; + ---ac-tx: #FFFFFF; + } +} + +* { + /* Reset margins and padding */ + margin: 0; + padding: 0; +} + +address, +area, +article, +aside, +audio, +blockquote, +datalist, +details, +dl, +fieldset, +figure, +form, +input, +iframe, +img, +meter, +nav, +ol, +optgroup, +option, +output, +p, +pre, +progress, +ruby, +section, +table, +textarea, +ul, +video { + /* Margins for most elements */ + margin-bottom: 1rem; +} + +html,input,select,button { + /* Set body font family and some finicky elements */ + font-family: var(---font-sans); +} + +body { + /* Center body in page */ + margin: 0 auto; + max-width: 70rem; + padding: 2rem; + border-radius: 6px; + overflow-x: hidden; + word-break: break-word; + overflow-wrap: break-word; + background: var(---bg-1); + + /* Main body text */ + color: var(---tx-2); + font-size: 1.03rem; + line-height: 1.5; +} + +::selection { + /* Set background color for selected text */ + background: var(---ac-1); + color: var(---ac-tx); +} + +h1,h2,h3,h4,h5,h6 { + line-height: 1; + color: var(---tx-1); + padding-top: .875rem; +} + +h1, +h2, +h3 { + color: var(---tx-1); + padding-bottom: 2px; + margin-bottom: 8px; + border-bottom: 1px solid var(---bg-2); +} + +h4, +h5, +h6 { + margin-bottom: .3rem; +} + +h1 { + font-size: 2.25rem; +} + +h2 { + font-size: 1.85rem; +} + +h3 { + font-size: 1.55rem; +} + +h4 { + font-size: 1.25rem; +} + +h5 { + font-size: 1rem; +} + +h6 { + font-size: .875rem; +} + +a { + color: var(---lk-1); +} + +a:hover { + color: var(---lk-2); +} + +abbr:hover { + /* Set the '?' cursor while hovering an abbreviation */ + cursor: help; +} + +blockquote { + padding: 1.5rem; + background: var(---bg-2); + border-left: 5px solid var(---bg-3); +} + +abbr { + cursor: help; +} + +blockquote *:last-child { + padding-bottom: 0; + margin-bottom: 0; +} + +header { + background: var(---bg-2); + border-bottom: 1px solid var(---bg-3); + padding: 1rem 1.5rem; + + /* This sets the right and left margins to cancel out the body's margins. It's width is still the same, but the background stretches across the page's width. */ + + margin: -2rem calc(0px - (50vw - 50%)) 2rem; + + /* Shorthand for: + + margin-top: -2rem; + margin-bottom: 2rem; + + margin-left: calc(0px - (50vw - 50%)); + margin-right: calc(0px - (50vw - 50%)); */ + + padding-left: calc(50vw - 50%); + padding-right: calc(50vw - 50%); +} + +header img#sitelogo { + margin-bottom: 0; + margin-right: 1em; +} + +header h1, +header h2, +header h3 { + padding-bottom: 0; + border-bottom: 0; +} + +header > *:first-child { + margin-top: 0; + padding-top: 0; +} + +header > *:last-child { + margin-bottom: 0; +} + +main header { + background: var(---bg-3); +} + +a button, +button, +input[type="submit"], +input[type="reset"], +input[type="button"] { + font-size: 1rem; + display: inline-block; + padding: 6px 12px; + text-align: center; + text-decoration: none; + white-space: nowrap; + background: var(---lk-1); + color: var(---lk-tx); + border: 0; + border-radius: 4px; + box-sizing: border-box; + cursor: pointer; + color: var(---lk-tx); +} + +a button[disabled], +button[disabled], +input[type="submit"][disabled], +input[type="reset"][disabled], +input[type="button"][disabled] { + cursor: default; + opacity: .5; + + /* Set the [X] cursor while hovering a disabled link */ + cursor: not-allowed; +} + +.button:focus, +.button:hover, +button:focus, +button:hover, +input[type="submit"]:focus, +input[type="submit"]:hover, +input[type="reset"]:focus, +input[type="reset"]:hover, +input[type="button"]:focus, +input[type="button"]:hover { + background: var(---lk-2); +} + +code, +pre, +kbd, +samp { + /* Set the font family for monospaced elements */ + font-family: var(---font-mono); +} + +code, +samp, +kbd, +pre { + /* The main preformatted style. This is changed slightly across different cases. */ + background: var(---bg-2); + border: 1px solid var(---bg-3); + border-radius: 4px; + padding: 3px 6px; +} + +kbd { + /* Makes the kbd element look like a keyboard key */ + border-bottom: 3px solid var(---bg-3); +} + +pre { + padding: 1rem 1.4rem; + max-width: 100%; + overflow: auto; +} + +pre.src { + position: relative; +} +pre.src:before { + /*display: none;*/ + position: absolute; + background-color: var(---bg-3); + /*color: #f3f4f4;*/ + top: 0; + right: 0; + padding: 0.2rem 0.5rem; +} + +pre code { + /* When <code> is in a <pre>, reset it's formatting to blend in */ + background: inherit; + font-size: inherit; + color: inherit; + border: 0; + padding: 0; + margin: 0; +} + +code pre { + /* When <pre> is in a <code>, reset it's formatting to blend in */ + display: inline; + background: inherit; + font-size: inherit; + color: inherit; + border: 0; + padding: 0; + margin: 0; +} + +details { + /* Make the <details> look more "clickable" */ + padding: .6rem 1rem; + background: var(---bg-2); + border: 1px solid var(---bg-3); + border-radius: 4px; +} + +summary { + /* Makes the <summary> look more like a "clickable" link with the pointer cursor */ + cursor: pointer; + font-weight: bold; +} + +details[open] { + /* Adjust the <details> padding while open */ + padding-bottom: .75rem; +} + +details[open] summary { + /* Adjust the <details> padding while open */ + margin-bottom: 6px; +} + +details[open]>*:last-child { + /* Resets the bottom margin of the last element in the <details> while <details> is opened. This prevents double margins/paddings. */ + margin-bottom: 0; +} + +dt { + font-weight: bold; +} + +dd::before { + /* Add an arrow to data table definitions */ + content: '→ '; +} + +hr { + /* Reset the border of the <hr> separator, then set a better line */ + border: 0; + border-bottom: 1px solid var(---bg-3); + margin: 1rem auto; +} + +fieldset { + margin-top: 1rem; + padding: 2rem; + border: 1px solid var(---bg-3); + border-radius: 4px; +} + +legend { + padding: auto .5rem; +} + +table { + /* border-collapse sets the table's elements to share borders, rather than floating as separate "boxes". */ + border-collapse: collapse; + width: 100% +} + +td, +th { + border: 1px solid var(---bg-3); + text-align: left; + padding: .5rem; +} + +th { + background: var(---bg-2); +} + +tr:nth-child(even) { + /* Set every other cell slightly darker. Improves readability. */ + background: var(---bg-2); +} + +table caption { + font-weight: bold; + margin-bottom: .5rem; +} + +textarea { + /* Don't let the <textarea> extend off the screen naturally or when dragged by the user */ + max-width: 100%; +} + +ol, +ul { + /* Replace the browser default padding */ + padding-left: 2rem; +} + +li { + margin-top: .4rem; +} + +ul ul, +ol ul, +ul ol, +ol ol { + margin-bottom: 0; +} + +mark { + padding: 3px 6px; + background: var(---ac-1); + color: var(---ac-tx); +} + +textarea, +select, +input { + padding: 6px 12px; + margin-bottom: .5rem; + background: var(---bg-2); + color: var(---tx-2); + border: 1px solid var(---bg-3); + border-radius: 4px; + box-shadow: none; + box-sizing: border-box; +} + +img { + max-width: 100%; +} + +aside { + --aside-offset: 1rem; + --aside-offset-lineheight: 1.5rem; + background-color: var(---bg-2); + padding: calc(var(--content-box-padding) * 0.5) calc(var(--content-box-padding) * 2); + padding-top: 1em; + margin-bottom: 0em; + float: right; + width: var(--aside-width); + font-size: 0.9rem; + line-height: 1.25rem; + border: 1px solid var(--bg-3); +} + +figure figcaption { + text-align: center; + color: var(---tx-2); + font-style: italic; + margin: 0 2rem; +} + +footer { + text-align: center; + color: var(---tx-4); +} + +footer a { + color: var(---tx-3); +} + +section img { + display: block; + margin-left: auto; + margin-right: auto; +} + +section img.shadow { + box-shadow: 0 28px 50px rgba(0,0,0,0.16); +} + +div.drawer:before { + content: ""; + position: absolute; + top: 0; + bottom: 0; + left: 4.4rem; + width: 1px; + background-color: #555; +} + +div.drawer { + display: flex; + padding: 0; + margin: 1rem; + padding-top: 1rem; + position: relative; + color: #555; +} + +div.drawer h6 { + box-sizing: border-box; + font-style: italic; + flex: 0 0 5rem; + margin:0; + padding:0; + align-self: center; + text-align: right; + padding-right: 1.2rem; +} + +div.drawer.info:before { + background-color: #00529B; +} +div.drawer.info { + color: #00529B; +} + +div.drawer.note:before { + background-color: #9F6000; +} +div.drawer.note { + color: #9F6000; +} +div.drawer.tip:before { + background-color: #4F8A10; +} +div.drawer.tip { + color: #4F8A10; +} +div.drawer.warning:before { + background-color: #D8000C; +} +div.drawer.warning { + color: #D8000C; +} +div.drawer.results:before { + background-color: black; +} +div.drawer.results { + font-family:monospace,monospace; + font-family: var(---font-mono); + color: black; + width: 100% !important; +} +div.drawer.results p { + display: block; + unicode-bidi: embed; + white-space: pre; + margin-top: 0; +} +div.drawer.results pre { + background-color: inherit; + border: 0; +} +div.drawer.results pre.src:before { + background-color: #eee; + color: #555; +} + +.tag { + font-family:var(---font-mono); + font-size: 1rem; +} +.tag span { + padding:.3em; + float:right; + margin-right:.5em; + border:1px solid #bbb; + border-radius:3px; + background-clip:padding-box; + color:#333; + background-color:#eee; + line-height:1rem; +} + +.todo { + font-family: monospace; color: red; +} +.done { + font-family: monospace; color: green; +} +.priority { font-family: monospace; color: orange; } +.timestamp { color: #bebebe; } +.timestamp-kwd { color: #5f9ea0; } diff --git a/www/vincent.demeester.fr/assets/.fancyindex/showdown.min.js b/www/vincent.demeester.fr/assets/.fancyindex/showdown.min.js @@ -0,0 +1,3 @@ +/*! showdown v 1.8.6 - 22-12-2017 */ +(function(){function g(g){"use strict";var A={omitExtraWLInCodeBlocks:{defaultValue:!1,describe:"Omit the default extra whiteline added to code blocks",type:"boolean"},noHeaderId:{defaultValue:!1,describe:"Turn on/off generated header id",type:"boolean"},prefixHeaderId:{defaultValue:!1,describe:"Add a prefix to the generated header ids. Passing a string will prefix that string to the header id. Setting to true will add a generic 'section-' prefix",type:"string"},rawPrefixHeaderId:{defaultValue:!1,describe:'Setting this option to true will prevent showdown from modifying the prefix. This might result in malformed IDs (if, for instance, the " char is used in the prefix)',type:"boolean"},ghCompatibleHeaderId:{defaultValue:!1,describe:"Generate header ids compatible with github style (spaces are replaced with dashes, a bunch of non alphanumeric chars are removed)",type:"boolean"},rawHeaderId:{defaultValue:!1,describe:"Remove only spaces, ' and \" from generated header ids (including prefixes), replacing them with dashes (-). WARNING: This might result in malformed ids",type:"boolean"},headerLevelStart:{defaultValue:!1,describe:"The header blocks level start",type:"integer"},parseImgDimensions:{defaultValue:!1,describe:"Turn on/off image dimension parsing",type:"boolean"},simplifiedAutoLink:{defaultValue:!1,describe:"Turn on/off GFM autolink style",type:"boolean"},excludeTrailingPunctuationFromURLs:{defaultValue:!1,describe:"Excludes trailing punctuation from links generated with autoLinking",type:"boolean"},literalMidWordUnderscores:{defaultValue:!1,describe:"Parse midword underscores as literal underscores",type:"boolean"},literalMidWordAsterisks:{defaultValue:!1,describe:"Parse midword asterisks as literal asterisks",type:"boolean"},strikethrough:{defaultValue:!1,describe:"Turn on/off strikethrough support",type:"boolean"},tables:{defaultValue:!1,describe:"Turn on/off tables support",type:"boolean"},tablesHeaderId:{defaultValue:!1,describe:"Add an id to table headers",type:"boolean"},ghCodeBlocks:{defaultValue:!0,describe:"Turn on/off GFM fenced code blocks support",type:"boolean"},tasklists:{defaultValue:!1,describe:"Turn on/off GFM tasklist support",type:"boolean"},smoothLivePreview:{defaultValue:!1,describe:"Prevents weird effects in live previews due to incomplete input",type:"boolean"},smartIndentationFix:{defaultValue:!1,description:"Tries to smartly fix indentation in es6 strings",type:"boolean"},disableForced4SpacesIndentedSublists:{defaultValue:!1,description:"Disables the requirement of indenting nested sublists by 4 spaces",type:"boolean"},simpleLineBreaks:{defaultValue:!1,description:"Parses simple line breaks as <br> (GFM Style)",type:"boolean"},requireSpaceBeforeHeadingText:{defaultValue:!1,description:"Makes adding a space between `#` and the header text mandatory (GFM Style)",type:"boolean"},ghMentions:{defaultValue:!1,description:"Enables github @mentions",type:"boolean"},ghMentionsLink:{defaultValue:"https://github.com/{u}",description:"Changes the link generated by @mentions. Only applies if ghMentions option is enabled.",type:"string"},encodeEmails:{defaultValue:!0,description:"Encode e-mail addresses through the use of Character Entities, transforming ASCII e-mail addresses into its equivalent decimal entities",type:"boolean"},openLinksInNewWindow:{defaultValue:!1,description:"Open all links in new windows",type:"boolean"},backslashEscapesHTMLTags:{defaultValue:!1,description:"Support for HTML Tag escaping. ex: <div>foo</div>",type:"boolean"},emoji:{defaultValue:!1,description:"Enable emoji support. Ex: `this is a :smile: emoji`",type:"boolean"},underline:{defaultValue:!1,description:"Enable support for underline. Syntax is double or triple underscores: `__underline word__`. With this option enabled, underscores no longer parses into `<em>` and `<strong>`",type:"boolean"},completeHTMLDocument:{defaultValue:!1,description:"Outputs a complete html document, including `<html>`, `<head>` and `<body>` tags",type:"boolean"},metadata:{defaultValue:!1,description:"Enable support for document metadata (defined at the top of the document between `«««` and `»»»` or between `---` and `---`).",type:"boolean"},splitAdjacentBlockquotes:{defaultValue:!1,description:"Split adjacent blockquote blocks",type:"boolean"}};if(!1===g)return JSON.parse(JSON.stringify(A));var C={};for(var I in A)A.hasOwnProperty(I)&&(C[I]=A[I].defaultValue);return C}function A(g,A){"use strict";var C=A?"Error in "+A+" extension->":"Error in unnamed extension",e={valid:!0,error:""};I.helper.isArray(g)||(g=[g]);for(var r=0;r<g.length;++r){var t=C+" sub-extension "+r+": ",a=g[r];if("object"!=typeof a)return e.valid=!1,e.error=t+"must be an object, but "+typeof a+" given",e;if(!I.helper.isString(a.type))return e.valid=!1,e.error=t+'property "type" must be a string, but '+typeof a.type+" given",e;var n=a.type=a.type.toLowerCase();if("language"===n&&(n=a.type="lang"),"html"===n&&(n=a.type="output"),"lang"!==n&&"output"!==n&&"listener"!==n)return e.valid=!1,e.error=t+"type "+n+' is not recognized. Valid values: "lang/language", "output/html" or "listener"',e;if("listener"===n){if(I.helper.isUndefined(a.listeners))return e.valid=!1,e.error=t+'. Extensions of type "listener" must have a property called "listeners"',e}else if(I.helper.isUndefined(a.filter)&&I.helper.isUndefined(a.regex))return e.valid=!1,e.error=t+n+' extensions must define either a "regex" property or a "filter" method',e;if(a.listeners){if("object"!=typeof a.listeners)return e.valid=!1,e.error=t+'"listeners" property must be an object but '+typeof a.listeners+" given",e;for(var o in a.listeners)if(a.listeners.hasOwnProperty(o)&&"function"!=typeof a.listeners[o])return e.valid=!1,e.error=t+'"listeners" property must be an hash of [event name]: [callback]. listeners.'+o+" must be a function but "+typeof a.listeners[o]+" given",e}if(a.filter){if("function"!=typeof a.filter)return e.valid=!1,e.error=t+'"filter" must be a function, but '+typeof a.filter+" given",e}else if(a.regex){if(I.helper.isString(a.regex)&&(a.regex=new RegExp(a.regex,"g")),!(a.regex instanceof RegExp))return e.valid=!1,e.error=t+'"regex" property must either be a string or a RegExp object, but '+typeof a.regex+" given",e;if(I.helper.isUndefined(a.replace))return e.valid=!1,e.error=t+'"regex" extensions must implement a replace string or function',e}}return e}function C(g,A){"use strict";return"¨E"+A.charCodeAt(0)+"E"}var I={},e={},r={},t=g(!0),a="vanilla",n={github:{omitExtraWLInCodeBlocks:!0,simplifiedAutoLink:!0,excludeTrailingPunctuationFromURLs:!0,literalMidWordUnderscores:!0,strikethrough:!0,tables:!0,tablesHeaderId:!0,ghCodeBlocks:!0,tasklists:!0,disableForced4SpacesIndentedSublists:!0,simpleLineBreaks:!0,requireSpaceBeforeHeadingText:!0,ghCompatibleHeaderId:!0,ghMentions:!0,backslashEscapesHTMLTags:!0,emoji:!0,splitAdjacentBlockquotes:!0},original:{noHeaderId:!0,ghCodeBlocks:!1},ghost:{omitExtraWLInCodeBlocks:!0,parseImgDimensions:!0,simplifiedAutoLink:!0,excludeTrailingPunctuationFromURLs:!0,literalMidWordUnderscores:!0,strikethrough:!0,tables:!0,tablesHeaderId:!0,ghCodeBlocks:!0,tasklists:!0,smoothLivePreview:!0,simpleLineBreaks:!0,requireSpaceBeforeHeadingText:!0,ghMentions:!1,encodeEmails:!0},vanilla:g(!0),allOn:function(){"use strict";var A=g(!0),C={};for(var I in A)A.hasOwnProperty(I)&&(C[I]=!0);return C}()};I.helper={},I.extensions={},I.setOption=function(g,A){"use strict";return t[g]=A,this},I.getOption=function(g){"use strict";return t[g]},I.getOptions=function(){"use strict";return t},I.resetOptions=function(){"use strict";t=g(!0)},I.setFlavor=function(g){"use strict";if(!n.hasOwnProperty(g))throw Error(g+" flavor was not found");I.resetOptions();var A=n[g];a=g;for(var C in A)A.hasOwnProperty(C)&&(t[C]=A[C])},I.getFlavor=function(){"use strict";return a},I.getFlavorOptions=function(g){"use strict";if(n.hasOwnProperty(g))return n[g]},I.getDefaultOptions=function(A){"use strict";return g(A)},I.subParser=function(g,A){"use strict";if(I.helper.isString(g)){if(void 0===A){if(e.hasOwnProperty(g))return e[g];throw Error("SubParser named "+g+" not registered!")}e[g]=A}},I.extension=function(g,C){"use strict";if(!I.helper.isString(g))throw Error("Extension 'name' must be a string");if(g=I.helper.stdExtName(g),I.helper.isUndefined(C)){if(!r.hasOwnProperty(g))throw Error("Extension named "+g+" is not registered!");return r[g]}"function"==typeof C&&(C=C()),I.helper.isArray(C)||(C=[C]);var e=A(C,g);if(!e.valid)throw Error(e.error);r[g]=C},I.getAllExtensions=function(){"use strict";return r},I.removeExtension=function(g){"use strict";delete r[g]},I.resetExtensions=function(){"use strict";r={}},I.validateExtension=function(g){"use strict";var C=A(g,null);return!!C.valid||(console.warn(C.error),!1)},I.hasOwnProperty("helper")||(I.helper={}),I.helper.isString=function(g){"use strict";return"string"==typeof g||g instanceof String},I.helper.isFunction=function(g){"use strict";return g&&"[object Function]"==={}.toString.call(g)},I.helper.isArray=function(g){"use strict";return Array.isArray(g)},I.helper.isUndefined=function(g){"use strict";return void 0===g},I.helper.forEach=function(g,A){"use strict";if(I.helper.isUndefined(g))throw new Error("obj param is required");if(I.helper.isUndefined(A))throw new Error("callback param is required");if(!I.helper.isFunction(A))throw new Error("callback param must be a function/closure");if("function"==typeof g.forEach)g.forEach(A);else if(I.helper.isArray(g))for(var C=0;C<g.length;C++)A(g[C],C,g);else{if("object"!=typeof g)throw new Error("obj does not seem to be an array or an iterable object");for(var e in g)g.hasOwnProperty(e)&&A(g[e],e,g)}},I.helper.stdExtName=function(g){"use strict";return g.replace(/[_?*+\/\\.^-]/g,"").replace(/\s/g,"").toLowerCase()},I.helper.escapeCharactersCallback=C,I.helper.escapeCharacters=function(g,A,I){"use strict";var e="(["+A.replace(/([\[\]\\])/g,"\\$1")+"])";I&&(e="\\\\"+e);var r=new RegExp(e,"g");return g=g.replace(r,C)};var o=function(g,A,C,I){"use strict";var e,r,t,a,n,o=I||"",s=o.indexOf("g")>-1,i=new RegExp(A+"|"+C,"g"+o.replace(/g/g,"")),l=new RegExp(A,o.replace(/g/g,"")),c=[];do{for(e=0;t=i.exec(g);)if(l.test(t[0]))e++||(a=(r=i.lastIndex)-t[0].length);else if(e&&!--e){n=t.index+t[0].length;var u={left:{start:a,end:r},match:{start:r,end:t.index},right:{start:t.index,end:n},wholeMatch:{start:a,end:n}};if(c.push(u),!s)return c}}while(e&&(i.lastIndex=r));return c};I.helper.matchRecursiveRegExp=function(g,A,C,I){"use strict";for(var e=o(g,A,C,I),r=[],t=0;t<e.length;++t)r.push([g.slice(e[t].wholeMatch.start,e[t].wholeMatch.end),g.slice(e[t].match.start,e[t].match.end),g.slice(e[t].left.start,e[t].left.end),g.slice(e[t].right.start,e[t].right.end)]);return r},I.helper.replaceRecursiveRegExp=function(g,A,C,e,r){"use strict";if(!I.helper.isFunction(A)){var t=A;A=function(){return t}}var a=o(g,C,e,r),n=g,s=a.length;if(s>0){var i=[];0!==a[0].wholeMatch.start&&i.push(g.slice(0,a[0].wholeMatch.start));for(var l=0;l<s;++l)i.push(A(g.slice(a[l].wholeMatch.start,a[l].wholeMatch.end),g.slice(a[l].match.start,a[l].match.end),g.slice(a[l].left.start,a[l].left.end),g.slice(a[l].right.start,a[l].right.end))),l<s-1&&i.push(g.slice(a[l].wholeMatch.end,a[l+1].wholeMatch.start));a[s-1].wholeMatch.end<g.length&&i.push(g.slice(a[s-1].wholeMatch.end)),n=i.join("")}return n},I.helper.regexIndexOf=function(g,A,C){"use strict";if(!I.helper.isString(g))throw"InvalidArgumentError: first parameter of showdown.helper.regexIndexOf function must be a string";if(A instanceof RegExp==!1)throw"InvalidArgumentError: second parameter of showdown.helper.regexIndexOf function must be an instance of RegExp";var e=g.substring(C||0).search(A);return e>=0?e+(C||0):e},I.helper.splitAtIndex=function(g,A){"use strict";if(!I.helper.isString(g))throw"InvalidArgumentError: first parameter of showdown.helper.regexIndexOf function must be a string";return[g.substring(0,A),g.substring(A)]},I.helper.encodeEmailAddress=function(g){"use strict";var A=[function(g){return"&#"+g.charCodeAt(0)+";"},function(g){return"&#x"+g.charCodeAt(0).toString(16)+";"},function(g){return g}];return g=g.replace(/./g,function(g){if("@"===g)g=A[Math.floor(2*Math.random())](g);else{var C=Math.random();g=C>.9?A[2](g):C>.45?A[1](g):A[0](g)}return g})},"undefined"==typeof console&&(console={warn:function(g){"use strict";alert(g)},log:function(g){"use strict";alert(g)},error:function(g){"use strict";throw g}}),I.helper.regexes={asteriskDashAndColon:/([*_:~])/g},I.helper.emojis={"+1":"👍","-1":"👎",100:"💯",1234:"🔢","1st_place_medal":"🥇","2nd_place_medal":"🥈","3rd_place_medal":"🥉","8ball":"🎱",a:"🅰️",ab:"🆎",abc:"🔤",abcd:"🔡",accept:"🉑",aerial_tramway:"🚡",airplane:"✈️",alarm_clock:"⏰",alembic:"⚗️",alien:"👽",ambulance:"🚑",amphora:"🏺",anchor:"⚓️",angel:"👼",anger:"💢",angry:"😠",anguished:"😧",ant:"🐜",apple:"🍎",aquarius:"♒️",aries:"♈️",arrow_backward:"◀️",arrow_double_down:"⏬",arrow_double_up:"⏫",arrow_down:"⬇️",arrow_down_small:"🔽",arrow_forward:"▶️",arrow_heading_down:"⤵️",arrow_heading_up:"⤴️",arrow_left:"⬅️",arrow_lower_left:"↙️",arrow_lower_right:"↘️",arrow_right:"➡️",arrow_right_hook:"↪️",arrow_up:"⬆️",arrow_up_down:"↕️",arrow_up_small:"🔼",arrow_upper_left:"↖️",arrow_upper_right:"↗️",arrows_clockwise:"🔃",arrows_counterclockwise:"🔄",art:"🎨",articulated_lorry:"🚛",artificial_satellite:"🛰",astonished:"😲",athletic_shoe:"👟",atm:"🏧",atom_symbol:"⚛️",avocado:"🥑",b:"🅱️",baby:"👶",baby_bottle:"🍼",baby_chick:"🐤",baby_symbol:"🚼",back:"🔙",bacon:"🥓",badminton:"🏸",baggage_claim:"🛄",baguette_bread:"🥖",balance_scale:"⚖️",balloon:"🎈",ballot_box:"🗳",ballot_box_with_check:"☑️",bamboo:"🎍",banana:"🍌",bangbang:"‼️",bank:"🏦",bar_chart:"📊",barber:"💈",baseball:"⚾️",basketball:"🏀",basketball_man:"⛹️",basketball_woman:"⛹️‍♀️",bat:"🦇",bath:"🛀",bathtub:"🛁",battery:"🔋",beach_umbrella:"🏖",bear:"🐻",bed:"🛏",bee:"🐝",beer:"🍺",beers:"🍻",beetle:"🐞",beginner:"🔰",bell:"🔔",bellhop_bell:"🛎",bento:"🍱",biking_man:"🚴",bike:"🚲",biking_woman:"🚴‍♀️",bikini:"👙",biohazard:"☣️",bird:"🐦",birthday:"🎂",black_circle:"⚫️",black_flag:"🏴",black_heart:"🖤",black_joker:"🃏",black_large_square:"⬛️",black_medium_small_square:"◾️",black_medium_square:"◼️",black_nib:"✒️",black_small_square:"▪️",black_square_button:"🔲",blonde_man:"👱",blonde_woman:"👱‍♀️",blossom:"🌼",blowfish:"🐡",blue_book:"📘",blue_car:"🚙",blue_heart:"💙",blush:"😊",boar:"🐗",boat:"⛵️",bomb:"💣",book:"📖",bookmark:"🔖",bookmark_tabs:"📑",books:"📚",boom:"💥",boot:"👢",bouquet:"💐",bowing_man:"🙇",bow_and_arrow:"🏹",bowing_woman:"🙇‍♀️",bowling:"🎳",boxing_glove:"🥊",boy:"👦",bread:"🍞",bride_with_veil:"👰",bridge_at_night:"🌉",briefcase:"💼",broken_heart:"💔",bug:"🐛",building_construction:"🏗",bulb:"💡",bullettrain_front:"🚅",bullettrain_side:"🚄",burrito:"🌯",bus:"🚌",business_suit_levitating:"🕴",busstop:"🚏",bust_in_silhouette:"👤",busts_in_silhouette:"👥",butterfly:"🦋",cactus:"🌵",cake:"🍰",calendar:"📆",call_me_hand:"🤙",calling:"📲",camel:"🐫",camera:"📷",camera_flash:"📸",camping:"🏕",cancer:"♋️",candle:"🕯",candy:"🍬",canoe:"🛶",capital_abcd:"🔠",capricorn:"♑️",car:"🚗",card_file_box:"🗃",card_index:"📇",card_index_dividers:"🗂",carousel_horse:"🎠",carrot:"🥕",cat:"🐱",cat2:"🐈",cd:"💿",chains:"⛓",champagne:"🍾",chart:"💹",chart_with_downwards_trend:"📉",chart_with_upwards_trend:"📈",checkered_flag:"🏁",cheese:"🧀",cherries:"🍒",cherry_blossom:"🌸",chestnut:"🌰",chicken:"🐔",children_crossing:"🚸",chipmunk:"🐿",chocolate_bar:"🍫",christmas_tree:"🎄",church:"⛪️",cinema:"🎦",circus_tent:"🎪",city_sunrise:"🌇",city_sunset:"🌆",cityscape:"🏙",cl:"🆑",clamp:"🗜",clap:"👏",clapper:"🎬",classical_building:"🏛",clinking_glasses:"🥂",clipboard:"📋",clock1:"🕐",clock10:"🕙",clock1030:"🕥",clock11:"🕚",clock1130:"🕦",clock12:"🕛",clock1230:"🕧",clock130:"🕜",clock2:"🕑",clock230:"🕝",clock3:"🕒",clock330:"🕞",clock4:"🕓",clock430:"🕟",clock5:"🕔",clock530:"🕠",clock6:"🕕",clock630:"🕡",clock7:"🕖",clock730:"🕢",clock8:"🕗",clock830:"🕣",clock9:"🕘",clock930:"🕤",closed_book:"📕",closed_lock_with_key:"🔐",closed_umbrella:"🌂",cloud:"☁️",cloud_with_lightning:"🌩",cloud_with_lightning_and_rain:"⛈",cloud_with_rain:"🌧",cloud_with_snow:"🌨",clown_face:"🤡",clubs:"♣️",cocktail:"🍸",coffee:"☕️",coffin:"⚰️",cold_sweat:"😰",comet:"☄️",computer:"💻",computer_mouse:"🖱",confetti_ball:"🎊",confounded:"😖",confused:"😕",congratulations:"㊗️",construction:"🚧",construction_worker_man:"👷",construction_worker_woman:"👷‍♀️",control_knobs:"🎛",convenience_store:"🏪",cookie:"🍪",cool:"🆒",policeman:"👮",copyright:"©️",corn:"🌽",couch_and_lamp:"🛋",couple:"👫",couple_with_heart_woman_man:"💑",couple_with_heart_man_man:"👨‍❤️‍👨",couple_with_heart_woman_woman:"👩‍❤️‍👩",couplekiss_man_man:"👨‍❤️‍💋‍👨",couplekiss_man_woman:"💏",couplekiss_woman_woman:"👩‍❤️‍💋‍👩",cow:"🐮",cow2:"🐄",cowboy_hat_face:"🤠",crab:"🦀",crayon:"🖍",credit_card:"💳",crescent_moon:"🌙",cricket:"🏏",crocodile:"🐊",croissant:"🥐",crossed_fingers:"🤞",crossed_flags:"🎌",crossed_swords:"⚔️",crown:"👑",cry:"😢",crying_cat_face:"😿",crystal_ball:"🔮",cucumber:"🥒",cupid:"💘",curly_loop:"➰",currency_exchange:"💱",curry:"🍛",custard:"🍮",customs:"🛃",cyclone:"🌀",dagger:"🗡",dancer:"💃",dancing_women:"👯",dancing_men:"👯‍♂️",dango:"🍡",dark_sunglasses:"🕶",dart:"🎯",dash:"💨",date:"📅",deciduous_tree:"🌳",deer:"🦌",department_store:"🏬",derelict_house:"🏚",desert:"🏜",desert_island:"🏝",desktop_computer:"🖥",male_detective:"🕵️",diamond_shape_with_a_dot_inside:"💠",diamonds:"♦️",disappointed:"😞",disappointed_relieved:"😥",dizzy:"💫",dizzy_face:"😵",do_not_litter:"🚯",dog:"🐶",dog2:"🐕",dollar:"💵",dolls:"🎎",dolphin:"🐬",door:"🚪",doughnut:"🍩",dove:"🕊",dragon:"🐉",dragon_face:"🐲",dress:"👗",dromedary_camel:"🐪",drooling_face:"🤤",droplet:"💧",drum:"🥁",duck:"🦆",dvd:"📀","e-mail":"📧",eagle:"🦅",ear:"👂",ear_of_rice:"🌾",earth_africa:"🌍",earth_americas:"🌎",earth_asia:"🌏",egg:"🥚",eggplant:"🍆",eight_pointed_black_star:"✴️",eight_spoked_asterisk:"✳️",electric_plug:"🔌",elephant:"🐘",email:"✉️",end:"🔚",envelope_with_arrow:"📩",euro:"💶",european_castle:"🏰",european_post_office:"🏤",evergreen_tree:"🌲",exclamation:"❗️",expressionless:"😑",eye:"👁",eye_speech_bubble:"👁‍🗨",eyeglasses:"👓",eyes:"👀",face_with_head_bandage:"🤕",face_with_thermometer:"🤒",fist_oncoming:"👊",factory:"🏭",fallen_leaf:"🍂",family_man_woman_boy:"👪",family_man_boy:"👨‍👦",family_man_boy_boy:"👨‍👦‍👦",family_man_girl:"👨‍👧",family_man_girl_boy:"👨‍👧‍👦",family_man_girl_girl:"👨‍👧‍👧",family_man_man_boy:"👨‍👨‍👦",family_man_man_boy_boy:"👨‍👨‍👦‍👦",family_man_man_girl:"👨‍👨‍👧",family_man_man_girl_boy:"👨‍👨‍👧‍👦",family_man_man_girl_girl:"👨‍👨‍👧‍👧",family_man_woman_boy_boy:"👨‍👩‍👦‍👦",family_man_woman_girl:"👨‍👩‍👧",family_man_woman_girl_boy:"👨‍👩‍👧‍👦",family_man_woman_girl_girl:"👨‍👩‍👧‍👧",family_woman_boy:"👩‍👦",family_woman_boy_boy:"👩‍👦‍👦",family_woman_girl:"👩‍👧",family_woman_girl_boy:"👩‍👧‍👦",family_woman_girl_girl:"👩‍👧‍👧",family_woman_woman_boy:"👩‍👩‍👦",family_woman_woman_boy_boy:"👩‍👩‍👦‍👦",family_woman_woman_girl:"👩‍👩‍👧",family_woman_woman_girl_boy:"👩‍👩‍👧‍👦",family_woman_woman_girl_girl:"👩‍👩‍👧‍👧",fast_forward:"⏩",fax:"📠",fearful:"😨",feet:"🐾",female_detective:"🕵️‍♀️",ferris_wheel:"🎡",ferry:"⛴",field_hockey:"🏑",file_cabinet:"🗄",file_folder:"📁",film_projector:"📽",film_strip:"🎞",fire:"🔥",fire_engine:"🚒",fireworks:"🎆",first_quarter_moon:"🌓",first_quarter_moon_with_face:"🌛",fish:"🐟",fish_cake:"🍥",fishing_pole_and_fish:"🎣",fist_raised:"✊",fist_left:"🤛",fist_right:"🤜",flags:"🎏",flashlight:"🔦",fleur_de_lis:"⚜️",flight_arrival:"🛬",flight_departure:"🛫",floppy_disk:"💾",flower_playing_cards:"🎴",flushed:"😳",fog:"🌫",foggy:"🌁",football:"🏈",footprints:"👣",fork_and_knife:"🍴",fountain:"⛲️",fountain_pen:"🖋",four_leaf_clover:"🍀",fox_face:"🦊",framed_picture:"🖼",free:"🆓",fried_egg:"🍳",fried_shrimp:"🍤",fries:"🍟",frog:"🐸",frowning:"😦",frowning_face:"☹️",frowning_man:"🙍‍♂️",frowning_woman:"🙍",middle_finger:"🖕",fuelpump:"⛽️",full_moon:"🌕",full_moon_with_face:"🌝",funeral_urn:"⚱️",game_die:"🎲",gear:"⚙️",gem:"💎",gemini:"♊️",ghost:"👻",gift:"🎁",gift_heart:"💝",girl:"👧",globe_with_meridians:"🌐",goal_net:"🥅",goat:"🐐",golf:"⛳️",golfing_man:"🏌️",golfing_woman:"🏌️‍♀️",gorilla:"🦍",grapes:"🍇",green_apple:"🍏",green_book:"📗",green_heart:"💚",green_salad:"🥗",grey_exclamation:"❕",grey_question:"❔",grimacing:"😬",grin:"😁",grinning:"😀",guardsman:"💂",guardswoman:"💂‍♀️",guitar:"🎸",gun:"🔫",haircut_woman:"💇",haircut_man:"💇‍♂️",hamburger:"🍔",hammer:"🔨",hammer_and_pick:"⚒",hammer_and_wrench:"🛠",hamster:"🐹",hand:"✋",handbag:"👜",handshake:"🤝",hankey:"💩",hatched_chick:"🐥",hatching_chick:"🐣",headphones:"🎧",hear_no_evil:"🙉",heart:"❤️",heart_decoration:"💟",heart_eyes:"😍",heart_eyes_cat:"😻",heartbeat:"💓",heartpulse:"💗",hearts:"♥️",heavy_check_mark:"✔️",heavy_division_sign:"➗",heavy_dollar_sign:"💲",heavy_heart_exclamation:"❣️",heavy_minus_sign:"➖",heavy_multiplication_x:"✖️",heavy_plus_sign:"➕",helicopter:"🚁",herb:"🌿",hibiscus:"🌺",high_brightness:"🔆",high_heel:"👠",hocho:"🔪",hole:"🕳",honey_pot:"🍯",horse:"🐴",horse_racing:"🏇",hospital:"🏥",hot_pepper:"🌶",hotdog:"🌭",hotel:"🏨",hotsprings:"♨️",hourglass:"⌛️",hourglass_flowing_sand:"⏳",house:"🏠",house_with_garden:"🏡",houses:"🏘",hugs:"🤗",hushed:"😯",ice_cream:"🍨",ice_hockey:"🏒",ice_skate:"⛸",icecream:"🍦",id:"🆔",ideograph_advantage:"🉐",imp:"👿",inbox_tray:"📥",incoming_envelope:"📨",tipping_hand_woman:"💁",information_source:"ℹ️",innocent:"😇",interrobang:"⁉️",iphone:"📱",izakaya_lantern:"🏮",jack_o_lantern:"🎃",japan:"🗾",japanese_castle:"🏯",japanese_goblin:"👺",japanese_ogre:"👹",jeans:"👖",joy:"😂",joy_cat:"😹",joystick:"🕹",kaaba:"🕋",key:"🔑",keyboard:"⌨️",keycap_ten:"🔟",kick_scooter:"🛴",kimono:"👘",kiss:"💋",kissing:"😗",kissing_cat:"😽",kissing_closed_eyes:"😚",kissing_heart:"😘",kissing_smiling_eyes:"😙",kiwi_fruit:"🥝",koala:"🐨",koko:"🈁",label:"🏷",large_blue_circle:"🔵",large_blue_diamond:"🔷",large_orange_diamond:"🔶",last_quarter_moon:"🌗",last_quarter_moon_with_face:"🌜",latin_cross:"✝️",laughing:"😆",leaves:"🍃",ledger:"📒",left_luggage:"🛅",left_right_arrow:"↔️",leftwards_arrow_with_hook:"↩️",lemon:"🍋",leo:"♌️",leopard:"🐆",level_slider:"🎚",libra:"♎️",light_rail:"🚈",link:"🔗",lion:"🦁",lips:"👄",lipstick:"💄",lizard:"🦎",lock:"🔒",lock_with_ink_pen:"🔏",lollipop:"🍭",loop:"➿",loud_sound:"🔊",loudspeaker:"📢",love_hotel:"🏩",love_letter:"💌",low_brightness:"🔅",lying_face:"🤥",m:"Ⓜ️",mag:"🔍",mag_right:"🔎",mahjong:"🀄️",mailbox:"📫",mailbox_closed:"📪",mailbox_with_mail:"📬",mailbox_with_no_mail:"📭",man:"👨",man_artist:"👨‍🎨",man_astronaut:"👨‍🚀",man_cartwheeling:"🤸‍♂️",man_cook:"👨‍🍳",man_dancing:"🕺",man_facepalming:"🤦‍♂️",man_factory_worker:"👨‍🏭",man_farmer:"👨‍🌾",man_firefighter:"👨‍🚒",man_health_worker:"👨‍⚕️",man_in_tuxedo:"🤵",man_judge:"👨‍⚖️",man_juggling:"🤹‍♂️",man_mechanic:"👨‍🔧",man_office_worker:"👨‍💼",man_pilot:"👨‍✈️",man_playing_handball:"🤾‍♂️",man_playing_water_polo:"🤽‍♂️",man_scientist:"👨‍🔬",man_shrugging:"🤷‍♂️",man_singer:"👨‍🎤",man_student:"👨‍🎓",man_teacher:"👨‍🏫",man_technologist:"👨‍💻",man_with_gua_pi_mao:"👲",man_with_turban:"👳",tangerine:"🍊",mans_shoe:"👞",mantelpiece_clock:"🕰",maple_leaf:"🍁",martial_arts_uniform:"🥋",mask:"😷",massage_woman:"💆",massage_man:"💆‍♂️",meat_on_bone:"🍖",medal_military:"🎖",medal_sports:"🏅",mega:"📣",melon:"🍈",memo:"📝",men_wrestling:"🤼‍♂️",menorah:"🕎",mens:"🚹",metal:"🤘",metro:"🚇",microphone:"🎤",microscope:"🔬",milk_glass:"🥛",milky_way:"🌌",minibus:"🚐",minidisc:"💽",mobile_phone_off:"📴",money_mouth_face:"🤑",money_with_wings:"💸",moneybag:"💰",monkey:"🐒",monkey_face:"🐵",monorail:"🚝",moon:"🌔",mortar_board:"🎓",mosque:"🕌",motor_boat:"🛥",motor_scooter:"🛵",motorcycle:"🏍",motorway:"🛣",mount_fuji:"🗻",mountain:"⛰",mountain_biking_man:"🚵",mountain_biking_woman:"🚵‍♀️",mountain_cableway:"🚠",mountain_railway:"🚞",mountain_snow:"🏔",mouse:"🐭",mouse2:"🐁",movie_camera:"🎥",moyai:"🗿",mrs_claus:"🤶",muscle:"💪",mushroom:"🍄",musical_keyboard:"🎹",musical_note:"🎵",musical_score:"🎼",mute:"🔇",nail_care:"💅",name_badge:"📛",national_park:"🏞",nauseated_face:"🤢",necktie:"👔",negative_squared_cross_mark:"❎",nerd_face:"🤓",neutral_face:"😐",new:"🆕",new_moon:"🌑",new_moon_with_face:"🌚",newspaper:"📰",newspaper_roll:"🗞",next_track_button:"⏭",ng:"🆖",no_good_man:"🙅‍♂️",no_good_woman:"🙅",night_with_stars:"🌃",no_bell:"🔕",no_bicycles:"🚳",no_entry:"⛔️",no_entry_sign:"🚫",no_mobile_phones:"📵",no_mouth:"😶",no_pedestrians:"🚷",no_smoking:"🚭","non-potable_water":"🚱",nose:"👃",notebook:"📓",notebook_with_decorative_cover:"📔",notes:"🎶",nut_and_bolt:"🔩",o:"⭕️",o2:"🅾️",ocean:"🌊",octopus:"🐙",oden:"🍢",office:"🏢",oil_drum:"🛢",ok:"🆗",ok_hand:"👌",ok_man:"🙆‍♂️",ok_woman:"🙆",old_key:"🗝",older_man:"👴",older_woman:"👵",om:"🕉",on:"🔛",oncoming_automobile:"🚘",oncoming_bus:"🚍",oncoming_police_car:"🚔",oncoming_taxi:"🚖",open_file_folder:"📂",open_hands:"👐",open_mouth:"😮",open_umbrella:"☂️",ophiuchus:"⛎",orange_book:"📙",orthodox_cross:"☦️",outbox_tray:"📤",owl:"🦉",ox:"🐂",package:"📦",page_facing_up:"📄",page_with_curl:"📃",pager:"📟",paintbrush:"🖌",palm_tree:"🌴",pancakes:"🥞",panda_face:"🐼",paperclip:"📎",paperclips:"🖇",parasol_on_ground:"⛱",parking:"🅿️",part_alternation_mark:"〽️",partly_sunny:"⛅️",passenger_ship:"🛳",passport_control:"🛂",pause_button:"⏸",peace_symbol:"☮️",peach:"🍑",peanuts:"🥜",pear:"🍐",pen:"🖊",pencil2:"✏️",penguin:"🐧",pensive:"😔",performing_arts:"🎭",persevere:"😣",person_fencing:"🤺",pouting_woman:"🙎",phone:"☎️",pick:"⛏",pig:"🐷",pig2:"🐖",pig_nose:"🐽",pill:"💊",pineapple:"🍍",ping_pong:"🏓",pisces:"♓️",pizza:"🍕",place_of_worship:"🛐",plate_with_cutlery:"🍽",play_or_pause_button:"⏯",point_down:"👇",point_left:"👈",point_right:"👉",point_up:"☝️",point_up_2:"👆",police_car:"🚓",policewoman:"👮‍♀️",poodle:"🐩",popcorn:"🍿",post_office:"🏣",postal_horn:"📯",postbox:"📮",potable_water:"🚰",potato:"🥔",pouch:"👝",poultry_leg:"🍗",pound:"💷",rage:"😡",pouting_cat:"😾",pouting_man:"🙎‍♂️",pray:"🙏",prayer_beads:"📿",pregnant_woman:"🤰",previous_track_button:"⏮",prince:"🤴",princess:"👸",printer:"🖨",purple_heart:"💜",purse:"👛",pushpin:"📌",put_litter_in_its_place:"🚮",question:"❓",rabbit:"🐰",rabbit2:"🐇",racehorse:"🐎",racing_car:"🏎",radio:"📻",radio_button:"🔘",radioactive:"☢️",railway_car:"🚃",railway_track:"🛤",rainbow:"🌈",rainbow_flag:"🏳️‍🌈",raised_back_of_hand:"🤚",raised_hand_with_fingers_splayed:"🖐",raised_hands:"🙌",raising_hand_woman:"🙋",raising_hand_man:"🙋‍♂️",ram:"🐏",ramen:"🍜",rat:"🐀",record_button:"⏺",recycle:"♻️",red_circle:"🔴",registered:"®️",relaxed:"☺️",relieved:"😌",reminder_ribbon:"🎗",repeat:"🔁",repeat_one:"🔂",rescue_worker_helmet:"⛑",restroom:"🚻",revolving_hearts:"💞",rewind:"⏪",rhinoceros:"🦏",ribbon:"🎀",rice:"🍚",rice_ball:"🍙",rice_cracker:"🍘",rice_scene:"🎑",right_anger_bubble:"🗯",ring:"💍",robot:"🤖",rocket:"🚀",rofl:"🤣",roll_eyes:"🙄",roller_coaster:"🎢",rooster:"🐓",rose:"🌹",rosette:"🏵",rotating_light:"🚨",round_pushpin:"📍",rowing_man:"🚣",rowing_woman:"🚣‍♀️",rugby_football:"🏉",running_man:"🏃",running_shirt_with_sash:"🎽",running_woman:"🏃‍♀️",sa:"🈂️",sagittarius:"♐️",sake:"🍶",sandal:"👡",santa:"🎅",satellite:"📡",saxophone:"🎷",school:"🏫",school_satchel:"🎒",scissors:"✂️",scorpion:"🦂",scorpius:"♏️",scream:"😱",scream_cat:"🙀",scroll:"📜",seat:"💺",secret:"㊙️",see_no_evil:"🙈",seedling:"🌱",selfie:"🤳",shallow_pan_of_food:"🥘",shamrock:"☘️",shark:"🦈",shaved_ice:"🍧",sheep:"🐑",shell:"🐚",shield:"🛡",shinto_shrine:"⛩",ship:"🚢",shirt:"👕",shopping:"🛍",shopping_cart:"🛒",shower:"🚿",shrimp:"🦐",signal_strength:"📶",six_pointed_star:"🔯",ski:"🎿",skier:"⛷",skull:"💀",skull_and_crossbones:"☠️",sleeping:"😴",sleeping_bed:"🛌",sleepy:"😪",slightly_frowning_face:"🙁",slightly_smiling_face:"🙂",slot_machine:"🎰",small_airplane:"🛩",small_blue_diamond:"🔹",small_orange_diamond:"🔸",small_red_triangle:"🔺",small_red_triangle_down:"🔻",smile:"😄",smile_cat:"😸",smiley:"😃",smiley_cat:"😺",smiling_imp:"😈",smirk:"😏",smirk_cat:"😼",smoking:"🚬",snail:"🐌",snake:"🐍",sneezing_face:"🤧",snowboarder:"🏂",snowflake:"❄️",snowman:"⛄️",snowman_with_snow:"☃️",sob:"😭",soccer:"⚽️",soon:"🔜",sos:"🆘",sound:"🔉",space_invader:"👾",spades:"♠️",spaghetti:"🍝",sparkle:"❇️",sparkler:"🎇",sparkles:"✨",sparkling_heart:"💖",speak_no_evil:"🙊",speaker:"🔈",speaking_head:"🗣",speech_balloon:"💬",speedboat:"🚤",spider:"🕷",spider_web:"🕸",spiral_calendar:"🗓",spiral_notepad:"🗒",spoon:"🥄",squid:"🦑",stadium:"🏟",star:"⭐️",star2:"🌟",star_and_crescent:"☪️",star_of_david:"✡️",stars:"🌠",station:"🚉",statue_of_liberty:"🗽",steam_locomotive:"🚂",stew:"🍲",stop_button:"⏹",stop_sign:"🛑",stopwatch:"⏱",straight_ruler:"📏",strawberry:"🍓",stuck_out_tongue:"😛",stuck_out_tongue_closed_eyes:"😝",stuck_out_tongue_winking_eye:"😜",studio_microphone:"🎙",stuffed_flatbread:"🥙",sun_behind_large_cloud:"🌥",sun_behind_rain_cloud:"🌦",sun_behind_small_cloud:"🌤",sun_with_face:"🌞",sunflower:"🌻",sunglasses:"😎",sunny:"☀️",sunrise:"🌅",sunrise_over_mountains:"🌄",surfing_man:"🏄",surfing_woman:"🏄‍♀️",sushi:"🍣",suspension_railway:"🚟",sweat:"😓",sweat_drops:"💦",sweat_smile:"😅",sweet_potato:"🍠",swimming_man:"🏊",swimming_woman:"🏊‍♀️",symbols:"🔣",synagogue:"🕍",syringe:"💉",taco:"🌮",tada:"🎉",tanabata_tree:"🎋",taurus:"♉️",taxi:"🚕",tea:"🍵",telephone_receiver:"📞",telescope:"🔭",tennis:"🎾",tent:"⛺️",thermometer:"🌡",thinking:"🤔",thought_balloon:"💭",ticket:"🎫",tickets:"🎟",tiger:"🐯",tiger2:"🐅",timer_clock:"⏲",tipping_hand_man:"💁‍♂️",tired_face:"😫",tm:"™️",toilet:"🚽",tokyo_tower:"🗼",tomato:"🍅",tongue:"👅",top:"🔝",tophat:"🎩",tornado:"🌪",trackball:"🖲",tractor:"🚜",traffic_light:"🚥",train:"🚋",train2:"🚆",tram:"🚊",triangular_flag_on_post:"🚩",triangular_ruler:"📐",trident:"🔱",triumph:"😤",trolleybus:"🚎",trophy:"🏆",tropical_drink:"🍹",tropical_fish:"🐠",truck:"🚚",trumpet:"🎺",tulip:"🌷",tumbler_glass:"🥃",turkey:"🦃",turtle:"🐢",tv:"📺",twisted_rightwards_arrows:"🔀",two_hearts:"💕",two_men_holding_hands:"👬",two_women_holding_hands:"👭",u5272:"🈹",u5408:"🈴",u55b6:"🈺",u6307:"🈯️",u6708:"🈷️",u6709:"🈶",u6e80:"🈵",u7121:"🈚️",u7533:"🈸",u7981:"🈲",u7a7a:"🈳",umbrella:"☔️",unamused:"😒",underage:"🔞",unicorn:"🦄",unlock:"🔓",up:"🆙",upside_down_face:"🙃",v:"✌️",vertical_traffic_light:"🚦",vhs:"📼",vibration_mode:"📳",video_camera:"📹",video_game:"🎮",violin:"🎻",virgo:"♍️",volcano:"🌋",volleyball:"🏐",vs:"🆚",vulcan_salute:"🖖",walking_man:"🚶",walking_woman:"🚶‍♀️",waning_crescent_moon:"🌘",waning_gibbous_moon:"🌖",warning:"⚠️",wastebasket:"🗑",watch:"⌚️",water_buffalo:"🐃",watermelon:"🍉",wave:"👋",wavy_dash:"〰️",waxing_crescent_moon:"🌒",wc:"🚾",weary:"😩",wedding:"💒",weight_lifting_man:"🏋️",weight_lifting_woman:"🏋️‍♀️",whale:"🐳",whale2:"🐋",wheel_of_dharma:"☸️",wheelchair:"♿️",white_check_mark:"✅",white_circle:"⚪️",white_flag:"🏳️",white_flower:"💮",white_large_square:"⬜️",white_medium_small_square:"◽️",white_medium_square:"◻️",white_small_square:"▫️",white_square_button:"🔳",wilted_flower:"🥀",wind_chime:"🎐",wind_face:"🌬",wine_glass:"🍷",wink:"😉",wolf:"🐺",woman:"👩",woman_artist:"👩‍🎨",woman_astronaut:"👩‍🚀",woman_cartwheeling:"🤸‍♀️",woman_cook:"👩‍🍳",woman_facepalming:"🤦‍♀️",woman_factory_worker:"👩‍🏭",woman_farmer:"👩‍🌾",woman_firefighter:"👩‍🚒",woman_health_worker:"👩‍⚕️",woman_judge:"👩‍⚖️",woman_juggling:"🤹‍♀️",woman_mechanic:"👩‍🔧",woman_office_worker:"👩‍💼",woman_pilot:"👩‍✈️",woman_playing_handball:"🤾‍♀️",woman_playing_water_polo:"🤽‍♀️",woman_scientist:"👩‍🔬",woman_shrugging:"🤷‍♀️",woman_singer:"👩‍🎤",woman_student:"👩‍🎓",woman_teacher:"👩‍🏫",woman_technologist:"👩‍💻",woman_with_turban:"👳‍♀️",womans_clothes:"👚",womans_hat:"👒",women_wrestling:"🤼‍♀️",womens:"🚺",world_map:"🗺",worried:"😟",wrench:"🔧",writing_hand:"✍️",x:"❌",yellow_heart:"💛",yen:"💴",yin_yang:"☯️",yum:"😋",zap:"⚡️",zipper_mouth_face:"🤐",zzz:"💤",octocat:'<img width="20" height="20" align="absmiddle" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAOwUlEQVR42uVbCVyO6RbPmn0sw9gZS0aZO4y5GTEUE2ObxjZjrbHEJVy3sWS5pkaWxjLEkCVDSbSgFLdESaWSLIVUSIi4kvb9f895vi/zbbR+yZ339/tbnu99n/ec/3Oe85xznufV0CjDBaAdwZqwnzCJ0FXjHV70/i8J5oQDhCFV8cJdq1atwqxZs+Ds7Iz4+HhqwgXCLELNKlK6G2Ej4e6lS5ewZcsWzJgxA+fOnWNZFqvzxT1v3boF/qcsBg0ahP3796OwsJAFWKYuIqjfPoS9cXFxWL58Obp06SInh5aWFr//jjoJWLlu3TolAorRuXNn7Ny5k4W4Spgj81xrgj5hLmED4RDhlNRygglBhADCSakpWxFMCHoETUJTwrYHDx7A1NT0je9nPHz4kN/fXl0EeI0aNeqtAjB69+4NPz8/FsSdlXvy5An8/f1hZ2cHCwsLGBsbY/To0cJy9PT0MGDAAAwePBhGRkbClNesWYODBw8iODgYOTk53M/d9evXo27duiW++8iRI3z/ZHURENOjR48ShSjGuHHjhHJ16tQp9TOKaNWqlZKpvw1MHluQOpSvk5eXh5YtW5ZbmarAvHnzmIBd6iCgXnZ2Npo1a1atCWAfwY5SHQTUKCoqQocOHao1AebmHBJgi7p8QBDP6epMwKFDvMDAWF0ELLS1ta3WBNy9e5cJMFIXAdvt7e2rNQHDhw9nAv5D+KKylV9y8+bNCi1pVYWZM2cyCfaVTcDdsqzH7xpBQRxcwqyylLdi5/K+KM/Q0dFhAqIri4Bn1T0AUgVpdmhYUeVHnD59+r1TnjF27Fgm4HhFCThoYmLyXhLQoEGD4mRKsyIE3OrZs+d7SQCDCyZcNSqv8k1evXoFTU3NUr+wzUcfYqRBf8yb/C2WzfoBFoTF08fBdMIITDD8CsP1+kL30x7Q6dYZH7drjfZ0f4fWLdG1Q1t81qMLBvTRwejB/TBl1BDMnzQGS2dMxKo5k7Fs9iSY/jAaBvR8Pc26pZaH02quLZSXgO6xsbGlelGnli1wZKcVMqN8gKcRwItrf+K/VB95doXaLwOJIVSzOU/+2Re5kV7IuuyJrIhTyLt6mmztLBBPNZLHoUAy9fE8UvJ8ikxfj8PwJPQErJeYlkquTZs2MQFLykuANgc/Jb2kn3Z3ZMaQUrmxwO1zyAo7gfRAJ6RfOIyMEFdkXj5F8BTK5lzxQv610yi8QcFatI8gQoCIK7x+hojwRnaE5H4JTiEj9Pjr/rJDqcZyn9b4ovu45LYbdWvXeqtsXMHiSlZ5CegRExPz1hd83PYj5POo0QinXyLFg48hnZTOiQ1Dzr1IZEaeQRoJn0HKZIR7lA2kfHrQUerXHTlx4ZL+rnjjFRGRGeYB5MUj2GnbW+XbuJFrp1heXgI6JCYmvvUFN1x3Aek3SWkapRAXMeJFGS8ge2Xfuog0toaykED3Mpk8+shOk+sv68Y50V9WuKewBKt5094o39atW/mRf5WXgIYZGRlo3Lixys4nj6A6Z1YMcqRCpwU4ouDlUyHk/QA/hNttR25Wlvh/ZthJUsil9ATQ/axkYbqEzDgfL0Ts/x35+aLyTES7IY36Q6w/+Q4/tP6wuUoZ9+7dy7ebVmQZjO/atavKzn32rAdeXkd6KCkXdAxZ13yFcLFnvPD73zrDVrsdTs6eggKSuSjjORHkUGoC0i86Iyc6QPQX7eqMnTodYNuzHU4vnosiaitMSUSavwMy6d3IvEUrzViVMrq5uXEX4ytCgL++vr5Sx7Vr1cIDX0dKkQJfj37Rs3jw1sBxkwlwGD4Ax3+ciN1faCHW76xQRFgAOcjSEMBkIe0x8nLzcez7kTg8Rh/uxuOxR/cTJISFSfq7eATpZCk8CAfXLVFJwIULXHnHoIoQYLtw4UKljps2aogXQcQuef/XAiMDKY+S4DhyEFwpDnCj9f+Afl8EbbWRTANaAdihlYoAMn8aZzyNuYODX/eD29TvRH/7v+qN8H27JdOAyWQfQQ74xPafVRLAPox9WUlK6hIGEgx4f00Kg2JcvHhRqeP6FIwknXemyen/2gLIIeC/CYk49M0AuE4xgtu0sThg8AUCN62TEuBdRgJo2Y+Kxh9D/k59SQiwH9QHobt3SAk4KSGA4oWjm1YqyVi8U6Soj4yOrHM/jTAyKVby/PnzIoNi8L+L4eXlpXoFcLcTgc1rAlISkJeXDxeK2A6P1hdTwI6mQPTJE+WbAlnJyE7PhNO3Q3BkrKGYWtxfHMkkmQLO0ilwA7+vXqAkn66urtBLUZ9iHfm30NBQaPAf165dA0d9vP2UlJSEp0+f4vHjx3j06JH4e+rUqUovcNmyGkiNEkLwklXsBG+ecMUOnfbYod1emG5uboFKJ8jPFVD0l0dBUHqoPDHpQeQEb0qc4FUHe3KAbYUT9JgzDbwOFL5MfN0fXkXhJ5PxSvLt2LFD1Ah5u4z1YJ14l4qnBe8v3rhxAzz4PAVG8nLHivIP0dHRiIiIQGRkpEgmrl69ClW1QBMjQ7LDW8hmU+RRI69ckJIkhL7jfRJBm62R+TJVYq6h0jhBRslsivqenT2MF/7OyI70VmkFhWnPJaS6OyPkt43IycqR9EfWlH7JDQUUTuNhCHR7Ke9YcRp/5coVoQPrcvnyZURFRYmBZlLS0kR8MVLD29sbnp6e8PHxQUBAgCgn8YO8E3z79m3BGKeVc+bMkXuBZt06SA12F/F5Go0gR4C8HBalPZMPXKL8lQKhPAqF+f97KXFyNx6HQsoPsshJ/kmAp2TKkJLISpXvjyxNhMYcDVLOEO+lPDi8B5mamipkZx1YF9YpJCRErAy+vr5CZ9ZdWABhDGEYYTBhAOFz3g4nfMJelNCbkNCpUye5F034mvxIPi1/FM+zQCw0k5B9O0iEr5kRXkqhMJOVf9NXIHjtT7hmaymSoBzKETimkAuFpaF1dkwI9RcmIYaXv3BJXoGCuyIgk5WpefPmKCgoYK46SmX/RKoL69Sfl0WuFEl1HlmWJXE5z6WmTZvKJxxmxkIQ3AuU5APk6NICj4hRT6eITTEEzqWk55HHPjz3cxJhNF5cxeNT9kj2cRDTQjEkzpDtjyyCic5l5fEA7uSHFEefR5pPsahrb2B9QkICFHeJ51HunkdLIg0VLY0BFKdLwllVHp4dHyvst3QuEiiju21vA/+VZkiluIKt4I3RIfWXQ4QgKUxkni47LJWUP3PmjHo2RxVI+CebmKJP6EiFDVurxUgmExe5PHlnPAkn8w4QqW62NCVmYopozid5H0CI9RKE21ggJeAYEeMnfitOnRn5XCfgeJ+VTosWQU8MOc6ZE0cqnUm4fv165SrPBVHCfMI4TowUfmOfsIcdJh92kBWmUcP6GDt8EDZbzIffH5tx3/ewSFjw5LKk0MEFEkZenDBjgew7Yiog5brkt+QrknvJmhIp4Apw/A1bVpjhG/0v5d7Vrl07bNu2TelUSqUoz8uI3Z49OEtBAy+TdP1CqKtwHzvQUxxgTJs2TeX5gdq1a0ObSmCjh+jB+NuvRamL1+3ls77HCip1rTSdJP5eNnMizKndjMLoH42G4bthX+FzHS3UVVEC69evH3799VeKMXJZrlWKclUGAZ5jxoxB02ZNsNlxH74aagBHZyex986HlVTczyGmI58h4CjL2toa48ePFxsUPEotWrQoc0GT0/C2bduiY8eO4ISMcxLeoOFYhS6qm2EpoZG65jmbv+dPSyRZlt5QfVjvtX19AOFNL+aDFNI4m0eFc9Ho5ORkaGtrl5kAVp6DMOk88efEjLe++ZhclZwHTJHEHbs4YOCmLj2645fdvwnTK42zoXtaEHwNDQ3LXdZm5yad3/2r+gQmDsRnIF5KAldX6zdsgG/GG8F44Vzcu3eP2y1K6GPr2rVrK1zbnz59Or/LoaoJCPZ4kCZsjw9GECL79OmDj9q2wb+320C3/5fgPQO6Vrzh+fpcDqxXr16lbHBwgkZXm6okYJr0ECMrX5vraiJ1lArEjrEnzWuOqemiYj9spGd2ee478XkiPsJakmJ83qA05/8qXNurJFLiunXrhpo1a6LxB02wyHIFZpovgOHwYfjZ0hK2lH5u2rwZ5suWYv5ycyUlmjRpgl69eimlrFy3kwuoyOvXr19frm3RokVMwPZ3TYC57E6xVq+e6KzVDSaL/oEp82Zh8IhhWLjGAp/p9oX5ujVKBNjY2MDV1VWuzd3dXaTesm2biUQuZ8u28elSPmKr8a4vdog8GnJpcT1N1KHUuBbt0jSgWuGbzJh3mVhh2TYHBwdxjFa2jVcZnvPVlQBOLXdZWlqW2ZFxNYYVlm07fPgwAgMD5dr4OD5HeHLFFxM+O42DGtXhIkFaMQlcUjIzM0P37t1Ro0YNpZPjPJcVK7SOjo5ybU5OTqIAo0gAh97VlgAZIj4l8Pn4WFaO64ocuXG6zJtDbMqySnC7IgF8uptLVrJtq1evFuWqak+A4j4i4TNpltiJ8LPiNFFFwNGjRyWFyfedAFUny/joekkEuLi4KK0CfykCeFnkiu1flgBeFtl3/D8SsMbKykpOifv37ysRcPz4cVHKUiSA8wwNdR9/VTMBSh9Y8S4Nf2qnSICiBbDzVCRg9uzZTMC+94kAv6FDh8opwRsVHPjItnl4eEDxHNLKlStFXV+2javQ/M1SpZe+1KA4L4G7WDG57fSm/OUbXiqG0ewAFYOeYcN4fwZhvLkp2y4tftrxcltdlf/w+fPn4qNGxTCYU2m6nrRu3VqunT/EoiuZvw6TTZHpyuNNmEaNGsndP3fu3OJAq1N1JOAHDmyKheVtNP4OkE2crULRAW7fvl20EyyLy24a8p+/7WISFixYIMLt4t82bNhQYjXqXREgPq3j74mlX3AmSL8E1eOPIBXnuVT5OsVZpuLnOMeOHeN7vifwiYhYzhC5IpwlOXj1QXWdBmy/XWU/X+UqMZfKBw4cKAobHPlJlZe9h6tOu+7cuSN2dg0MDMSSyZUpmXvaSD+crq/xvl0k9BTCRa7qEPq+5T4t6ffF52WVV+f1P6zyLG30bsU4AAAAAElFTkSuQmCC">',showdown:'<img width="20" height="20" align="absmiddle" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAECtaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzA2NyA3OS4xNTc3NDcsIDIwMTUvMDMvMzAtMjM6NDA6NDIgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIgogICAgICAgICAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICAgICAgICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgICAgICAgICB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIgogICAgICAgICAgICB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIj4KICAgICAgICAgPHhtcDpDcmVhdG9yVG9vbD5BZG9iZSBQaG90b3Nob3AgQ0MgMjAxNSAoV2luZG93cyk8L3htcDpDcmVhdG9yVG9vbD4KICAgICAgICAgPHhtcDpDcmVhdGVEYXRlPjIwMTUtMDEtMTVUMjE6MDE6MTlaPC94bXA6Q3JlYXRlRGF0ZT4KICAgICAgICAgPHhtcDpNZXRhZGF0YURhdGU+MjAxNy0xMC0yNFQxMzozMTozMCswMTowMDwveG1wOk1ldGFkYXRhRGF0ZT4KICAgICAgICAgPHhtcDpNb2RpZnlEYXRlPjIwMTctMTAtMjRUMTM6MzE6MzArMDE6MDA8L3htcDpNb2RpZnlEYXRlPgogICAgICAgICA8cGhvdG9zaG9wOkNvbG9yTW9kZT4zPC9waG90b3Nob3A6Q29sb3JNb2RlPgogICAgICAgICA8cGhvdG9zaG9wOklDQ1Byb2ZpbGU+c1JHQiBJRUM2MTk2Ni0yLjE8L3Bob3Rvc2hvcDpJQ0NQcm9maWxlPgogICAgICAgICA8cGhvdG9zaG9wOlRleHRMYXllcnM+CiAgICAgICAgICAgIDxyZGY6QmFnPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHBob3Rvc2hvcDpMYXllck5hbWU+UyAtPC9waG90b3Nob3A6TGF5ZXJOYW1lPgogICAgICAgICAgICAgICAgICA8cGhvdG9zaG9wOkxheWVyVGV4dD5TIC08L3Bob3Rvc2hvcDpMYXllclRleHQ+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICA8L3JkZjpCYWc+CiAgICAgICAgIDwvcGhvdG9zaG9wOlRleHRMYXllcnM+CiAgICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2UvcG5nPC9kYzpmb3JtYXQ+CiAgICAgICAgIDx4bXBNTTpJbnN0YW5jZUlEPnhtcC5paWQ6N2NkMzQxNzctOWYyZi0yNDRiLWEyYjQtMzU1MzJkY2Y1MWJiPC94bXBNTTpJbnN0YW5jZUlEPgogICAgICAgICA8eG1wTU06RG9jdW1lbnRJRD5hZG9iZTpkb2NpZDpwaG90b3Nob3A6M2E1YzgxYmYtYjhiNy0xMWU3LTk0NDktYTQ2MzdlZjJkNjMzPC94bXBNTTpEb2N1bWVudElEPgogICAgICAgICA8eG1wTU06T3JpZ2luYWxEb2N1bWVudElEPnhtcC5kaWQ6NjBDNUFFNjVGNjlDRTQxMTk0NUE4NTVFM0JDQTdFRUI8L3htcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOkhpc3Rvcnk+CiAgICAgICAgICAgIDxyZGY6U2VxPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5jcmVhdGVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6NjBDNUFFNjVGNjlDRTQxMTk0NUE4NTVFM0JDQTdFRUI8L3N0RXZ0Omluc3RhbmNlSUQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDp3aGVuPjIwMTUtMDEtMTVUMjE6MDE6MTlaPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgQ1M2IChXaW5kb3dzKTwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPnNhdmVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6ODZjNjBkMGQtOGY0Yy01ZTRlLWEwMjQtODI4ZWQyNTIwZDc3PC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE3LTEwLTI0VDEzOjMxOjMwKzAxOjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgQ0MgMjAxNSAoV2luZG93cyk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpjaGFuZ2VkPi88L3N0RXZ0OmNoYW5nZWQ+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5jb252ZXJ0ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnBhcmFtZXRlcnM+ZnJvbSBhcHBsaWNhdGlvbi92bmQuYWRvYmUucGhvdG9zaG9wIHRvIGltYWdlL3BuZzwvc3RFdnQ6cGFyYW1ldGVycz4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPmRlcml2ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnBhcmFtZXRlcnM+Y29udmVydGVkIGZyb20gYXBwbGljYXRpb24vdm5kLmFkb2JlLnBob3Rvc2hvcCB0byBpbWFnZS9wbmc8L3N0RXZ0OnBhcmFtZXRlcnM+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjdjZDM0MTc3LTlmMmYtMjQ0Yi1hMmI0LTM1NTMyZGNmNTFiYjwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAxNy0xMC0yNFQxMzozMTozMCswMTowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKFdpbmRvd3MpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgPC9yZGY6U2VxPgogICAgICAgICA8L3htcE1NOkhpc3Rvcnk+CiAgICAgICAgIDx4bXBNTTpEZXJpdmVkRnJvbSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgIDxzdFJlZjppbnN0YW5jZUlEPnhtcC5paWQ6ODZjNjBkMGQtOGY0Yy01ZTRlLWEwMjQtODI4ZWQyNTIwZDc3PC9zdFJlZjppbnN0YW5jZUlEPgogICAgICAgICAgICA8c3RSZWY6ZG9jdW1lbnRJRD54bXAuZGlkOjYwQzVBRTY1RjY5Q0U0MTE5NDVBODU1RTNCQ0E3RUVCPC9zdFJlZjpkb2N1bWVudElEPgogICAgICAgICAgICA8c3RSZWY6b3JpZ2luYWxEb2N1bWVudElEPnhtcC5kaWQ6NjBDNUFFNjVGNjlDRTQxMTk0NUE4NTVFM0JDQTdFRUI8L3N0UmVmOm9yaWdpbmFsRG9jdW1lbnRJRD4KICAgICAgICAgPC94bXBNTTpEZXJpdmVkRnJvbT4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgICAgPHRpZmY6WFJlc29sdXRpb24+NzIwMDAwLzEwMDAwPC90aWZmOlhSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpZUmVzb2x1dGlvbj43MjAwMDAvMTAwMDA8L3RpZmY6WVJlc29sdXRpb24+CiAgICAgICAgIDx0aWZmOlJlc29sdXRpb25Vbml0PjI8L3RpZmY6UmVzb2x1dGlvblVuaXQ+CiAgICAgICAgIDxleGlmOkNvbG9yU3BhY2U+MTwvZXhpZjpDb2xvclNwYWNlPgogICAgICAgICA8ZXhpZjpQaXhlbFhEaW1lbnNpb24+NjQ8L2V4aWY6UGl4ZWxYRGltZW5zaW9uPgogICAgICAgICA8ZXhpZjpQaXhlbFlEaW1lbnNpb24+NjQ8L2V4aWY6UGl4ZWxZRGltZW5zaW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0idyI/Pse7bzcAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAA1JJREFUeNrsm1+OmlAUhz+aeS9dwZggJn1AnRUMO6jpBgZXULuC2hWUWUGZBTSxKyiuoA4mfUBMnB04K5g+9DihRBHlyh/lJLwIXLgf99xzzu9etZeXFy7Z3nDh1gBoAFy4XeVtQNO0zNcapmUDfUBPnFoBfhQGq6IBaHmjwD4Ahmk5wAD4kKG5J8CNwsAFaHe6DvA9cc0wCgOv8gDka3vA9RHNPgo0D7hNnJtGYWBXxgV2dH4MfMnRRA+Y1WIO2NJ5F/ikoKm3tYsChmkNFHW+fmHQMC1dfHaXPQP3wM1yMdc2B/AOGALTWobBmI1Shu0UGCwX83XyRBQGawHntTtdG5gUNfxVu4CTNqNv6/wWGL7kCc+1AmCYVisl3I2ydD4GYZUCs7IjoLXrxHIx9w9tLAqDCfBwDrXAY457x+cAoCfuwRGjYFUnAGk+PsjR7s8Dn1VeLWCYVlpDw+VivjVHSHt+u9PVJbzGzZXQWTkAkz0V31fATUaEsjVJlQBs4FeGcteLgzgbAALBA+4y3voAeJL8nA0AHfClnM1qm1HhnYUidCSE+KzvSSJUTwAxCOMcpfETMFYpfRUKIAbCFhC3OTJJJwqDWS0BxED0JZ4Pjix1P2+E0loCSMBwyK4S/xc1ojBwag8gMU84cvTKGgmlAYhngu1O9xAXuVE5J1QCQCz3bwHuHvdQui5QKQAxEO6eEKpsFCgTRSXkvdoxSlBMCxhJJbgrrbZRtHCiShN0pRB6PeQ3ckBw2K0oKXMBVYJIP+Nvh9qulFivGoBt1lLQxowT2ykBXCfnhZIglgYACWmqXQv+baioBYCeiCQHm+QEg1O7RhF7hO4OhSAhcJKSFU7qBGADwZeqMMuXn6TUBw8qlaMrirNb4LdhWlP+SWD+cjFfxTpuS2GUpik+o3jFSEkqbJiWn0P0OMSGqlWiOu0TvD+FRHZKAE+oW+cfRmEwqlsesJJEJs8y91QqP+9UL6lqEtz2gpuNEY5sm9sIHln2DRa2aFKGJtiXkZEMiWtgVvRKUSUFkSKt2S7fAGgAXLYpmQQXf36MUChTZdUa2u8/rkvPA6Tz30r4eH3ybcBS5gJ6SaNXb+aABkA1AMxKenclBZLW/He4cYEGwEXb3wEASelexk6LIIIAAAAASUVORK5CYII=">'},I.Converter=function(g){"use strict";function C(g,C){if(C=C||null,I.helper.isString(g)){if(g=I.helper.stdExtName(g),C=g,I.extensions[g])return console.warn("DEPRECATION WARNING: "+g+" is an old extension that uses a deprecated loading method.Please inform the developer that the extension should be updated!"),void function(g,C){"function"==typeof g&&(g=g(new I.Converter));I.helper.isArray(g)||(g=[g]);var e=A(g,C);if(!e.valid)throw Error(e.error);for(var r=0;r<g.length;++r)switch(g[r].type){case"lang":s.push(g[r]);break;case"output":i.push(g[r]);break;default:throw Error("Extension loader error: Type unrecognized!!!")}}(I.extensions[g],g);if(I.helper.isUndefined(r[g]))throw Error('Extension "'+g+'" could not be loaded. It was either not found or is not a valid extension.');g=r[g]}"function"==typeof g&&(g=g()),I.helper.isArray(g)||(g=[g]);var t=A(g,C);if(!t.valid)throw Error(t.error);for(var a=0;a<g.length;++a){switch(g[a].type){case"lang":s.push(g[a]);break;case"output":i.push(g[a])}if(g[a].hasOwnProperty("listeners"))for(var n in g[a].listeners)g[a].listeners.hasOwnProperty(n)&&e(n,g[a].listeners[n])}}function e(g,A){if(!I.helper.isString(g))throw Error("Invalid argument in converter.listen() method: name must be a string, but "+typeof g+" given");if("function"!=typeof A)throw Error("Invalid argument in converter.listen() method: callback must be a function, but "+typeof A+" given");l.hasOwnProperty(g)||(l[g]=[]),l[g].push(A)}var o={},s=[],i=[],l={},c=a,u={parsed:{},raw:"",format:""};!function(){g=g||{};for(var A in t)t.hasOwnProperty(A)&&(o[A]=t[A]);if("object"!=typeof g)throw Error("Converter expects the passed parameter to be an object, but "+typeof g+" was passed instead.");for(var e in g)g.hasOwnProperty(e)&&(o[e]=g[e]);o.extensions&&I.helper.forEach(o.extensions,C)}(),this._dispatch=function(g,A,C,I){if(l.hasOwnProperty(g))for(var e=0;e<l[g].length;++e){var r=l[g][e](g,A,this,C,I);r&&void 0!==r&&(A=r)}return A},this.listen=function(g,A){return e(g,A),this},this.makeHtml=function(g){if(!g)return g;var A={gHtmlBlocks:[],gHtmlMdBlocks:[],gHtmlSpans:[],gUrls:{},gTitles:{},gDimensions:{},gListLevel:0,hashLinkCounts:{},langExtensions:s,outputModifiers:i,converter:this,ghCodeBlocks:[],metadata:{parsed:{},raw:"",format:""}};return g=g.replace(/¨/g,"¨T"),g=g.replace(/\$/g,"¨D"),g=g.replace(/\r\n/g,"\n"),g=g.replace(/\r/g,"\n"),g=g.replace(/\u00A0/g," "),o.smartIndentationFix&&(g=function(g){var A=g.match(/^\s*/)[0].length,C=new RegExp("^\\s{0,"+A+"}","gm");return g.replace(C,"")}(g)),g="\n\n"+g+"\n\n",g=I.subParser("detab")(g,o,A),g=g.replace(/^[ \t]+$/gm,""),I.helper.forEach(s,function(C){g=I.subParser("runExtension")(C,g,o,A)}),g=I.subParser("metadata")(g,o,A),g=I.subParser("hashPreCodeTags")(g,o,A),g=I.subParser("githubCodeBlocks")(g,o,A),g=I.subParser("hashHTMLBlocks")(g,o,A),g=I.subParser("hashCodeTags")(g,o,A),g=I.subParser("stripLinkDefinitions")(g,o,A),g=I.subParser("blockGamut")(g,o,A),g=I.subParser("unhashHTMLSpans")(g,o,A),g=I.subParser("unescapeSpecialChars")(g,o,A),g=g.replace(/¨D/g,"$$"),g=g.replace(/¨T/g,"¨"),g=I.subParser("completeHTMLDocument")(g,o,A),I.helper.forEach(i,function(C){g=I.subParser("runExtension")(C,g,o,A)}),u=A.metadata,g},this.setOption=function(g,A){o[g]=A},this.getOption=function(g){return o[g]},this.getOptions=function(){return o},this.addExtension=function(g,A){C(g,A=A||null)},this.useExtension=function(g){C(g)},this.setFlavor=function(g){if(!n.hasOwnProperty(g))throw Error(g+" flavor was not found");var A=n[g];c=g;for(var C in A)A.hasOwnProperty(C)&&(o[C]=A[C])},this.getFlavor=function(){return c},this.removeExtension=function(g){I.helper.isArray(g)||(g=[g]);for(var A=0;A<g.length;++A){for(var C=g[A],e=0;e<s.length;++e)s[e]===C&&s[e].splice(e,1);for(;0<i.length;++e)i[0]===C&&i[0].splice(e,1)}},this.getAllExtensions=function(){return{language:s,output:i}},this.getMetadata=function(g){return g?u.raw:u.parsed},this.getMetadataFormat=function(){return u.format},this._setMetadataPair=function(g,A){u.parsed[g]=A},this._setMetadataFormat=function(g){u.format=g},this._setMetadataRaw=function(g){u.raw=g}},I.subParser("anchors",function(g,A,C){"use strict";var e=function(g,e,r,t,a,n,o){if(I.helper.isUndefined(o)&&(o=""),r=r.toLowerCase(),g.search(/\(<?\s*>? ?(['"].*['"])?\)$/m)>-1)t="";else if(!t){if(r||(r=e.toLowerCase().replace(/ ?\n/g," ")),t="#"+r,I.helper.isUndefined(C.gUrls[r]))return g;t=C.gUrls[r],I.helper.isUndefined(C.gTitles[r])||(o=C.gTitles[r])}var s='<a href="'+(t=t.replace(I.helper.regexes.asteriskDashAndColon,I.helper.escapeCharactersCallback))+'"';return""!==o&&null!==o&&(s+=' title="'+(o=(o=o.replace(/"/g,""")).replace(I.helper.regexes.asteriskDashAndColon,I.helper.escapeCharactersCallback))+'"'),A.openLinksInNewWindow&&!/^#/.test(t)&&(s+=' target="¨E95Eblank"'),s+=">"+e+"</a>"};return g=(g=C.converter._dispatch("anchors.before",g,A,C)).replace(/\[((?:\[[^\]]*]|[^\[\]])*)] ?(?:\n *)?\[(.*?)]()()()()/g,e),g=g.replace(/\[((?:\[[^\]]*]|[^\[\]])*)]()[ \t]*\([ \t]?<([^>]*)>(?:[ \t]*((["'])([^"]*?)\5))?[ \t]?\)/g,e),g=g.replace(/\[((?:\[[^\]]*]|[^\[\]])*)]()[ \t]*\([ \t]?<?([\S]+?(?:\([\S]*?\)[\S]*?)?)>?(?:[ \t]*((["'])([^"]*?)\5))?[ \t]?\)/g,e),g=g.replace(/\[([^\[\]]+)]()()()()()/g,e),A.ghMentions&&(g=g.replace(/(^|\s)(\\)?(@([a-z\d\-]+))(?=[.!?;,[\]()]|\s|$)/gim,function(g,C,e,r,t){if("\\"===e)return C+r;if(!I.helper.isString(A.ghMentionsLink))throw new Error("ghMentionsLink option must be a string");var a=A.ghMentionsLink.replace(/\{u}/g,t),n="";return A.openLinksInNewWindow&&(n=' target="¨E95Eblank"'),C+'<a href="'+a+'"'+n+">"+r+"</a>"})),g=C.converter._dispatch("anchors.after",g,A,C)});var s=/([*~_]+|\b)(((https?|ftp|dict):\/\/|www\.)[^'">\s]+?\.[^'">\s]+?)()(\1)?(?=\s|$)(?!["<>])/gi,i=/([*~_]+|\b)(((https?|ftp|dict):\/\/|www\.)[^'">\s]+\.[^'">\s]+?)([.!?,()\[\]])?(\1)?(?=\s|$)(?!["<>])/gi,l=/()<(((https?|ftp|dict):\/\/|www\.)[^'">\s]+)()>()/gi,c=/(^|\s)(?:mailto:)?([A-Za-z0-9!#$%&'*+-/=?^_`{|}~.]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)(?=$|\s)/gim,u=/<()(?:mailto:)?([-.\w]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,d=function(g){"use strict";return function(A,C,e,r,t,a,n){var o=e=e.replace(I.helper.regexes.asteriskDashAndColon,I.helper.escapeCharactersCallback),s="",i="",l=C||"",c=n||"";return/^www\./i.test(e)&&(e=e.replace(/^www\./i,"http://www.")),g.excludeTrailingPunctuationFromURLs&&a&&(s=a),g.openLinksInNewWindow&&(i=' target="¨E95Eblank"'),l+'<a href="'+e+'"'+i+">"+o+"</a>"+s+c}},p=function(g,A){"use strict";return function(C,e,r){var t="mailto:";return e=e||"",r=I.subParser("unescapeSpecialChars")(r,g,A),g.encodeEmails?(t=I.helper.encodeEmailAddress(t+r),r=I.helper.encodeEmailAddress(r)):t+=r,e+'<a href="'+t+'">'+r+"</a>"}};I.subParser("autoLinks",function(g,A,C){"use strict";return g=C.converter._dispatch("autoLinks.before",g,A,C),g=g.replace(l,d(A)),g=g.replace(u,p(A,C)),g=C.converter._dispatch("autoLinks.after",g,A,C)}),I.subParser("simplifiedAutoLinks",function(g,A,C){"use strict";return A.simplifiedAutoLink?(g=C.converter._dispatch("simplifiedAutoLinks.before",g,A,C),g=A.excludeTrailingPunctuationFromURLs?g.replace(i,d(A)):g.replace(s,d(A)),g=g.replace(c,p(A,C)),g=C.converter._dispatch("simplifiedAutoLinks.after",g,A,C)):g}),I.subParser("blockGamut",function(g,A,C){"use strict";return g=C.converter._dispatch("blockGamut.before",g,A,C),g=I.subParser("blockQuotes")(g,A,C),g=I.subParser("headers")(g,A,C),g=I.subParser("horizontalRule")(g,A,C),g=I.subParser("lists")(g,A,C),g=I.subParser("codeBlocks")(g,A,C),g=I.subParser("tables")(g,A,C),g=I.subParser("hashHTMLBlocks")(g,A,C),g=I.subParser("paragraphs")(g,A,C),g=C.converter._dispatch("blockGamut.after",g,A,C)}),I.subParser("blockQuotes",function(g,A,C){"use strict";g=C.converter._dispatch("blockQuotes.before",g,A,C),g+="\n\n";var e=/(^ {0,3}>[ \t]?.+\n(.+\n)*\n*)+/gm;return A.splitAdjacentBlockquotes&&(e=/^ {0,3}>[\s\S]*?(?:\n\n)/gm),g=g.replace(e,function(g){return g=g.replace(/^[ \t]*>[ \t]?/gm,""),g=g.replace(/¨0/g,""),g=g.replace(/^[ \t]+$/gm,""),g=I.subParser("githubCodeBlocks")(g,A,C),g=I.subParser("blockGamut")(g,A,C),g=g.replace(/(^|\n)/g,"$1 "),g=g.replace(/(\s*<pre>[^\r]+?<\/pre>)/gm,function(g,A){var C=A;return C=C.replace(/^ /gm,"¨0"),C=C.replace(/¨0/g,"")}),I.subParser("hashBlock")("<blockquote>\n"+g+"\n</blockquote>",A,C)}),g=C.converter._dispatch("blockQuotes.after",g,A,C)}),I.subParser("codeBlocks",function(g,A,C){"use strict";g=C.converter._dispatch("codeBlocks.before",g,A,C);return g=(g+="¨0").replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=¨0))/g,function(g,e,r){var t=e,a=r,n="\n";return t=I.subParser("outdent")(t,A,C),t=I.subParser("encodeCode")(t,A,C),t=I.subParser("detab")(t,A,C),t=t.replace(/^\n+/g,""),t=t.replace(/\n+$/g,""),A.omitExtraWLInCodeBlocks&&(n=""),t="<pre><code>"+t+n+"</code></pre>",I.subParser("hashBlock")(t,A,C)+a}),g=g.replace(/¨0/,""),g=C.converter._dispatch("codeBlocks.after",g,A,C)}),I.subParser("codeSpans",function(g,A,C){"use strict";return void 0===(g=C.converter._dispatch("codeSpans.before",g,A,C))&&(g=""),g=g.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,function(g,e,r,t){var a=t;return a=a.replace(/^([ \t]*)/g,""),a=a.replace(/[ \t]*$/g,""),a=I.subParser("encodeCode")(a,A,C),a=e+"<code>"+a+"</code>",a=I.subParser("hashHTMLSpans")(a,A,C)}),g=C.converter._dispatch("codeSpans.after",g,A,C)}),I.subParser("completeHTMLDocument",function(g,A,C){"use strict";if(!A.completeHTMLDocument)return g;g=C.converter._dispatch("completeHTMLDocument.before",g,A,C);var I="html",e="<!DOCTYPE HTML>\n",r="",t='<meta charset="utf-8">\n',a="",n="";void 0!==C.metadata.parsed.doctype&&(e="<!DOCTYPE "+C.metadata.parsed.doctype+">\n","html"!==(I=C.metadata.parsed.doctype.toString().toLowerCase())&&"html5"!==I||(t='<meta charset="utf-8">'));for(var o in C.metadata.parsed)if(C.metadata.parsed.hasOwnProperty(o))switch(o.toLowerCase()){case"doctype":break;case"title":r="<title>"+C.metadata.parsed.title+"</title>\n";break;case"charset":t="html"===I||"html5"===I?'<meta charset="'+C.metadata.parsed.charset+'">\n':'<meta name="charset" content="'+C.metadata.parsed.charset+'">\n';break;case"language":case"lang":a=' lang="'+C.metadata.parsed[o]+'"',n+='<meta name="'+o+'" content="'+C.metadata.parsed[o]+'">\n';break;default:n+='<meta name="'+o+'" content="'+C.metadata.parsed[o]+'">\n'}return g=e+"<html"+a+">\n<head>\n"+r+t+n+"</head>\n<body>\n"+g.trim()+"\n</body>\n</html>",g=C.converter._dispatch("completeHTMLDocument.after",g,A,C)}),I.subParser("detab",function(g,A,C){"use strict";return g=C.converter._dispatch("detab.before",g,A,C),g=g.replace(/\t(?=\t)/g," "),g=g.replace(/\t/g,"¨A¨B"),g=g.replace(/¨B(.+?)¨A/g,function(g,A){for(var C=A,I=4-C.length%4,e=0;e<I;e++)C+=" ";return C}),g=g.replace(/¨A/g," "),g=g.replace(/¨B/g,""),g=C.converter._dispatch("detab.after",g,A,C)}),I.subParser("ellipsis",function(g,A,C){"use strict";return g=C.converter._dispatch("ellipsis.before",g,A,C),g=g.replace(/\.\.\./g,"…"),g=C.converter._dispatch("ellipsis.after",g,A,C)}),I.subParser("emoji",function(g,A,C){"use strict";if(!A.emoji)return g;return g=(g=C.converter._dispatch("emoji.before",g,A,C)).replace(/:([\S]+?):/g,function(g,A){return I.helper.emojis.hasOwnProperty(A)?I.helper.emojis[A]:g}),g=C.converter._dispatch("emoji.after",g,A,C)}),I.subParser("encodeAmpsAndAngles",function(g,A,C){"use strict";return g=C.converter._dispatch("encodeAmpsAndAngles.before",g,A,C),g=g.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&"),g=g.replace(/<(?![a-z\/?$!])/gi,"<"),g=g.replace(/</g,"<"),g=g.replace(/>/g,">"),g=C.converter._dispatch("encodeAmpsAndAngles.after",g,A,C)}),I.subParser("encodeBackslashEscapes",function(g,A,C){"use strict";return g=C.converter._dispatch("encodeBackslashEscapes.before",g,A,C),g=g.replace(/\\(\\)/g,I.helper.escapeCharactersCallback),g=g.replace(/\\([`*_{}\[\]()>#+.!~=|-])/g,I.helper.escapeCharactersCallback),g=C.converter._dispatch("encodeBackslashEscapes.after",g,A,C)}),I.subParser("encodeCode",function(g,A,C){"use strict";return g=C.converter._dispatch("encodeCode.before",g,A,C),g=g.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/([*_{}\[\]\\=~-])/g,I.helper.escapeCharactersCallback),g=C.converter._dispatch("encodeCode.after",g,A,C)}),I.subParser("escapeSpecialCharsWithinTagAttributes",function(g,A,C){"use strict";return g=(g=C.converter._dispatch("escapeSpecialCharsWithinTagAttributes.before",g,A,C)).replace(/<\/?[a-z\d_:-]+(?:[\s]+[\s\S]+?)?>/gi,function(g){return g.replace(/(.)<\/?code>(?=.)/g,"$1`").replace(/([\\`*_~=|])/g,I.helper.escapeCharactersCallback)}),g=g.replace(/<!(--(?:(?:[^>-]|-[^>])(?:[^-]|-[^-])*)--)>/gi,function(g){return g.replace(/([\\`*_~=|])/g,I.helper.escapeCharactersCallback)}),g=C.converter._dispatch("escapeSpecialCharsWithinTagAttributes.after",g,A,C)}),I.subParser("githubCodeBlocks",function(g,A,C){"use strict";return A.ghCodeBlocks?(g=C.converter._dispatch("githubCodeBlocks.before",g,A,C),g+="¨0",g=g.replace(/(?:^|\n)(```+|~~~+)([^\s`~]*)\n([\s\S]*?)\n\1/g,function(g,e,r,t){var a=A.omitExtraWLInCodeBlocks?"":"\n";return t=I.subParser("encodeCode")(t,A,C),t=I.subParser("detab")(t,A,C),t=t.replace(/^\n+/g,""),t=t.replace(/\n+$/g,""),t="<pre><code"+(r?' class="'+r+" language-"+r+'"':"")+">"+t+a+"</code></pre>",t=I.subParser("hashBlock")(t,A,C),"\n\n¨G"+(C.ghCodeBlocks.push({text:g,codeblock:t})-1)+"G\n\n"}),g=g.replace(/¨0/,""),C.converter._dispatch("githubCodeBlocks.after",g,A,C)):g}),I.subParser("hashBlock",function(g,A,C){"use strict";return g=C.converter._dispatch("hashBlock.before",g,A,C),g=g.replace(/(^\n+|\n+$)/g,""),g="\n\n¨K"+(C.gHtmlBlocks.push(g)-1)+"K\n\n",g=C.converter._dispatch("hashBlock.after",g,A,C)}),I.subParser("hashCodeTags",function(g,A,C){"use strict";g=C.converter._dispatch("hashCodeTags.before",g,A,C);return g=I.helper.replaceRecursiveRegExp(g,function(g,e,r,t){var a=r+I.subParser("encodeCode")(e,A,C)+t;return"¨C"+(C.gHtmlSpans.push(a)-1)+"C"},"<code\\b[^>]*>","</code>","gim"),g=C.converter._dispatch("hashCodeTags.after",g,A,C)}),I.subParser("hashElement",function(g,A,C){"use strict";return function(g,A){var I=A;return I=I.replace(/\n\n/g,"\n"),I=I.replace(/^\n/,""),I=I.replace(/\n+$/g,""),I="\n\n¨K"+(C.gHtmlBlocks.push(I)-1)+"K\n\n"}}),I.subParser("hashHTMLBlocks",function(g,A,C){"use strict";g=C.converter._dispatch("hashHTMLBlocks.before",g,A,C);var e=["pre","div","h1","h2","h3","h4","h5","h6","blockquote","table","dl","ol","ul","script","noscript","form","fieldset","iframe","math","style","section","header","footer","nav","article","aside","address","audio","canvas","figure","hgroup","output","video","p"],r=function(g,A,I,e){var r=g;return-1!==I.search(/\bmarkdown\b/)&&(r=I+C.converter.makeHtml(A)+e),"\n\n¨K"+(C.gHtmlBlocks.push(r)-1)+"K\n\n"};A.backslashEscapesHTMLTags&&(g=g.replace(/\\<(\/?[^>]+?)>/g,function(g,A){return"<"+A+">"}));for(var t=0;t<e.length;++t)for(var a,n=new RegExp("^ {0,3}(<"+e[t]+"\\b[^>]*>)","im"),o="<"+e[t]+"\\b[^>]*>",s="</"+e[t]+">";-1!==(a=I.helper.regexIndexOf(g,n));){var i=I.helper.splitAtIndex(g,a),l=I.helper.replaceRecursiveRegExp(i[1],r,o,s,"im");if(l===i[1])break;g=i[0].concat(l)}return g=g.replace(/(\n {0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,I.subParser("hashElement")(g,A,C)),g=I.helper.replaceRecursiveRegExp(g,function(g){return"\n\n¨K"+(C.gHtmlBlocks.push(g)-1)+"K\n\n"},"^ {0,3}\x3c!--","--\x3e","gm"),g=g.replace(/(?:\n\n)( {0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,I.subParser("hashElement")(g,A,C)),g=C.converter._dispatch("hashHTMLBlocks.after",g,A,C)}),I.subParser("hashHTMLSpans",function(g,A,C){"use strict";function I(g){return"¨C"+(C.gHtmlSpans.push(g)-1)+"C"}return g=C.converter._dispatch("hashHTMLSpans.before",g,A,C),g=g.replace(/<[^>]+?\/>/gi,function(g){return I(g)}),g=g.replace(/<([^>]+?)>[\s\S]*?<\/\1>/g,function(g){return I(g)}),g=g.replace(/<([^>]+?)\s[^>]+?>[\s\S]*?<\/\1>/g,function(g){return I(g)}),g=g.replace(/<[^>]+?>/gi,function(g){return I(g)}),g=C.converter._dispatch("hashHTMLSpans.after",g,A,C)}),I.subParser("unhashHTMLSpans",function(g,A,C){"use strict";g=C.converter._dispatch("unhashHTMLSpans.before",g,A,C);for(var I=0;I<C.gHtmlSpans.length;++I){for(var e=C.gHtmlSpans[I],r=0;/¨C(\d+)C/.test(e);){var t=RegExp.$1;if(e=e.replace("¨C"+t+"C",C.gHtmlSpans[t]),10===r){console.error("maximum nesting of 10 spans reached!!!");break}++r}g=g.replace("¨C"+I+"C",e)}return g=C.converter._dispatch("unhashHTMLSpans.after",g,A,C)}),I.subParser("hashPreCodeTags",function(g,A,C){"use strict";g=C.converter._dispatch("hashPreCodeTags.before",g,A,C);return g=I.helper.replaceRecursiveRegExp(g,function(g,e,r,t){var a=r+I.subParser("encodeCode")(e,A,C)+t;return"\n\n¨G"+(C.ghCodeBlocks.push({text:g,codeblock:a})-1)+"G\n\n"},"^ {0,3}<pre\\b[^>]*>\\s*<code\\b[^>]*>","^ {0,3}</code>\\s*</pre>","gim"),g=C.converter._dispatch("hashPreCodeTags.after",g,A,C)}),I.subParser("headers",function(g,A,C){"use strict";function e(g){var e,r;if(A.customizedHeaderId){var t=g.match(/\{([^{]+?)}\s*$/);t&&t[1]&&(g=t[1])}return e=g,r=I.helper.isString(A.prefixHeaderId)?A.prefixHeaderId:!0===A.prefixHeaderId?"section-":"",A.rawPrefixHeaderId||(e=r+e),e=A.ghCompatibleHeaderId?e.replace(/ /g,"-").replace(/&/g,"").replace(/¨T/g,"").replace(/¨D/g,"").replace(/[&+$,\/:;=?@"#{}|^¨~\[\]`\\*)(%.!'<>]/g,"").toLowerCase():A.rawHeaderId?e.replace(/ /g,"-").replace(/&/g,"&").replace(/¨T/g,"¨").replace(/¨D/g,"$").replace(/["']/g,"-").toLowerCase():e.replace(/[^\w]/g,"").toLowerCase(),A.rawPrefixHeaderId&&(e=r+e),C.hashLinkCounts[e]?e=e+"-"+C.hashLinkCounts[e]++:C.hashLinkCounts[e]=1,e}g=C.converter._dispatch("headers.before",g,A,C);var r=isNaN(parseInt(A.headerLevelStart))?1:parseInt(A.headerLevelStart),t=A.smoothLivePreview?/^(.+)[ \t]*\n={2,}[ \t]*\n+/gm:/^(.+)[ \t]*\n=+[ \t]*\n+/gm,a=A.smoothLivePreview?/^(.+)[ \t]*\n-{2,}[ \t]*\n+/gm:/^(.+)[ \t]*\n-+[ \t]*\n+/gm;g=(g=g.replace(t,function(g,t){var a=I.subParser("spanGamut")(t,A,C),n=A.noHeaderId?"":' id="'+e(t)+'"',o="<h"+r+n+">"+a+"</h"+r+">";return I.subParser("hashBlock")(o,A,C)})).replace(a,function(g,t){var a=I.subParser("spanGamut")(t,A,C),n=A.noHeaderId?"":' id="'+e(t)+'"',o=r+1,s="<h"+o+n+">"+a+"</h"+o+">";return I.subParser("hashBlock")(s,A,C)});var n=A.requireSpaceBeforeHeadingText?/^(#{1,6})[ \t]+(.+?)[ \t]*#*\n+/gm:/^(#{1,6})[ \t]*(.+?)[ \t]*#*\n+/gm;return g=g.replace(n,function(g,t,a){var n=a;A.customizedHeaderId&&(n=a.replace(/\s?\{([^{]+?)}\s*$/,""));var o=I.subParser("spanGamut")(n,A,C),s=A.noHeaderId?"":' id="'+e(a)+'"',i=r-1+t.length,l="<h"+i+s+">"+o+"</h"+i+">";return I.subParser("hashBlock")(l,A,C)}),g=C.converter._dispatch("headers.after",g,A,C)}),I.subParser("horizontalRule",function(g,A,C){"use strict";g=C.converter._dispatch("horizontalRule.before",g,A,C);var e=I.subParser("hashBlock")("<hr />",A,C);return g=g.replace(/^ {0,2}( ?-){3,}[ \t]*$/gm,e),g=g.replace(/^ {0,2}( ?\*){3,}[ \t]*$/gm,e),g=g.replace(/^ {0,2}( ?_){3,}[ \t]*$/gm,e),g=C.converter._dispatch("horizontalRule.after",g,A,C)}),I.subParser("images",function(g,A,C){"use strict";function e(g,A,e,r,t,a,n,o){var s=C.gUrls,i=C.gTitles,l=C.gDimensions;if(e=e.toLowerCase(),o||(o=""),g.search(/\(<?\s*>? ?(['"].*['"])?\)$/m)>-1)r="";else if(""===r||null===r){if(""!==e&&null!==e||(e=A.toLowerCase().replace(/ ?\n/g," ")),r="#"+e,I.helper.isUndefined(s[e]))return g;r=s[e],I.helper.isUndefined(i[e])||(o=i[e]),I.helper.isUndefined(l[e])||(t=l[e].width,a=l[e].height)}A=A.replace(/"/g,""").replace(I.helper.regexes.asteriskDashAndColon,I.helper.escapeCharactersCallback);var c='<img src="'+(r=r.replace(I.helper.regexes.asteriskDashAndColon,I.helper.escapeCharactersCallback))+'" alt="'+A+'"';return o&&(c+=' title="'+(o=o.replace(/"/g,""").replace(I.helper.regexes.asteriskDashAndColon,I.helper.escapeCharactersCallback))+'"'),t&&a&&(c+=' width="'+(t="*"===t?"auto":t)+'"',c+=' height="'+(a="*"===a?"auto":a)+'"'),c+=" />"}return g=(g=C.converter._dispatch("images.before",g,A,C)).replace(/!\[([^\]]*?)] ?(?:\n *)?\[([\s\S]*?)]()()()()()/g,e),g=g.replace(/!\[([^\]]*?)][ \t]*()\([ \t]?<?(data:.+?\/.+?;base64,[A-Za-z0-9+/=\n]+?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(["'])([^"]*?)\6)?[ \t]?\)/g,function(g,A,C,I,r,t,a,n){return I=I.replace(/\s/g,""),e(g,A,C,I,r,t,0,n)}),g=g.replace(/!\[([^\]]*?)][ \t]*()\([ \t]?<([^>]*)>(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(?:(["'])([^"]*?)\6))?[ \t]?\)/g,e),g=g.replace(/!\[([^\]]*?)][ \t]*()\([ \t]?<?([\S]+?(?:\([\S]*?\)[\S]*?)?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(["'])([^"]*?)\6)?[ \t]?\)/g,e),g=g.replace(/!\[([^\[\]]+)]()()()()()/g,e),g=C.converter._dispatch("images.after",g,A,C)}),I.subParser("italicsAndBold",function(g,A,C){"use strict";function I(g,A,C){return A+g+C}return g=C.converter._dispatch("italicsAndBold.before",g,A,C),g=A.literalMidWordUnderscores?(g=(g=g.replace(/\b___(\S[\s\S]*)___\b/g,function(g,A){return I(A,"<strong><em>","</em></strong>")})).replace(/\b__(\S[\s\S]*)__\b/g,function(g,A){return I(A,"<strong>","</strong>")})).replace(/\b_(\S[\s\S]*?)_\b/g,function(g,A){return I(A,"<em>","</em>")}):(g=(g=g.replace(/___(\S[\s\S]*?)___/g,function(g,A){return/\S$/.test(A)?I(A,"<strong><em>","</em></strong>"):g})).replace(/__(\S[\s\S]*?)__/g,function(g,A){return/\S$/.test(A)?I(A,"<strong>","</strong>"):g})).replace(/_([^\s_][\s\S]*?)_/g,function(g,A){return/\S$/.test(A)?I(A,"<em>","</em>"):g}),g=A.literalMidWordAsterisks?(g=(g=g.replace(/([^*]|^)\B\*\*\*(\S[\s\S]+?)\*\*\*\B(?!\*)/g,function(g,A,C){return I(C,A+"<strong><em>","</em></strong>")})).replace(/([^*]|^)\B\*\*(\S[\s\S]+?)\*\*\B(?!\*)/g,function(g,A,C){return I(C,A+"<strong>","</strong>")})).replace(/([^*]|^)\B\*(\S[\s\S]+?)\*\B(?!\*)/g,function(g,A,C){return I(C,A+"<em>","</em>")}):(g=(g=g.replace(/\*\*\*(\S[\s\S]*?)\*\*\*/g,function(g,A){return/\S$/.test(A)?I(A,"<strong><em>","</em></strong>"):g})).replace(/\*\*(\S[\s\S]*?)\*\*/g,function(g,A){return/\S$/.test(A)?I(A,"<strong>","</strong>"):g})).replace(/\*([^\s*][\s\S]*?)\*/g,function(g,A){return/\S$/.test(A)?I(A,"<em>","</em>"):g}),g=C.converter._dispatch("italicsAndBold.after",g,A,C)}),I.subParser("lists",function(g,A,C){"use strict";function e(g,e){C.gListLevel++,g=g.replace(/\n{2,}$/,"\n");var r=/(\n)?(^ {0,3})([*+-]|\d+[.])[ \t]+((\[(x|X| )?])?[ \t]*[^\r]+?(\n{1,2}))(?=\n*(¨0| {0,3}([*+-]|\d+[.])[ \t]+))/gm,t=/\n[ \t]*\n(?!¨0)/.test(g+="¨0");return A.disableForced4SpacesIndentedSublists&&(r=/(\n)?(^ {0,3})([*+-]|\d+[.])[ \t]+((\[(x|X| )?])?[ \t]*[^\r]+?(\n{1,2}))(?=\n*(¨0|\2([*+-]|\d+[.])[ \t]+))/gm),g=g.replace(r,function(g,e,r,a,n,o,s){s=s&&""!==s.trim();var i=I.subParser("outdent")(n,A,C),l="";return o&&A.tasklists&&(l=' class="task-list-item" style="list-style-type: none;"',i=i.replace(/^[ \t]*\[(x|X| )?]/m,function(){var g='<input type="checkbox" disabled style="margin: 0px 0.35em 0.25em -1.6em; vertical-align: middle;"';return s&&(g+=" checked"),g+=">"})),i=i.replace(/^([-*+]|\d\.)[ \t]+[\S\n ]*/g,function(g){return"¨A"+g}),e||i.search(/\n{2,}/)>-1?(i=I.subParser("githubCodeBlocks")(i,A,C),i=I.subParser("blockGamut")(i,A,C)):(i=(i=I.subParser("lists")(i,A,C)).replace(/\n$/,""),i=(i=I.subParser("hashHTMLBlocks")(i,A,C)).replace(/\n\n+/g,"\n\n"),i=t?I.subParser("paragraphs")(i,A,C):I.subParser("spanGamut")(i,A,C)),i=i.replace("¨A",""),i="<li"+l+">"+i+"</li>\n"}),g=g.replace(/¨0/g,""),C.gListLevel--,e&&(g=g.replace(/\s+$/,"")),g}function r(g,A){if("ol"===A){var C=g.match(/^ *(\d+)\./);if(C&&"1"!==C[1])return' start="'+C[1]+'"'}return""}function t(g,C,I){var t=A.disableForced4SpacesIndentedSublists?/^ ?\d+\.[ \t]/gm:/^ {0,3}\d+\.[ \t]/gm,a=A.disableForced4SpacesIndentedSublists?/^ ?[*+-][ \t]/gm:/^ {0,3}[*+-][ \t]/gm,n="ul"===C?t:a,o="";if(-1!==g.search(n))!function A(s){var i=s.search(n),l=r(g,C);-1!==i?(o+="\n\n<"+C+l+">\n"+e(s.slice(0,i),!!I)+"</"+C+">\n",n="ul"===(C="ul"===C?"ol":"ul")?t:a,A(s.slice(i))):o+="\n\n<"+C+l+">\n"+e(s,!!I)+"</"+C+">\n"}(g);else{var s=r(g,C);o="\n\n<"+C+s+">\n"+e(g,!!I)+"</"+C+">\n"}return o}return g=C.converter._dispatch("lists.before",g,A,C),g+="¨0",g=C.gListLevel?g.replace(/^(( {0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(¨0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm,function(g,A,C){return t(A,C.search(/[*+-]/g)>-1?"ul":"ol",!0)}):g.replace(/(\n\n|^\n?)(( {0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(¨0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm,function(g,A,C,I){return t(C,I.search(/[*+-]/g)>-1?"ul":"ol",!1)}),g=g.replace(/¨0/,""),g=C.converter._dispatch("lists.after",g,A,C)}),I.subParser("metadata",function(g,A,C){"use strict";function I(g){C.metadata.raw=g,(g=(g=g.replace(/&/g,"&").replace(/"/g,""")).replace(/\n {4}/g," ")).replace(/^([\S ]+): +([\s\S]+?)$/gm,function(g,A,I){return C.metadata.parsed[A]=I,""})}return A.metadata?(g=C.converter._dispatch("metadata.before",g,A,C),g=g.replace(/^\s*«««+(\S*?)\n([\s\S]+?)\n»»»+\n/,function(g,A,C){return I(C),"¨M"}),g=g.replace(/^\s*---+(\S*?)\n([\s\S]+?)\n---+\n/,function(g,A,e){return A&&(C.metadata.format=A),I(e),"¨M"}),g=g.replace(/¨M/g,""),g=C.converter._dispatch("metadata.after",g,A,C)):g}),I.subParser("outdent",function(g,A,C){"use strict";return g=C.converter._dispatch("outdent.before",g,A,C),g=g.replace(/^(\t|[ ]{1,4})/gm,"¨0"),g=g.replace(/¨0/g,""),g=C.converter._dispatch("outdent.after",g,A,C)}),I.subParser("paragraphs",function(g,A,C){"use strict";for(var e=(g=(g=(g=C.converter._dispatch("paragraphs.before",g,A,C)).replace(/^\n+/g,"")).replace(/\n+$/g,"")).split(/\n{2,}/g),r=[],t=e.length,a=0;a<t;a++){var n=e[a];n.search(/¨(K|G)(\d+)\1/g)>=0?r.push(n):n.search(/\S/)>=0&&(n=(n=I.subParser("spanGamut")(n,A,C)).replace(/^([ \t]*)/g,"<p>"),n+="</p>",r.push(n))}for(t=r.length,a=0;a<t;a++){for(var o="",s=r[a],i=!1;/¨(K|G)(\d+)\1/.test(s);){var l=RegExp.$1,c=RegExp.$2;o=(o="K"===l?C.gHtmlBlocks[c]:i?I.subParser("encodeCode")(C.ghCodeBlocks[c].text,A,C):C.ghCodeBlocks[c].codeblock).replace(/\$/g,"$$$$"),s=s.replace(/(\n\n)?¨(K|G)\d+\2(\n\n)?/,o),/^<pre\b[^>]*>\s*<code\b[^>]*>/.test(s)&&(i=!0)}r[a]=s}return g=r.join("\n"),g=g.replace(/^\n+/g,""),g=g.replace(/\n+$/g,""),C.converter._dispatch("paragraphs.after",g,A,C)}),I.subParser("runExtension",function(g,A,C,I){"use strict";if(g.filter)A=g.filter(A,I.converter,C);else if(g.regex){var e=g.regex;e instanceof RegExp||(e=new RegExp(e,"g")),A=A.replace(e,g.replace)}return A}),I.subParser("spanGamut",function(g,A,C){"use strict";return g=C.converter._dispatch("spanGamut.before",g,A,C),g=I.subParser("codeSpans")(g,A,C),g=I.subParser("escapeSpecialCharsWithinTagAttributes")(g,A,C),g=I.subParser("encodeBackslashEscapes")(g,A,C),g=I.subParser("images")(g,A,C),g=I.subParser("anchors")(g,A,C),g=I.subParser("autoLinks")(g,A,C),g=I.subParser("simplifiedAutoLinks")(g,A,C),g=I.subParser("emoji")(g,A,C),g=I.subParser("underline")(g,A,C),g=I.subParser("italicsAndBold")(g,A,C),g=I.subParser("strikethrough")(g,A,C),g=I.subParser("ellipsis")(g,A,C),g=I.subParser("hashHTMLSpans")(g,A,C),g=I.subParser("encodeAmpsAndAngles")(g,A,C),A.simpleLineBreaks?/\n\n¨K/.test(g)||(g=g.replace(/\n+/g,"<br />\n")):g=g.replace(/ +\n/g,"<br />\n"),g=C.converter._dispatch("spanGamut.after",g,A,C)}),I.subParser("strikethrough",function(g,A,C){"use strict";return A.strikethrough&&(g=(g=C.converter._dispatch("strikethrough.before",g,A,C)).replace(/(?:~){2}([\s\S]+?)(?:~){2}/g,function(g,e){return function(g){return A.simplifiedAutoLink&&(g=I.subParser("simplifiedAutoLinks")(g,A,C)),"<del>"+g+"</del>"}(e)}),g=C.converter._dispatch("strikethrough.after",g,A,C)),g}),I.subParser("stripLinkDefinitions",function(g,A,C){"use strict";var e=function(g,e,r,t,a,n,o){return e=e.toLowerCase(),r.match(/^data:.+?\/.+?;base64,/)?C.gUrls[e]=r.replace(/\s/g,""):C.gUrls[e]=I.subParser("encodeAmpsAndAngles")(r,A,C),n?n+o:(o&&(C.gTitles[e]=o.replace(/"|'/g,""")),A.parseImgDimensions&&t&&a&&(C.gDimensions[e]={width:t,height:a}),"")};return g=(g+="¨0").replace(/^ {0,3}\[(.+)]:[ \t]*\n?[ \t]*<?(data:.+?\/.+?;base64,[A-Za-z0-9+/=\n]+?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*\n?[ \t]*(?:(\n*)["|'(](.+?)["|')][ \t]*)?(?:\n\n|(?=¨0)|(?=\n\[))/gm,e),g=g.replace(/^ {0,3}\[(.+)]:[ \t]*\n?[ \t]*<?([^>\s]+)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*\n?[ \t]*(?:(\n*)["|'(](.+?)["|')][ \t]*)?(?:\n+|(?=¨0))/gm,e),g=g.replace(/¨0/,"")}),I.subParser("tables",function(g,A,C){"use strict";function e(g){return/^:[ \t]*--*$/.test(g)?' style="text-align:left;"':/^--*[ \t]*:[ \t]*$/.test(g)?' style="text-align:right;"':/^:[ \t]*--*[ \t]*:$/.test(g)?' style="text-align:center;"':""}function r(g,e){var r="";return g=g.trim(),(A.tablesHeaderId||A.tableHeaderId)&&(r=' id="'+g.replace(/ /g,"_").toLowerCase()+'"'),g=I.subParser("spanGamut")(g,A,C),"<th"+r+e+">"+g+"</th>\n"}function t(g,e){return"<td"+e+">"+I.subParser("spanGamut")(g,A,C)+"</td>\n"}function a(g){var a,n=g.split("\n");for(a=0;a<n.length;++a)/^ {0,3}\|/.test(n[a])&&(n[a]=n[a].replace(/^ {0,3}\|/,"")),/\|[ \t]*$/.test(n[a])&&(n[a]=n[a].replace(/\|[ \t]*$/,"")),n[a]=I.subParser("codeSpans")(n[a],A,C);var o=n[0].split("|").map(function(g){return g.trim()}),s=n[1].split("|").map(function(g){return g.trim()}),i=[],l=[],c=[],u=[];for(n.shift(),n.shift(),a=0;a<n.length;++a)""!==n[a].trim()&&i.push(n[a].split("|").map(function(g){return g.trim()}));if(o.length<s.length)return g;for(a=0;a<s.length;++a)c.push(e(s[a]));for(a=0;a<o.length;++a)I.helper.isUndefined(c[a])&&(c[a]=""),l.push(r(o[a],c[a]));for(a=0;a<i.length;++a){for(var d=[],p=0;p<l.length;++p)I.helper.isUndefined(i[a][p]),d.push(t(i[a][p],c[p]));u.push(d)}return function(g,A){for(var C="<table>\n<thead>\n<tr>\n",I=g.length,e=0;e<I;++e)C+=g[e];for(C+="</tr>\n</thead>\n<tbody>\n",e=0;e<A.length;++e){C+="<tr>\n";for(var r=0;r<I;++r)C+=A[e][r];C+="</tr>\n"}return C+="</tbody>\n</table>\n"}(l,u)}if(!A.tables)return g;return g=C.converter._dispatch("tables.before",g,A,C),g=g.replace(/\\(\|)/g,I.helper.escapeCharactersCallback),g=g.replace(/^ {0,3}\|?.+\|.+\n {0,3}\|?[ \t]*:?[ \t]*(?:[-=]){2,}[ \t]*:?[ \t]*\|[ \t]*:?[ \t]*(?:[-=]){2,}[\s\S]+?(?:\n\n|¨0)/gm,a),g=g.replace(/^ {0,3}\|.+\|[ \t]*\n {0,3}\|[ \t]*:?[ \t]*(?:[-=]){2,}[ \t]*:?[ \t]*\|[ \t]*\n( {0,3}\|.+\|[ \t]*\n)*(?:\n|¨0)/gm,a),g=C.converter._dispatch("tables.after",g,A,C)}),I.subParser("underline",function(g,A,C){"use strict";return A.underline?(g=C.converter._dispatch("underline.before",g,A,C),g=A.literalMidWordUnderscores?g.replace(/\b_?__(\S[\s\S]*)___?\b/g,function(g,A){return"<u>"+A+"</u>"}):g.replace(/_?__(\S[\s\S]*?)___?/g,function(g,A){return/\S$/.test(A)?"<u>"+A+"</u>":g}),g=g.replace(/(_)/g,I.helper.escapeCharactersCallback),g=C.converter._dispatch("underline.after",g,A,C)):g}),I.subParser("unescapeSpecialChars",function(g,A,C){"use strict";return g=C.converter._dispatch("unescapeSpecialChars.before",g,A,C),g=g.replace(/¨E(\d+)E/g,function(g,A){var C=parseInt(A);return String.fromCharCode(C)}),g=C.converter._dispatch("unescapeSpecialChars.after",g,A,C)});"function"==typeof define&&define.amd?define(function(){"use strict";return I}):"undefined"!=typeof module&&module.exports?module.exports=I:this.showdown=I}).call(this); +//# sourceMappingURL=showdown.min.js.map diff --git a/www/vincent.demeester.fr/assets/.fancyindex/styles.css b/www/vincent.demeester.fr/assets/.fancyindex/styles.css @@ -0,0 +1,196 @@ +/* styles.css + * Better styling for of Nginx FancyIndex page + * © 2015-17, Lilian Besson (Naereen) and contributors, + * open-sourced under the MIT License, https://lbesson.mit-license.org/ + * hosted on GitHub, https://GitHub.com/Naereen/Nginx-Fancyindex-Theme + */ + +* { + font-family: 'Verdana', sans-serif; + margin: 0; + padding: 0; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +html { + color: #61666c; + font-weight: 300; + font-size: 1em; + line-height: 2em; +} + +body { + margin: 0 auto; + padding-top: 20px; + max-width: 800px; +} + +thead { + font-weight: 200; + font-size: 1.2em; +} + +h1 { + font-weight: 200; + text-align: center; + font-size: 1.4em; + line-height: 3em; +} + +a { + color: #5f5f5f; + text-decoration: none; +} + a:hover { + color: #000; + } + a.clear, a.clear:link, a.clear:visited { + color: #666; + padding: 2px 0; + font-weight: 400; + font-size: 14px; + margin: 0 0 0 20px; + line-height: 14px; + display: inline-block; + border-bottom: transparent 1px solid; + vertical-align: -10px; + -webkit-transition: all 300ms ease-in; + -moz-transition: all 300ms ease-in; + -ms-transition: all 300ms ease-in; + -o-transition: all 300ms ease-in; + transition: all 300ms ease-in; + } + +input { + margin: 0 auto; + font-size: 100%; + vertical-align: middle; + *overflow: visible; + line-height: normal; + font-family: 'Open Sans', sans-serif; + font-size: 12px; + font-weight: 300; + line-height: 18px; + color: #555; + display: inline-block; + height: 20px; + padding: 4px 32px 4px 6px; + margin-bottom: 9px; + font-size: 14px; + line-height: 20px; + color: #555; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + width: 196px; + background-color: #fff; + border: 1px solid #ccc; + -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075); + -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075); + box-shadow: inset 0 1px 1px rgba(0,0,0,0.075); + -webkit-transition: border linear .2s,box-shadow linear .2s; + -moz-transition: border linear .2s,box-shadow linear .2s; + -o-transition: border linear .2s,box-shadow linear .2s; + transition: border linear .2s,box-shadow linear .2s; +} + input:focus { + outline: 0; + border-color: rgba(0,0,0,0.8); + -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(0,0,0,0.6); + -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(0,0,0,0.6); + box-shadow: inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(0,0,0,0.6); + } + input::-moz-focus-inner { + padding: 0; + border: 0; + } + +#search { + display: block; + margin-left: auto; + margin-right: auto; + width: 250px; + margin-top: 20px; + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; + -webkit-appearance: textfield; + -webkit-transition: all 300ms ease-in; + -moz-transition: all 300ms ease-in; + -ms-transition: all 300ms ease-in; + -o-transition: all 300ms ease-in; + transition: all 300ms ease-in; +} + +table { + border-collapse: collapse; + font-size: 0.9em; + max-width: 100%; + margin: 20px auto 0; +} + +tr { + outline: 0; + border: 0; +} + tr:hover td { + background: #f6f6f6; + } + tr td:first-of-type { + padding-left: 10px; + padding-right: 10px; + } + tr.parent a { + color: #9099A3; + } + +th { + + text-align: left; + font-size: .75em; + padding-right: 20px; +} + th + th { + width: 25%; + } + th + th + th + th { + width: 5%; + } + +td { + padding: 5px 0; + outline: 0; + border: 0; + border-bottom: 1px solid #edf1f5; + vertical-align: middle; + text-align: left; + -webkit-transition: background 300ms ease-in; + -moz-transition: background 300ms ease-in; + -ms-transition: background 300ms ease-in; + -o-transition: background 300ms ease-in; + transition: background 300ms ease-in; +} + td:last-child,th:last-child { + text-align: right; + padding-right: 0; + } + td a { + display: block; + } + +.parent a:hover { + color: #2a2a2a; +} + +footer { + font-size:12px; + text-align:center; +} +footer a { + text-decoration: underline; + color:#990012; +} + +img { max-width: 100%; } diff --git a/www/vincent.demeester.fr/css/fonts/Merriweather-Black.otf b/www/vincent.demeester.fr/css/fonts/Merriweather-Black.otf Binary files differ. diff --git a/www/vincent.demeester.fr/css/fonts/Merriweather-Bold.otf b/www/vincent.demeester.fr/css/fonts/Merriweather-Bold.otf Binary files differ. diff --git a/www/vincent.demeester.fr/css/fonts/Merriweather-BoldItalic.otf b/www/vincent.demeester.fr/css/fonts/Merriweather-BoldItalic.otf Binary files differ. diff --git a/www/vincent.demeester.fr/css/fonts/Merriweather-HeavyItalic.otf b/www/vincent.demeester.fr/css/fonts/Merriweather-HeavyItalic.otf Binary files differ. diff --git a/www/vincent.demeester.fr/css/fonts/Merriweather-Italic.otf b/www/vincent.demeester.fr/css/fonts/Merriweather-Italic.otf Binary files differ. diff --git a/www/vincent.demeester.fr/css/fonts/Merriweather-Light.otf b/www/vincent.demeester.fr/css/fonts/Merriweather-Light.otf Binary files differ. diff --git a/www/vincent.demeester.fr/css/fonts/Merriweather-LightItalic.otf b/www/vincent.demeester.fr/css/fonts/Merriweather-LightItalic.otf Binary files differ. diff --git a/www/vincent.demeester.fr/css/fonts/Merriweather-Regular.otf b/www/vincent.demeester.fr/css/fonts/Merriweather-Regular.otf Binary files differ. diff --git a/www/vincent.demeester.fr/css/new.css b/www/vincent.demeester.fr/css/new.css @@ -0,0 +1,605 @@ +:root { + ---font-sans: sans-serif; + ---font-mono: monospace; + ---tx-1: #000000; + ---tx-2: #1A1A1A; + ---tx-3: #666666; + ---tx-4: #999999; + ---bg-1: #FFFFFF; + ---bg-2: #F6F8FA; + ---bg-3: #E5E7EB; + ---lk-1: #0070F3; + ---lk-2: #0366D6; + ---lk-tx: #FFFFFF; + ---ac-1: #79FFE1; + ---ac-tx: #0C4047; + + --content-box-padding: 0.5rem; + --aside-width: 12.5rem; +} + +@media (prefers-color-scheme: dark) { + :root { + ---tx-1: #ffffff; + ---tx-2: #eeeeee; + ---tx-3: #cccccc; + ---tx-4: #aaaaaa; + ---bg-1: #000000; + ---bg-2: #111111; + ---bg-3: #222222; + ---lk-1: #3291FF; + ---lk-2: #0070F3; + ---lk-tx: #FFFFFF; + ---ac-1: #7928CA; + ---ac-tx: #FFFFFF; + } +} + +* { + /* Reset margins and padding */ + margin: 0; + padding: 0; +} + +address, +area, +article, +aside, +audio, +blockquote, +datalist, +details, +dl, +fieldset, +figure, +form, +input, +iframe, +img, +meter, +nav, +ol, +optgroup, +option, +output, +p, +pre, +progress, +ruby, +section, +table, +textarea, +ul, +video { + /* Margins for most elements */ + margin-bottom: 1rem; +} + +html,input,select,button { + /* Set body font family and some finicky elements */ + font-family: var(---font-sans); +} + +body { + /* Center body in page */ + margin: 0 auto; + max-width: 70rem; + padding: 2rem; + border-radius: 6px; + overflow-x: hidden; + word-break: break-word; + overflow-wrap: break-word; + background: var(---bg-1); + + /* Main body text */ + color: var(---tx-2); + font-size: 1.03rem; + line-height: 1.5; +} + +::selection { + /* Set background color for selected text */ + background: var(---ac-1); + color: var(---ac-tx); +} + +h1,h2,h3,h4,h5,h6 { + line-height: 1; + color: var(---tx-1); + padding-top: .875rem; +} + +h1, +h2, +h3 { + color: var(---tx-1); + padding-bottom: 2px; + margin-bottom: 8px; + border-bottom: 1px solid var(---bg-2); +} + +h4, +h5, +h6 { + margin-bottom: .3rem; +} + +h1 { + font-size: 2.25rem; +} + +h2 { + font-size: 1.85rem; +} + +h3 { + font-size: 1.55rem; +} + +h4 { + font-size: 1.25rem; +} + +h5 { + font-size: 1rem; +} + +h6 { + font-size: .875rem; +} + +a { + color: var(---lk-1); +} + +a:hover { + color: var(---lk-2); +} + +abbr:hover { + /* Set the '?' cursor while hovering an abbreviation */ + cursor: help; +} + +blockquote { + padding: 1.5rem; + background: var(---bg-2); + border-left: 5px solid var(---bg-3); +} + +abbr { + cursor: help; +} + +blockquote *:last-child { + padding-bottom: 0; + margin-bottom: 0; +} + +header { + background: var(---bg-2); + border-bottom: 1px solid var(---bg-3); + padding: 1rem 1.5rem; + + /* This sets the right and left margins to cancel out the body's margins. It's width is still the same, but the background stretches across the page's width. */ + + margin: -2rem calc(0px - (50vw - 50%)) 2rem; + + /* Shorthand for: + + margin-top: -2rem; + margin-bottom: 2rem; + + margin-left: calc(0px - (50vw - 50%)); + margin-right: calc(0px - (50vw - 50%)); */ + + padding-left: calc(50vw - 50%); + padding-right: calc(50vw - 50%); +} + +header img#sitelogo { + margin-bottom: 0; + margin-right: 1em; +} + +header h1, +header h2, +header h3 { + padding-bottom: 0; + border-bottom: 0; +} + +header > *:first-child { + margin-top: 0; + padding-top: 0; +} + +header > *:last-child { + margin-bottom: 0; +} + +main header { + background: var(---bg-3); +} + +a button, +button, +input[type="submit"], +input[type="reset"], +input[type="button"] { + font-size: 1rem; + display: inline-block; + padding: 6px 12px; + text-align: center; + text-decoration: none; + white-space: nowrap; + background: var(---lk-1); + color: var(---lk-tx); + border: 0; + border-radius: 4px; + box-sizing: border-box; + cursor: pointer; + color: var(---lk-tx); +} + +a button[disabled], +button[disabled], +input[type="submit"][disabled], +input[type="reset"][disabled], +input[type="button"][disabled] { + cursor: default; + opacity: .5; + + /* Set the [X] cursor while hovering a disabled link */ + cursor: not-allowed; +} + +.button:focus, +.button:hover, +button:focus, +button:hover, +input[type="submit"]:focus, +input[type="submit"]:hover, +input[type="reset"]:focus, +input[type="reset"]:hover, +input[type="button"]:focus, +input[type="button"]:hover { + background: var(---lk-2); +} + +code, +pre, +kbd, +samp { + /* Set the font family for monospaced elements */ + font-family: var(---font-mono); +} + +code, +samp, +kbd, +pre { + /* The main preformatted style. This is changed slightly across different cases. */ + background: var(---bg-2); + border: 1px solid var(---bg-3); + border-radius: 4px; + padding: 3px 6px; +} + +kbd { + /* Makes the kbd element look like a keyboard key */ + border-bottom: 3px solid var(---bg-3); +} + +pre { + padding: 1rem 1.4rem; + max-width: 100%; + overflow: auto; +} + +pre.src { + position: relative; +} +pre.src:before { + /*display: none;*/ + position: absolute; + background-color: var(---bg-3); + /*color: #f3f4f4;*/ + top: 0; + right: 0; + padding: 0.2rem 0.5rem; +} + +pre code { + /* When <code> is in a <pre>, reset it's formatting to blend in */ + background: inherit; + font-size: inherit; + color: inherit; + border: 0; + padding: 0; + margin: 0; +} + +code pre { + /* When <pre> is in a <code>, reset it's formatting to blend in */ + display: inline; + background: inherit; + font-size: inherit; + color: inherit; + border: 0; + padding: 0; + margin: 0; +} + +details { + /* Make the <details> look more "clickable" */ + padding: .6rem 1rem; + background: var(---bg-2); + border: 1px solid var(---bg-3); + border-radius: 4px; +} + +summary { + /* Makes the <summary> look more like a "clickable" link with the pointer cursor */ + cursor: pointer; + font-weight: bold; +} + +details[open] { + /* Adjust the <details> padding while open */ + padding-bottom: .75rem; +} + +details[open] summary { + /* Adjust the <details> padding while open */ + margin-bottom: 6px; +} + +details[open]>*:last-child { + /* Resets the bottom margin of the last element in the <details> while <details> is opened. This prevents double margins/paddings. */ + margin-bottom: 0; +} + +dt { + font-weight: bold; +} + +dd::before { + /* Add an arrow to data table definitions */ + content: '→ '; +} + +hr { + /* Reset the border of the <hr> separator, then set a better line */ + border: 0; + border-bottom: 1px solid var(---bg-3); + margin: 1rem auto; +} + +fieldset { + margin-top: 1rem; + padding: 2rem; + border: 1px solid var(---bg-3); + border-radius: 4px; +} + +legend { + padding: auto .5rem; +} + +table { + /* border-collapse sets the table's elements to share borders, rather than floating as separate "boxes". */ + border-collapse: collapse; + width: 100% +} + +td, +th { + border: 1px solid var(---bg-3); + text-align: left; + padding: .5rem; +} + +th { + background: var(---bg-2); +} + +tr:nth-child(even) { + /* Set every other cell slightly darker. Improves readability. */ + background: var(---bg-2); +} + +table caption { + font-weight: bold; + margin-bottom: .5rem; +} + +textarea { + /* Don't let the <textarea> extend off the screen naturally or when dragged by the user */ + max-width: 100%; +} + +ol, +ul { + /* Replace the browser default padding */ + padding-left: 2rem; +} + +li { + margin-top: .4rem; +} + +ul ul, +ol ul, +ul ol, +ol ol { + margin-bottom: 0; +} + +mark { + padding: 3px 6px; + background: var(---ac-1); + color: var(---ac-tx); +} + +textarea, +select, +input { + padding: 6px 12px; + margin-bottom: .5rem; + background: var(---bg-2); + color: var(---tx-2); + border: 1px solid var(---bg-3); + border-radius: 4px; + box-shadow: none; + box-sizing: border-box; +} + +img { + max-width: 100%; +} + +aside { + --aside-offset: 1rem; + --aside-offset-lineheight: 1.5rem; + background-color: var(---bg-2); + padding: calc(var(--content-box-padding) * 0.5) calc(var(--content-box-padding) * 2); + padding-top: 1em; + margin-bottom: 0em; + float: right; + width: var(--aside-width); + font-size: 0.9rem; + line-height: 1.25rem; + border: 1px solid var(--bg-3); +} + +figure figcaption { + text-align: center; + color: var(---tx-2); + font-style: italic; + margin: 0 2rem; +} + +footer { + text-align: center; + color: var(---tx-4); +} + +footer a { + color: var(---tx-3); +} + +section img { + display: block; + margin-left: auto; + margin-right: auto; +} + +section img.shadow { + box-shadow: 0 28px 50px rgba(0,0,0,0.16); +} + +div.drawer:before { + content: ""; + position: absolute; + top: 0; + bottom: 0; + left: 4.4rem; + width: 1px; + background-color: #555; +} + +div.drawer { + display: flex; + padding: 0; + margin: 1rem; + padding-top: 1rem; + position: relative; + color: #555; +} + +div.drawer h6 { + box-sizing: border-box; + font-style: italic; + flex: 0 0 5rem; + margin:0; + padding:0; + align-self: center; + text-align: right; + padding-right: 1.2rem; +} + +div.drawer.info:before { + background-color: #00529B; +} +div.drawer.info { + color: #00529B; +} + +div.drawer.note:before { + background-color: #9F6000; +} +div.drawer.note { + color: #9F6000; +} +div.drawer.tip:before { + background-color: #4F8A10; +} +div.drawer.tip { + color: #4F8A10; +} +div.drawer.warning:before { + background-color: #D8000C; +} +div.drawer.warning { + color: #D8000C; +} +div.drawer.results:before { + background-color: black; +} +div.drawer.results { + font-family:monospace,monospace; + font-family: var(---font-mono); + color: black; + width: 100% !important; +} +div.drawer.results p { + display: block; + unicode-bidi: embed; + white-space: pre; + margin-top: 0; +} +div.drawer.results pre { + background-color: inherit; + border: 0; +} +div.drawer.results pre.src:before { + background-color: #eee; + color: #555; +} + +.tag { + font-family:var(---font-mono); + font-size: 1rem; +} +.tag span { + padding:.3em; + float:right; + margin-right:.5em; + border:1px solid #bbb; + border-radius:3px; + background-clip:padding-box; + color:#333; + background-color:#eee; + line-height:1rem; +} + +.todo { + font-family: monospace; color: red; +} +.done { + font-family: monospace; color: green; +} +.priority { font-family: monospace; color: orange; } +.timestamp { color: #bebebe; } +.timestamp-kwd { color: #5f9ea0; } diff --git a/www/vincent.demeester.fr/css/site.css b/www/vincent.demeester.fr/css/site.css @@ -0,0 +1,353 @@ +@font-face { + font-family: "Merriweather"; + src: url("/css/fonts/Merriweather-Regular.otf") format("opentype"); +} +@font-face { + font-family: "Merriweather"; + font-weight: bold; + src: url("/css/fonts/Merriweather-Bold.otf") format("opentype"); +} +@font-face { + font-family: "Merriweather"; + font-weight: bold; + font-style: italic; + src: url("/css/fonts/Merriweather-BoldItalic.otf") format("opentype"); +} +@font-face { + font-family: "Merriweather"; + font-style: italic; + src: url("/css/fonts/Merriweather-Italic.otf") format("opentype"); +} +@font-face { + font-family: "Merriweather"; + font-weight: lighter; + font-style: italic; + src: url("/css/fonts/Merriweather-LightItalic.otf") format("opentype"); +} +@font-face { + font-family: "Merriweather"; + font-weight: lighter; + src: url("/css/fonts/Merriweather-Light.otf") format("opentype"); +} + +html{ + margin: 1rem 2rem; + font-size: 106.25%; + --body-vertical-margin: 1rem; + --body-horizontal-margin: 1rem; + --content-box-padding: 0.5rem; + --font-main: Georgia, sans-serif; + --font-mono: 'Ubuntu Mono','Fira Code','Deja Vu Sans Mono','Bitstream Vera Sans Mono','Source Code Pro','Courier New',monospace; + --font-quote: "Fresco", Georgia, serif; + --font-sans: Avenir Next, Avenir, system-ui, sans-serif; + --font-header: 'Merriweather','Linux Libertine','Georgia','Times',serif; + --figure-offset: 0; + --figcaption-padding: 0; + --aside-width: 12.5rem; + --column-gap: 2rem; + --padding: 1rem; +} + +body { + font-family: var(--font-main); + max-width: 70rem; + min-width: 30rem; + line-height: 1.4rem; + margin: 0 auto; +} + +p { + word-wrap: break-word; +} +body > header{ + margin-bottom: 4em; +} + +body > header nav { + float:right; +} + +body > header nav ul li { + display: inline-block; + list-style: none; + padding: 0 0.5rem; +} + +main { + font-size: 1.1rem; + font-family: var(--font-sans); +} + +div.index h1 { + font-family: "Merriweather", "Linux Libertine", "Georgia", "Times", serif; + font-size: 2rem; + line-height: 2rem; + font-weight: bolder; +} + +div.index h2 { + font-family: "Merriweather", "Linux Libertine", "Georgia", "Times", serif; + font-size: 1.5rem; + line-height: 2rem; +} + +nav#table-of-contents { + background: #f2f2f2; + padding: 0.2rem 1rem; + font-size: 90%; +} + +nav#table-of-contents h2 { + font-family: var(--font-sans); + font-weight: bold; + font-style: italic; +} + +nav#table-of-contents li { + list-style-type: square; +} + +nav#table-of-contents li a { + color: black; +} +nav#table-of-contents li a:hover { + color: #33f; + background-color: inherit; +} + +code { + background-color: #f3f4f4; + font-family: var(--font-mono); + padding: 0.1rem 0.3rem; + border: 1px solid #e1e1e1; + border-radius: 4px; +} + +blockquote code { + background-color: #999; + border-color: #666; + color: white; +} + +pre { + background-color: #f3f4f4; + font-family: var(--font-mono); + white-space: pre-wrap; + overflow: auto; + overflow-x: auto; + margin: 1.25rem calc(-1 * var(--content-box-padding)); + padding: var(--content-box-padding); + border: 1px solid #999; +} + +pre.src { + position: relative; +} + +pre.src:before { + /*display: none;*/ + position: absolute; + background-color: #999; + color: #f3f4f4; + top: 0; + right: 0; + padding: 0.2rem 0.5rem; +} + +aside { + --aside-offset: 1rem; + --aside-offset-lineheight: 1.5rem; + background-color: #f8f8f8; + padding: calc(var(--content-box-padding) * 0.5) calc(var(--content-box-padding) * 2); + float: right; + width: var(--aside-width); + font-size: 0.9rem; + line-height: 1.25rem; + border: 1px solid #ccc; +} + +a { text-decoration: none; color: #339; } +a:hover { color: #33f; background-color: #f6f6f6; } + +blockquote { + font-family: var(--font-quote); + text-rendering: optimizeLegibility; + line-height: 1.4; + font-style: italic; + background-color: #e0e0e0; + border-radius: 0.5em; + width: 90%; + margin: 1em auto; + padding: 0.2rem 1.2rem; +} + +h1, h2, h3, h4 { + font-family: var(--font-header); +} +main header h1 { + font-size: 300%; + line-height: 3.8rem; +} + +figure figcaption { + text-align: center; + color: #666; + font-style: italic; + margin: 0 2rem; +} + +footer { + text-align: center; + color: #999; +} + +footer a { + color: #666; +} + +section img { + display: block; + margin-left: auto; + margin-right: auto; +} + + +section img.shadow { + box-shadow: 0 28px 50px rgba(0,0,0,0.16); +} + +table { + margin:1em auto; + border-top:2px solid; + border-collapse:collapse +} +table,thead { + border-bottom:2px solid +} +table td+td,table th+th { + border-left:1px solid gray +} +table tr { + border-top:1px solid #d3d3d3 +} +td,th { + padding:.3em .6em; + vertical-align:middle +} + +.title { text-align: center; + margin-bottom: 1.5rem; } +.subtitle { + text-align: center; + font-size: medium; + font-weight: bold; + margin-top:0; + color: #999; +} + +div.drawer:before { + content: ""; + position: absolute; + top: 0; + bottom: 0; + left: 6.4rem; + width: 1px; + background-color: #555; +} + +div.drawer { + display: flex; + padding: 0; + margin: 1rem; + margin-left: -7rem; + position: relative; + color: #555; +} + +div.drawer h6 { + box-sizing: border-box; + font-style: italic; + flex: 0 0 7rem; + margin:0; + padding:0; + align-self: center; + text-align: right; + padding-right: 1.2rem; +} + +div.drawer.info:before { + background-color: #00529B; +} +div.drawer.info { + color: #00529B; +} + +div.drawer.note:before { + background-color: #9F6000; +} +div.drawer.note { + color: #9F6000; +} +div.drawer.tip:before { + background-color: #4F8A10; +} +div.drawer.tip { + color: #4F8A10; +} +div.drawer.warning:before { + background-color: #D8000C; +} +div.drawer.warning { + color: #D8000C; +} +div.drawer.results:before { + background-color: black; +} +div.drawer.results { + font-family:monospace,monospace; + font-family: var(--font-mono); + color: black; + width: 100% !important; +} +div.drawer.results p { + display: block; + unicode-bidi: embed; + white-space: pre; + margin-top: 0; +} +div.drawer.results pre { + background-color: inherit; + border: 0; +} +div.drawer.results pre.src:before { + background-color: #eee; + color: #555; +} + +.tag { + font-family:var(--font-mono); + font-size: 1rem; +} +.tag span { + padding:.3em; + float:right; + margin-right:.5em; + border:1px solid #bbb; + border-radius:3px; + background-clip:padding-box; + color:#333; + background-color:#eee; + line-height:1rem; +} + +.todo { font-family: monospace; color: red; } +.done { font-family: monospace; color: green; } +.priority { font-family: monospace; color: orange; } +.timestamp { color: #bebebe; } +.timestamp-kwd { color: #5f9ea0; } +.org-right { margin-left: auto; margin-right: 0px; text-align: right; } +.org-left { margin-left: 0px; margin-right: auto; text-align: left; } +.org-center { margin-left: auto; margin-right: auto; text-align: center; } +.underline { text-decoration: underline; } +#postamble p, #preamble p { font-size: 90%; margin: .2em; } +p.verse { margin-left: 3%; } diff --git a/www/vincent.demeester.fr/css/syntax.css b/www/vincent.demeester.fr/css/syntax.css @@ -0,0 +1,280 @@ +pre.src:hover:before { display: inline;} +/* Languages per Org manual */ +pre.src-asymptote:before { content: 'Asymptote'; } +pre.src-awk:before { content: 'Awk'; } +pre.src-C:before { content: 'C'; } +/* pre.src-C++ doesn't work in CSS */ +pre.src-clojure:before { content: 'Clojure'; } +pre.src-css:before { content: 'CSS'; } +pre.src-D:before { content: 'D'; } +pre.src-ditaa:before { content: 'ditaa'; } +pre.src-dot:before { content: 'Graphviz'; } +pre.src-calc:before { content: 'Emacs Calc'; } +pre.src-emacs-lisp:before { content: 'Emacs Lisp'; } +pre.src-fortran:before { content: 'Fortran'; } +pre.src-gnuplot:before { content: 'gnuplot'; } +pre.src-haskell:before { content: 'Haskell'; } +pre.src-hledger:before { content: 'hledger'; } +pre.src-java:before { content: 'Java'; } +pre.src-js:before { content: 'Javascript'; } +pre.src-latex:before { content: 'LaTeX'; } +pre.src-ledger:before { content: 'Ledger'; } +pre.src-lisp:before { content: 'Lisp'; } +pre.src-lilypond:before { content: 'Lilypond'; } +pre.src-lua:before { content: 'Lua'; } +pre.src-matlab:before { content: 'MATLAB'; } +pre.src-mscgen:before { content: 'Mscgen'; } +pre.src-ocaml:before { content: 'Objective Caml'; } +pre.src-octave:before { content: 'Octave'; } +pre.src-org:before { content: 'Org mode'; } +pre.src-oz:before { content: 'OZ'; } +pre.src-plantuml:before { content: 'Plantuml'; } +pre.src-processing:before { content: 'Processing.js'; } +pre.src-python:before { content: 'Python'; } +pre.src-R:before { content: 'R'; } +pre.src-ruby:before { content: 'Ruby'; } +pre.src-sass:before { content: 'Sass'; } +pre.src-scheme:before { content: 'Scheme'; } +pre.src-screen:before { content: 'Gnu Screen'; } +pre.src-sed:before { content: 'Sed'; } +pre.src-sh:before { content: 'shell'; } +pre.src-sql:before { content: 'SQL'; } +pre.src-sqlite:before { content: 'SQLite'; } +/* additional languages in org.el's org-babel-load-languages alist */ +pre.src-forth:before { content: 'Forth'; } +pre.src-io:before { content: 'IO'; } +pre.src-J:before { content: 'J'; } +pre.src-makefile:before { content: 'Makefile'; } +pre.src-maxima:before { content: 'Maxima'; } +pre.src-perl:before { content: 'Perl'; } +pre.src-picolisp:before { content: 'Pico Lisp'; } +pre.src-scala:before { content: 'Scala'; } +pre.src-shell:before { content: 'Shell Script'; } +pre.src-ebnf2ps:before { content: 'ebfn2ps'; } +/* additional language identifiers per \"defun org-babel-execute\" + in ob-*.el */ +pre.src-cpp:before { content: 'C++'; } +pre.src-abc:before { content: 'ABC'; } +pre.src-coq:before { content: 'Coq'; } +pre.src-groovy:before { content: 'Groovy'; } +/* additional language identifiers from org-babel-shell-names in + ob-shell.el: ob-shell is the only babel language using a lambda to put + the execution function name together. */ +pre.src-go:before { content: 'go'; } +pre.src-bash:before { content: 'bash'; } +pre.src-csh:before { content: 'csh'; } +pre.src-ash:before { content: 'ash'; } +pre.src-dash:before { content: 'dash'; } +pre.src-ksh:before { content: 'ksh'; } +pre.src-mksh:before { content: 'mksh'; } +pre.src-posh:before { content: 'posh'; } +/* Additional Emacs modes also supported by the LaTeX listings package */ +pre.src-ada:before { content: 'Ada'; } +pre.src-asm:before { content: 'Assembler'; } +pre.src-caml:before { content: 'Caml'; } +pre.src-delphi:before { content: 'Delphi'; } +pre.src-html:before { content: 'HTML'; } +pre.src-idl:before { content: 'IDL'; } +pre.src-mercury:before { content: 'Mercury'; } +pre.src-metapost:before { content: 'MetaPost'; } +pre.src-modula-2:before { content: 'Modula-2'; } +pre.src-pascal:before { content: 'Pascal'; } +pre.src-ps:before { content: 'PostScript'; } +pre.src-prolog:before { content: 'Prolog'; } +pre.src-simula:before { content: 'Simula'; } +pre.src-tcl:before { content: 'tcl'; } +pre.src-tex:before { content: 'TeX'; } +pre.src-plain-tex:before { content: 'Plain TeX'; } +pre.src-verilog:before { content: 'Verilog'; } +pre.src-vhdl:before { content: 'VHDL'; } +pre.src-xml:before { content: 'XML'; } +pre.src-nxml:before { content: 'XML'; } +pre.src-yaml:before { content: 'YAML'; } +pre.src-toml:before { content: 'TOML'; } +/* add a generic configuration mode; LaTeX export needs an additional + (add-to-list 'org-latex-listings-langs '(conf \" \")) in .emacs */ +pre.src-conf:before { content: 'Configuration File'; } + +table { border-collapse:collapse; } +caption.t-above { caption-side: top; } +caption.t-bottom { caption-side: bottom; } +td, th { vertical-align:top; } +th.org-right { text-align: center; } +th.org-left { text-align: center; } +th.org-center { text-align: center; } +td.org-right { text-align: right; } +td.org-left { text-align: left; } +td.org-center { text-align: center; } +dt { font-weight: bold; } +.footpara { display: inline; } +.footdef { margin-bottom: 1em; } +.figure { padding: 1em; } +.figure p { text-align: center; } +.inlinetask { + padding: 10px; + border: 2px solid gray; + margin: 10px; + background: #ffffcc; +} +#org-div-home-and-up + { text-align: right; font-size: 70%; white-space: nowrap; } +textarea { overflow-x: auto; } +.linenr { font-size: smaller } +.code-highlighted { background-color: #ffff00; } +.org-info-js_info-navigation { border-style: none; } +#org-info-js_console-label + { font-size: 10px; font-weight: bold; white-space: nowrap; } +.org-info-js_search-highlight + { background-color: #ffff00; color: #000000; font-weight: bold; } +.org-svg { width: 90%; } + +/****************************** + * Syntax highlighting + *** + * Source: https://github.com/fniessen/org-html-themes/blob/master/styles/bigblow/css/htmlize.css + ******************************/ +.org-bold { /* bold */ font-weight: bold; } +.org-bold-italic { /* bold-italic */ font-weight: bold; font-style: italic; } +.org-buffer-menu-buffer { /* buffer-menu-buffer */ font-weight: bold; } +.org-builtin { /* font-lock-builtin-face */ color: #7a378b; } +.org-button { /* button */ text-decoration: underline; } +.org-calendar-today { /* calendar-today */ text-decoration: underline; } +.org-change-log-acknowledgement { /* change-log-acknowledgement */ color: #b22222; } +.org-change-log-conditionals { /* change-log-conditionals */ color: #a0522d; } +.org-change-log-date { /* change-log-date */ color: #8b2252; } +.org-change-log-email { /* change-log-email */ color: #a0522d; } +.org-change-log-file { /* change-log-file */ color: #0000ff; } +.org-change-log-function { /* change-log-function */ color: #a0522d; } +.org-change-log-list { /* change-log-list */ color: #a020f0; } +.org-change-log-name { /* change-log-name */ color: #008b8b; } +.org-comint-highlight-input { /* comint-highlight-input */ font-weight: bold; } +.org-comint-highlight-prompt { /* comint-highlight-prompt */ color: #00008b; } +.org-comment { /* font-lock-comment-face */ color: #b22222; } +.org-comment-delimiter { /* font-lock-comment-delimiter-face */ color: #b22222; } +.org-completions-annotations { /* completions-annotations */ font-style: italic; } +.org-completions-common-part { /* completions-common-part */ color: #000000; background-color: #ffffff; } +.org-completions-first-difference { /* completions-first-difference */ font-weight: bold; } +.org-constant { /* font-lock-constant-face */ color: #008b8b; } +.org-diary { /* diary */ color: #ff0000; } +.org-diff-context { /* diff-context */ color: #7f7f7f; } +.org-diff-file-header { /* diff-file-header */ background-color: #b3b3b3; font-weight: bold; } +.org-diff-function { /* diff-function */ background-color: #cccccc; } +.org-diff-header { /* diff-header */ background-color: #cccccc; } +.org-diff-hunk-header { /* diff-hunk-header */ background-color: #cccccc; } +.org-diff-index { /* diff-index */ background-color: #b3b3b3; font-weight: bold; } +.org-diff-nonexistent { /* diff-nonexistent */ background-color: #b3b3b3; font-weight: bold; } +.org-diff-refine-change { /* diff-refine-change */ background-color: #d9d9d9; } +.org-dired-directory { /* dired-directory */ color: #0000ff; } +.org-dired-flagged { /* dired-flagged */ color: #ff0000; font-weight: bold; } +.org-dired-header { /* dired-header */ color: #228b22; } +.org-dired-ignored { /* dired-ignored */ color: #7f7f7f; } +.org-dired-mark { /* dired-mark */ color: #008b8b; } +.org-dired-marked { /* dired-marked */ color: #ff0000; font-weight: bold; } +.org-dired-perm-write { /* dired-perm-write */ color: #b22222; } +.org-dired-symlink { /* dired-symlink */ color: #a020f0; } +.org-dired-warning { /* dired-warning */ color: #ff0000; font-weight: bold; } +.org-doc { /* font-lock-doc-face */ color: #8b2252; } +.org-escape-glyph { /* escape-glyph */ color: #a52a2a; } +.org-file-name-shadow { /* file-name-shadow */ color: #7f7f7f; } +.org-flyspell-duplicate { /* flyspell-duplicate */ color: #cdad00; font-weight: bold; text-decoration: underline; } +.org-flyspell-incorrect { /* flyspell-incorrect */ color: #ff4500; font-weight: bold; text-decoration: underline; } +.org-fringe { /* fringe */ background-color: #f2f2f2; } +.org-function-name { /* font-lock-function-name-face */ color: #0000ff; } +.org-header-line { /* header-line */ color: #333333; background-color: #e5e5e5; } +.org-help-argument-name { /* help-argument-name */ font-style: italic; } +.org-highlight { /* highlight */ background-color: #b4eeb4; } +.org-holiday { /* holiday */ background-color: #ffc0cb; } +.org-isearch { /* isearch */ color: #b0e2ff; background-color: #cd00cd; } +.org-isearch-fail { /* isearch-fail */ background-color: #ffc1c1; } +.org-italic { /* italic */ font-style: italic; } +.org-keyword { /* font-lock-keyword-face */ color: #a020f0; } +.org-lazy-highlight { /* lazy-highlight */ background-color: #afeeee; } +.org-link { /* link */ color: #0000ff; text-decoration: underline; } +.org-link-visited { /* link-visited */ color: #8b008b; text-decoration: underline; } +.org-log-edit-header { /* log-edit-header */ color: #a020f0; } +.org-log-edit-summary { /* log-edit-summary */ color: #0000ff; } +.org-log-edit-unknown-header { /* log-edit-unknown-header */ color: #b22222; } +.org-match { /* match */ background-color: #ffff00; } +.org-next-error { /* next-error */ background-color: #eedc82; } +.org-nobreak-space { /* nobreak-space */ color: #a52a2a; text-decoration: underline; } +.org-org-archived { /* org-archived */ color: #7f7f7f; } +.org-org-block { /* org-block */ color: #7f7f7f; } +.org-org-block-begin-line { /* org-block-begin-line */ color: #b22222; } +.org-org-block-end-line { /* org-block-end-line */ color: #b22222; } +.org-org-checkbox { /* org-checkbox */ font-weight: bold; } +.org-org-checkbox-statistics-done { /* org-checkbox-statistics-done */ color: #228b22; font-weight: bold; } +.org-org-checkbox-statistics-todo { /* org-checkbox-statistics-todo */ color: #ff0000; font-weight: bold; } +.org-org-clock-overlay { /* org-clock-overlay */ background-color: #ffff00; } +.org-org-code { /* org-code */ color: #7f7f7f; } +.org-org-column { /* org-column */ background-color: #e5e5e5; } +.org-org-column-title { /* org-column-title */ background-color: #e5e5e5; font-weight: bold; text-decoration: underline; } +.org-org-date { /* org-date */ color: #a020f0; text-decoration: underline; } +.org-org-document-info { /* org-document-info */ color: #191970; } +.org-org-document-info-keyword { /* org-document-info-keyword */ color: #7f7f7f; } +.org-org-document-title { /* org-document-title */ color: #191970; font-size: 144%; font-weight: bold; } +.org-org-done { /* org-done */ color: #228b22; font-weight: bold; } +.org-org-drawer { /* org-drawer */ color: #0000ff; } +.org-org-ellipsis { /* org-ellipsis */ color: #b8860b; text-decoration: underline; } +.org-org-footnote { /* org-footnote */ color: #a020f0; text-decoration: underline; } +.org-org-formula { /* org-formula */ color: #b22222; } +.org-org-headline-done { /* org-headline-done */ color: #bc8f8f; } +.org-org-hide { /* org-hide */ color: #ffffff; } +.org-org-latex-and-export-specials { /* org-latex-and-export-specials */ color: #8b4513; } +.org-org-level-1 { /* org-level-1 */ color: #0000ff; } +.org-org-level-2 { /* org-level-2 */ color: #a0522d; } +.org-org-level-3 { /* org-level-3 */ color: #a020f0; } +.org-org-level-4 { /* org-level-4 */ color: #b22222; } +.org-org-level-5 { /* org-level-5 */ color: #228b22; } +.org-org-level-6 { /* org-level-6 */ color: #008b8b; } +.org-org-level-7 { /* org-level-7 */ color: #7a378b; } +.org-org-level-8 { /* org-level-8 */ color: #8b2252; } +.org-org-link { /* org-link */ color: #0000ff; text-decoration: underline; } +.org-org-meta-line { /* org-meta-line */ color: #b22222; } +.org-org-mode-line-clock { /* org-mode-line-clock */ color: #000000; background-color: #bfbfbf; } +.org-org-mode-line-clock-overrun { /* org-mode-line-clock-overrun */ color: #000000; background-color: #ff0000; } +.org-org-quote { /* org-quote */ color: #7f7f7f; } +.org-org-scheduled { /* org-scheduled */ color: #006400; } +.org-org-scheduled-previously { /* org-scheduled-previously */ color: #b22222; } +.org-org-scheduled-today { /* org-scheduled-today */ color: #006400; } +.org-org-sexp-date { /* org-sexp-date */ color: #a020f0; } +.org-org-special-keyword { /* org-special-keyword */ color: #a020f0; } +.org-org-table { /* org-table */ color: #0000ff; } +.org-org-tag { /* org-tag */ font-weight: bold; } +.org-org-target { /* org-target */ text-decoration: underline; } +.org-org-time-grid { /* org-time-grid */ color: #b8860b; } +.org-org-todo { /* org-todo */ color: #ff0000; font-weight: bold; } +.org-org-upcoming-deadline { /* org-upcoming-deadline */ color: #b22222; } +.org-org-verbatim { /* org-verbatim */ color: #7f7f7f; } +.org-org-verse { /* org-verse */ color: #7f7f7f; } +.org-org-warning { /* org-warning */ color: #ff0000; font-weight: bold; } +.org-outline-1 { /* outline-1 */ color: #0000ff; } +.org-outline-2 { /* outline-2 */ color: #a0522d; } +.org-outline-3 { /* outline-3 */ color: #a020f0; } +.org-outline-4 { /* outline-4 */ color: #b22222; } +.org-outline-5 { /* outline-5 */ color: #228b22; } +.org-outline-6 { /* outline-6 */ color: #008b8b; } +.org-outline-7 { /* outline-7 */ color: #7a378b; } +.org-outline-8 { /* outline-8 */ color: #8b2252; } +.org-preprocessor { /* font-lock-preprocessor-face */ color: #7a378b; } +.org-query-replace { /* query-replace */ color: #b0e2ff; background-color: #cd00cd; } +.org-regexp-grouping-backslash { /* font-lock-regexp-grouping-backslash */ font-weight: bold; } +.org-regexp-grouping-construct { /* font-lock-regexp-grouping-construct */ font-weight: bold; } +.org-region { /* region */ background-color: #eedc82; } +.org-secondary-selection { /* secondary-selection */ background-color: #ffff00; } +.org-shadow { /* shadow */ color: #7f7f7f; } +.org-show-paren-match { /* show-paren-match */ background-color: #40e0d0; } +.org-show-paren-mismatch { /* show-paren-mismatch */ color: #ffffff; background-color: #a020f0; } +.org-string { /* font-lock-string-face */ color: #8b2252; } +.org-tool-bar { /* tool-bar */ color: #000000; background-color: #bfbfbf; } +.org-tooltip { /* tooltip */ color: #000000; background-color: #ffffe0; } +.org-trailing-whitespace { /* trailing-whitespace */ background-color: #ff0000; } +.org-type { /* font-lock-type-face */ color: #228b22; } +.org-underline { /* underline */ text-decoration: underline; } +.org-variable-name { /* font-lock-variable-name-face */ color: #a0522d; } +.org-warning { /* font-lock-warning-face */ color: #ff0000; font-weight: bold; } +.org-widget-button { /* widget-button */ font-weight: bold; } +.org-widget-button-pressed { /* widget-button-pressed */ color: #ff0000; } +.org-widget-documentation { /* widget-documentation */ color: #006400; } +.org-widget-field { /* widget-field */ background-color: #d9d9d9; } +.org-widget-inactive { /* widget-inactive */ color: #7f7f7f; } +.org-widget-single-line-field { /* widget-single-line-field */ background-color: #d9d9d9; } diff --git a/www/vincent.demeester.fr/images/2012/velo_casse.jpg b/www/vincent.demeester.fr/images/2012/velo_casse.jpg Binary files differ. diff --git a/www/vincent.demeester.fr/images/2014/03/83029.jpg b/www/vincent.demeester.fr/images/2014/03/83029.jpg @@ -0,0 +1 @@ +../../../../../.git/annex/objects/kP/4F/SHA256E-s358707--d69a2e9bf43ad36b5f0cd9235a831d906d003935b0b6fbd95c2b80d18e558325.jpg/SHA256E-s358707--d69a2e9bf43ad36b5f0cd9235a831d906d003935b0b6fbd95c2b80d18e558325.jpg+ \ No newline at end of file diff --git a/www/vincent.demeester.fr/images/2014/03/iXnpyHj1JibDI-small.png b/www/vincent.demeester.fr/images/2014/03/iXnpyHj1JibDI-small.png @@ -0,0 +1 @@ +../../../../../.git/annex/objects/71/60/SHA256E-s1633871--87b97ff1bdc8197ece9476a38f56ff20c3e5a31e37e18fbf6f16edfa2289739c.png/SHA256E-s1633871--87b97ff1bdc8197ece9476a38f56ff20c3e5a31e37e18fbf6f16edfa2289739c.png+ \ No newline at end of file diff --git a/www/vincent.demeester.fr/images/2014/03/wallpaper-1470931.jpg b/www/vincent.demeester.fr/images/2014/03/wallpaper-1470931.jpg @@ -0,0 +1 @@ +../../../../../.git/annex/objects/6G/Vp/SHA256E-s621197--7f7762eb56ed58c2646550ec497a7038d2306b4d2a96568e1539b0a014a40231.jpg/SHA256E-s621197--7f7762eb56ed58c2646550ec497a7038d2306b4d2a96568e1539b0a014a40231.jpg+ \ No newline at end of file diff --git a/www/vincent.demeester.fr/images/2016/firefox-place-awesomebar-example.png b/www/vincent.demeester.fr/images/2016/firefox-place-awesomebar-example.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/2016/firefox-place-bookmark-toolbar.png b/www/vincent.demeester.fr/images/2016/firefox-place-bookmark-toolbar.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/2016/firefox-place-most-visited-today.png b/www/vincent.demeester.fr/images/2016/firefox-place-most-visited-today.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/2019/01/desk1.jpg b/www/vincent.demeester.fr/images/2019/01/desk1.jpg Binary files differ. diff --git a/www/vincent.demeester.fr/images/2020-04-15-16-12-54.png b/www/vincent.demeester.fr/images/2020-04-15-16-12-54.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/bg.png b/www/vincent.demeester.fr/images/bg.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/bg_blue.png b/www/vincent.demeester.fr/images/bg_blue.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/bg_gray.png b/www/vincent.demeester.fr/images/bg_gray.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/bg_green.png b/www/vincent.demeester.fr/images/bg_green.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/bg_red.png b/www/vincent.demeester.fr/images/bg_red.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/bg_yellow.png b/www/vincent.demeester.fr/images/bg_yellow.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/button-blue.png b/www/vincent.demeester.fr/images/button-blue.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/button-green.png b/www/vincent.demeester.fr/images/button-green.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/button-light-blue.png b/www/vincent.demeester.fr/images/button-light-blue.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/button-orange.png b/www/vincent.demeester.fr/images/button-orange.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/button-red.png b/www/vincent.demeester.fr/images/button-red.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/button-white.png b/www/vincent.demeester.fr/images/button-white.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/button-yellow.png b/www/vincent.demeester.fr/images/button-yellow.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/comments.png b/www/vincent.demeester.fr/images/comments.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/emacs/2020-02-29-13-46-08.png b/www/vincent.demeester.fr/images/emacs/2020-02-29-13-46-08.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/emacs/2020-02-29-14-41-59.png b/www/vincent.demeester.fr/images/emacs/2020-02-29-14-41-59.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/emacs/2020-03-03-21-57-41.png b/www/vincent.demeester.fr/images/emacs/2020-03-03-21-57-41.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/emacs/2020-03-03-21-59-22.png b/www/vincent.demeester.fr/images/emacs/2020-03-03-21-59-22.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/email.png b/www/vincent.demeester.fr/images/email.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/favicon.ico b/www/vincent.demeester.fr/images/favicon.ico Binary files differ. diff --git a/www/vincent.demeester.fr/images/favicon.png b/www/vincent.demeester.fr/images/favicon.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/fsfe.blogs.png b/www/vincent.demeester.fr/images/fsfe.blogs.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/github.png b/www/vincent.demeester.fr/images/github.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/identi.png b/www/vincent.demeester.fr/images/identi.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/imessage.png b/www/vincent.demeester.fr/images/imessage.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/key.png b/www/vincent.demeester.fr/images/key.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/lastfm.png b/www/vincent.demeester.fr/images/lastfm.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/linkedin.png b/www/vincent.demeester.fr/images/linkedin.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/logo.shortbrain.png b/www/vincent.demeester.fr/images/logo.shortbrain.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/sandbox/long-img.png b/www/vincent.demeester.fr/images/sandbox/long-img.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/sandbox/pic-demo.png b/www/vincent.demeester.fr/images/sandbox/pic-demo.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/sandbox/some_filename.png b/www/vincent.demeester.fr/images/sandbox/some_filename.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/sbr-big.png b/www/vincent.demeester.fr/images/sbr-big.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/sbr-medium.png b/www/vincent.demeester.fr/images/sbr-medium.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/sbr-small.png b/www/vincent.demeester.fr/images/sbr-small.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/social-rss.png b/www/vincent.demeester.fr/images/social-rss.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/stackoverflow.png b/www/vincent.demeester.fr/images/stackoverflow.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/star.png b/www/vincent.demeester.fr/images/star.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/tekton/tekton-horizontal-color.png b/www/vincent.demeester.fr/images/tekton/tekton-horizontal-color.png @@ -0,0 +1 @@ +../../../../.git/annex/objects/07/P3/SHA256E-s235244--ffbafb40375b39fd398efc1f2be318a89e5a0245af726af802317b4f61b2a74d.png/SHA256E-s235244--ffbafb40375b39fd398efc1f2be318a89e5a0245af726af802317b4f61b2a74d.png+ \ No newline at end of file diff --git a/www/vincent.demeester.fr/images/user_gray.png b/www/vincent.demeester.fr/images/user_gray.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/vde.png b/www/vincent.demeester.fr/images/vde.png @@ -0,0 +1 @@ +../../../.git/annex/objects/J2/0X/SHA256E-s266717--a54f64874c8bb24992190a618a929571491b40b1b04645fd3e8012243fbac997.png/SHA256E-s266717--a54f64874c8bb24992190a618a929571491b40b1b04645fd3e8012243fbac997.png+ \ No newline at end of file diff --git a/www/vincent.demeester.fr/images/www-inspirations/Screenshot_2020-03-22 Blogging with Emacs, and Emacs only.png b/www/vincent.demeester.fr/images/www-inspirations/Screenshot_2020-03-22 Blogging with Emacs, and Emacs only.png @@ -0,0 +1 @@ +../../../../.git/annex/objects/xM/Qp/SHA256E-s971495--728a775298fbed863232da8ce70d2c62e9e415c7445ee4a4071530d08645fce0.png/SHA256E-s971495--728a775298fbed863232da8ce70d2c62e9e415c7445ee4a4071530d08645fce0.png+ \ No newline at end of file diff --git a/www/vincent.demeester.fr/images/www-inspirations/Screenshot_2020-03-22 GTD in 15 minutes – A Pragmatic Guide to Getting Things Done.png b/www/vincent.demeester.fr/images/www-inspirations/Screenshot_2020-03-22 GTD in 15 minutes – A Pragmatic Guide to Getting Things Done.png @@ -0,0 +1 @@ +../../../../.git/annex/objects/91/Kp/SHA256E-s2259811--97fb71efcef48811aea10b5ef941da4ac514abf335147483b448f27489f53b67.png/SHA256E-s2259811--97fb71efcef48811aea10b5ef941da4ac514abf335147483b448f27489f53b67.png+ \ No newline at end of file diff --git a/www/vincent.demeester.fr/images/www-inspirations/Screenshot_2020-03-22 Idioms of Dynamic Languages Will Crichton.png b/www/vincent.demeester.fr/images/www-inspirations/Screenshot_2020-03-22 Idioms of Dynamic Languages Will Crichton.png @@ -0,0 +1 @@ +../../../../.git/annex/objects/2P/wx/SHA256E-s2427851--8c0acbaa75df2f509487d9ba5e9c1c2d47989d600c789e0c61509b229bc25685.png/SHA256E-s2427851--8c0acbaa75df2f509487d9ba5e9c1c2d47989d600c789e0c61509b229bc25685.png+ \ No newline at end of file diff --git a/www/vincent.demeester.fr/images/www-inspirations/Screenshot_2020-03-22 Local-first software You own your data, in spite of the cloud.png b/www/vincent.demeester.fr/images/www-inspirations/Screenshot_2020-03-22 Local-first software You own your data, in spite of the cloud.png @@ -0,0 +1 @@ +../../../../.git/annex/objects/3x/5K/SHA256E-s6491477--9b3d2c4cd13b9586d14c382bf9ac2596aa1bc555696655f22b856e6bdb35c43a.png/SHA256E-s6491477--9b3d2c4cd13b9586d14c382bf9ac2596aa1bc555696655f22b856e6bdb35c43a.png+ \ No newline at end of file diff --git a/www/vincent.demeester.fr/images/www-inspirations/Screenshot_2020-03-22 Peter Bourgon · Programming with errors.png b/www/vincent.demeester.fr/images/www-inspirations/Screenshot_2020-03-22 Peter Bourgon · Programming with errors.png @@ -0,0 +1 @@ +../../../../.git/annex/objects/8F/f7/SHA256E-s268614--2c2d98aededc35ed1661d5f5b162c54642011fd37af271e61d25f63677cf4a03.png/SHA256E-s268614--2c2d98aededc35ed1661d5f5b162c54642011fd37af271e61d25f63677cf4a03.png+ \ No newline at end of file diff --git a/www/vincent.demeester.fr/images/www-inspirations/Screenshot_2020-03-22 RSS feeds and how to use them.png b/www/vincent.demeester.fr/images/www-inspirations/Screenshot_2020-03-22 RSS feeds and how to use them.png @@ -0,0 +1 @@ +../../../../.git/annex/objects/X4/Z9/SHA256E-s227063--c81d8b3d8924cd772af639ac8c9e805f8d7e4396e99ae90184367bebd16dd07c.png/SHA256E-s227063--c81d8b3d8924cd772af639ac8c9e805f8d7e4396e99ae90184367bebd16dd07c.png+ \ No newline at end of file diff --git a/www/vincent.demeester.fr/images/www-inspirations/Screenshot_2020-03-22 Subset Park Digital Minimalism for the Working Hacker.png b/www/vincent.demeester.fr/images/www-inspirations/Screenshot_2020-03-22 Subset Park Digital Minimalism for the Working Hacker.png @@ -0,0 +1 @@ +../../../../.git/annex/objects/FX/Z4/SHA256E-s1280552--b7d115e8e6e729e874568c04fbce5ba3fece260cbea0ad7fef543f70fb5841ac.png/SHA256E-s1280552--b7d115e8e6e729e874568c04fbce5ba3fece260cbea0ad7fef543f70fb5841ac.png+ \ No newline at end of file diff --git a/www/vincent.demeester.fr/images/www-inspirations/Screenshot_2020-03-22 Vincent Demeester.png b/www/vincent.demeester.fr/images/www-inspirations/Screenshot_2020-03-22 Vincent Demeester.png @@ -0,0 +1 @@ +../../../../.git/annex/objects/pJ/6Q/SHA256E-s1401227--14f5799ec65c28c4f19d69dcff697e9f4eac0ca9bc8ddcb6e783c9fa75ce45d2.png/SHA256E-s1401227--14f5799ec65c28c4f19d69dcff697e9f4eac0ca9bc8ddcb6e783c9fa75ce45d2.png+ \ No newline at end of file diff --git a/www/vincent.demeester.fr/images/www-inspirations/Screenshot_2020-03-22 Zettelkasten.png b/www/vincent.demeester.fr/images/www-inspirations/Screenshot_2020-03-22 Zettelkasten.png Binary files differ. diff --git a/www/vincent.demeester.fr/images/www-inspirations/Screenshot_2020-03-22 kind.png b/www/vincent.demeester.fr/images/www-inspirations/Screenshot_2020-03-22 kind.png @@ -0,0 +1 @@ +../../../../.git/annex/objects/jM/wj/SHA256E-s353626--737d6587861cf35afa6efb1346dea5ba34898302ed5f6ebbda7502b835631be4.png/SHA256E-s353626--737d6587861cf35afa6efb1346dea5ba34898302ed5f6ebbda7502b835631be4.png+ \ No newline at end of file diff --git a/www/vincent.demeester.fr/index.org b/www/vincent.demeester.fr/index.org @@ -0,0 +1,14 @@ +#+TITLE: Vincent Demeester +#+FILETAGS: home index +#+LINK: monorepo https://git.sr.ht/~vdemeester/home + +#+BEGIN_EXPORT html +<header> +<h1>Hi, I'm Vincent Demeester</h1> +<p>I'm a <em>french</em> developer 🐻, Gopher 🐹, sysadmin 🐺, <abbr title="who does all kinds of work"> factotum</abbr> 🦁, <span class="fan"><abbr title="as in free speek">free</abbr>-software <abbr title="You could say Evangelist">fan</abbr></span> 👼 and unicode lover 🐸. I'm working <a href="https://www.redhat.com">RedHat</a> 🎩 as a principal software engineer, previously at <a href="https://www.docker.com">Docker</a> 🐳 and <a href="http://www.zenika.com/">Zenika</a> 🐯. I am a maintainer of the <a href="https://docker.com">docker project (<a href="https://github.com/moby/moby"><code>moby/moby</code></a>, <a href="https://github.com/docker/cli"><code>docker/cli</code></a>, …), the <a href="https://github.com/tektoncd">TektonCD</a> project (<code>tektoncd/*</code>) and a tiny bit of <a href="https://github.com/NixOS/nixpkgs">NixOS</a>.</p> +</header> +#+END_EXPORT + +#+BEGIN_COMMENT +{{{title}}} +#+END_COMMENT diff --git a/www/vincent.demeester.fr/legacy/posts/2012-05-07-reinit-and-jekyll/index.html b/www/vincent.demeester.fr/legacy/posts/2012-05-07-reinit-and-jekyll/index.html @@ -0,0 +1,161 @@ +<!DOCTYPE html> + +<html lang="en"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2012-05-07-reinit-and-jekyll/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang="" class="gray"> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">Reinit and Jekyll</h1><a href='https://vincent.demeester.fr/posts/2012-05-07-reinit-and-jekyll/'></a> + <address class="signature"> + <span class="date">Mon, 7 May, 2012</span> + <span class="words">(300 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#me">me</a></li> + + + + + + <li class="tag tag-intro"><a href="/tags/#intro">intro<span>1</span></a></li> + + + <li class="tag tag-jekyll"><a href="/tags/#jekyll">jekyll<span>4</span></a></li> + + <br/> + + </ul> + </header> + + + + + +<p>Two weeks ago, my <em>online</em> personal server has been attacked and, somehow, died. I’m in the process +of re-installation of it but I’m going to hardened a bit the security on it. Anyway, this crash meant +that every piece of site I maintain has been down. That’s why I moved this <em>identity site</em> on the +github pages, using a CNAME ; That way I can crash as much as I want my server(s), this page should +still be up for a while.</p> + +<p>And I’m switching on Jekyll for this website as It is supported by Github page, easy to use and easy +to deploy elsewhere (if one day I want to move from Github).</p> + +<p>The rest of the post is going to be used as a <em>sandbox</em> post to test the site styles.</p> + +<blockquote> +<p>This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, +consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. +Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.</p> + +<p>Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse +id sem consectetuer libero luctus adipiscing.</p> +</blockquote> + +<h2 id="highlight">Highlight</h2> + +<div class="highlight"><pre class="chroma"><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">foo</span> + <span class="nb">puts</span> <span class="s1">'foo'</span> +<span class="k">end</span></code></pre></div> + +<p>Some bash script…</p> + +<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="cp">#!/bin/bash +</span><span class="cp"></span>update_gems<span class="o">()</span> <span class="o">{</span> + <span class="nb">echo</span> <span class="s2">"Update gems for all versions ? (y/N)"</span> + <span class="nb">read</span> UPDATE_GEMS + <span class="nb">test</span> -z <span class="s2">"</span><span class="si">${</span><span class="nv">UPDATE_GEMS</span><span class="si">}</span><span class="s2">"</span> <span class="o">&&</span> <span class="nv">UPDATE_GEMS</span><span class="o">=</span><span class="s2">"n"</span> + <span class="k">if</span> <span class="nb">test</span> <span class="s2">"</span><span class="si">${</span><span class="nv">UPDATE_GEMS</span><span class="si">}</span><span class="s2">"</span> <span class="o">=</span> <span class="s2">"y"</span><span class="p">;</span> <span class="k">then</span> + <span class="k">for</span> version in <span class="sb">`</span>ls --color<span class="o">=</span>never <span class="nv">$HOME</span>/.rbenv/versions<span class="sb">`</span><span class="p">;</span> <span class="k">do</span> + <span class="nb">echo</span> <span class="s2">"Updating </span><span class="si">${</span><span class="nv">version</span><span class="p">%/</span><span class="si">}</span><span class="s2">"</span> + <span class="nv">RBENV_VERSION</span><span class="o">=</span><span class="s2">"</span><span class="si">${</span><span class="nv">version</span><span class="p">%/</span><span class="si">}</span><span class="s2">"</span> rbenv <span class="nb">exec</span> gem update + <span class="nv">RBENV_VERSION</span><span class="o">=</span><span class="s2">"</span><span class="si">${</span><span class="nv">version</span><span class="p">%/</span><span class="si">}</span><span class="s2">"</span> rbenv <span class="nb">exec</span> gem install bundler + <span class="k">done</span> + <span class="k">fi</span> +<span class="o">}</span> + +update_gems</code></pre></div> + + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2012-05-08-gitolite-quick-and-dirty-mirror/" title="Gitolite quick and dirty mirror">← Previous post</a> + + + + <a class="paging-link next" href="/about/" title="About">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2012-05-08-gitolite-quick-and-dirty-mirror/index.html b/www/vincent.demeester.fr/legacy/posts/2012-05-08-gitolite-quick-and-dirty-mirror/index.html @@ -0,0 +1,243 @@ +<!DOCTYPE html> + +<html lang="en"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2012-05-08-gitolite-quick-and-dirty-mirror/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang="" class="gray"> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">Gitolite quick and dirty mirror</h1><a href='https://vincent.demeester.fr/posts/2012-05-08-gitolite-quick-and-dirty-mirror/'></a> + <address class="signature"> + <span class="date">Tue, 8 May, 2012</span> + <span class="words">(700 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#developement">developement</a></li> + + + + + + <li class="tag tag-gitolite"><a href="/tags/#gitolite">gitolite<span>1</span></a></li> + + + <li class="tag tag-git"><a href="/tags/#git">git<span>3</span></a></li> + + + <li class="tag tag-linux"><a href="/tags/#linux">linux<span>4</span></a></li> + + + <li class="tag tag-mirror"><a href="/tags/#mirror">mirror<span>1</span></a></li> + + + <li class="tag tag-github"><a href="/tags/#github">github<span>2</span></a></li> + + <br/> + + </ul> + </header> + + + + + +<p>I’m running a gitolite <em>instance</em> on my personal server to manage my repositories +(personnal, private or public) ; and I am quickly going to share with you how I +setup a <em>quick and dirty</em> mirror feature.</p> + +<p>First, I am using <strong>gitolite 3</strong>. The mirroring we are going to setup is not the +<em>supported</em> <a href="http://sitaramc.github.com/gitolite/mirroring.html">mirroring <strong>built-in</strong></a>. +We are going to implement a simplier way to set mirror thing :</p> + +<ol> +<li>Write a custom gitolite command ; the idea is to be able to write <code>git-config</code> +stuff.</li> +<li>Write a hook that take a specific <code>git-config</code> (let say <code>mirror.url</code>) and do +a simple mirroring.</li> +</ol> + +<h1 id="gitolite-commands">Gitolite commands</h1> + +<p>Gitolite 3 has been rewritten to be more flexible : <a href="http://sitaramc.github.com/gitolite/g3why.html">Why a completely new version</a>. +The rewrite made it really easy to extend gitolite. <del>I’ve fork <a href="https://github.com/vdemeester/gitolite">gitolite</a> on github</del> +I’ve created a <a href="http://github.com/vdemeester/vdemeester-gitolite-local-code">repository git</a> +to easily add commands to my gitolite instance via <em>local code</em>. The gitolite command I wrote is +a quick and dirty script in shell to add <code>git config</code>. The source should speek +for itself ; It <em>should</em> include some way to check if the given config is not +already present in the <code>gitolite-admin</code> configuration file — and so might be +rewritten in <code>Perl</code>.</p> + +<p>The command is <code>write-git-config</code> because a <code>git-config</code> command already exists +in the built-in commands.</p> + +<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="cp">#!/bin/sh +</span><span class="cp"></span> +<span class="c1"># Usage: ssh git@host write-git-config <repo> <key> <value></span> +# +<span class="c1"># Set git-config value for user-created ("wild") repo.</span> + +die<span class="o">()</span> <span class="o">{</span> <span class="nb">echo</span> <span class="s2">"</span><span class="nv">$@</span><span class="s2">"</span> ><span class="p">&</span><span class="m">2</span><span class="p">;</span> <span class="nb">exit</span> <span class="m">1</span><span class="p">;</span> <span class="o">}</span> +usage<span class="o">()</span> <span class="o">{</span> perl -lne <span class="s1">'print substr($_, 2) if /^# Usage/../^$/'</span> < <span class="nv">$0</span><span class="p">;</span> <span class="nb">exit</span> <span class="m">1</span><span class="p">;</span> <span class="o">}</span> +<span class="o">[</span> -z <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> <span class="o">]</span> <span class="o">&&</span> <span class="o">[</span> -z <span class="s2">"</span><span class="nv">$2</span><span class="s2">"</span> <span class="o">]</span> <span class="o">&&</span> <span class="o">[</span> -z <span class="s2">"</span><span class="nv">$3</span><span class="s2">"</span> <span class="o">]</span> <span class="o">&&</span> usage +<span class="o">[</span> <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> <span class="o">=</span> <span class="s2">"-h"</span> <span class="o">]</span> <span class="o">&&</span> usage +<span class="o">[</span> -z <span class="s2">"</span><span class="nv">$GL_USER</span><span class="s2">"</span> <span class="o">]</span> <span class="o">&&</span> die GL_USER not <span class="nb">set</span> + +<span class="c1"># ----------------------------------------------------------------------</span> +<span class="nv">repo</span><span class="o">=</span><span class="nv">$1</span><span class="p">;</span> <span class="nb">shift</span> +<span class="nv">key</span><span class="o">=</span><span class="nv">$1</span><span class="p">;</span> <span class="nb">shift</span> +<span class="nv">value</span><span class="o">=</span><span class="nv">$1</span><span class="p">;</span> <span class="nb">shift</span> + +<span class="c1"># this shell script takes arguments that are completely under the user's</span> +<span class="c1"># control, so make sure you quote those suckers!</span> + +<span class="k">if</span> gitolite query-rc -q WRITER_CAN_UPDATE_DESC +<span class="k">then</span> + gitolite access -q <span class="s2">"</span><span class="nv">$repo</span><span class="s2">"</span> <span class="nv">$GL_USER</span> W any <span class="o">||</span> die You are not authorised +<span class="k">else</span> + gitolite creator <span class="s2">"</span><span class="nv">$repo</span><span class="s2">"</span> <span class="nv">$GL_USER</span> <span class="o">||</span> die You are not authorised +<span class="k">fi</span> + +<span class="c1"># if it passes, $repo is a valid repo name so it is known to contain only sane</span> +<span class="c1"># characters. This is because 'gitolite creator' return true only if there</span> +<span class="c1"># *is* a repo of that name and it has a gl-creator file that contains the same</span> +<span class="c1"># text as $GL_USER.</span> + +<span class="nv">configfile</span><span class="o">=</span><span class="sb">`</span>gitolite query-rc GL_REPO_BASE<span class="sb">`</span>/<span class="s2">"</span><span class="nv">$repo</span><span class="s2">"</span>.git/config + +git config --file <span class="s2">"</span><span class="nv">$configfile</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$key</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$value</span><span class="s2">"</span></code></pre></div> + +<h1 id="gitolite-hooks">Gitolite hooks</h1> + +<p>The next step is to write a quick <code>post-receive</code> hook that check if there is a +certain <code>git-config</code> entry and run <code>git push --mirror</code>. The file is in +<code>$HOME/.gitolite/hooks/common/post-receive</code> ; you could add a better system to +hooks (to be able to add “dynamic” hooks, …).</p> + +<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="cp">#!/bin/sh +</span><span class="cp"></span> +<span class="c1"># Simple gitolite mirroring</span> + +<span class="c1"># flush STDIN coming from git, because gitolite's own post-receive.mirrorpush</span> +<span class="c1"># script does the same thing</span> +<span class="o">[</span> -t <span class="m">0</span> <span class="o">]</span> <span class="o">||</span> cat >/dev/null + +<span class="o">[</span> -z <span class="s2">"</span><span class="nv">$GL_REPO</span><span class="s2">"</span> <span class="o">]</span> <span class="o">&&</span> die GL_REPO not <span class="nb">set</span> + +<span class="nv">target</span><span class="o">=</span><span class="sb">`</span>git config --get mirror.url<span class="sb">`</span> +<span class="o">[</span> -z <span class="s2">"</span><span class="nv">$target</span><span class="s2">"</span> <span class="o">]</span> <span class="o">&&</span> <span class="nb">exit</span> <span class="m">0</span> + +<span class="c1"># Support a REPO variable for wildcard mirrors</span> +<span class="nv">gl_repo_escaped</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> <span class="nv">$GL_REPO</span> <span class="p">|</span> sed <span class="s1">'s/\//\\\//g'</span><span class="k">)</span> +<span class="nv">target</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> <span class="nv">$target</span> <span class="p">|</span> sed -e <span class="s2">"s/REPO/</span><span class="nv">$gl_repo_escaped</span><span class="s2">/g"</span><span class="k">)</span> + +<span class="c1"># Do the mirror push</span> +git push --mirror <span class="nv">$target</span></code></pre></div> + +<p>The next, and final step is to run <code>gitolite compile</code> to update links to hooks +for every repositories.</p> + +<h1 id="for-real">For real</h1> + +<p>And finaly, this is the final step you’ll do.</p> + +<pre><code>$ ssh git@host write-git-config vincent/vcsh-home mirror.url git@github.com:vdemeester/vcsh-home.git +$ git push +Counting objects: 5, done. +Delta compression using up to 2 threads. +Compressing objects: 100% (3/3), done. +Writing objects: 100% (3/3), 294 bytes, done. +Total 3 (delta 2), reused 0 (delta 0) +remote: To git@github.com:vdemeester/vcsh-home.git +remote: 65681a8..701c990 master -> master +To git@host:vincent/vcsh-home.git + 65681a8..701c990 master -> master +</code></pre> + +<p>And that should be it !</p> + +<p><strong>Update 2012/10/04</strong> : Moved from gitolite fork to <em>gitolite local code</em> +repository.</p> + + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2012-05-13-jekyll-foreman-guard-bundler/" title="Jekyll Forman Guard Bundler">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2012-05-07-reinit-and-jekyll/" title="Reinit and Jekyll">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2012-05-13-jekyll-foreman-guard-bundler/index.html b/www/vincent.demeester.fr/legacy/posts/2012-05-13-jekyll-foreman-guard-bundler/index.html @@ -0,0 +1,194 @@ +<!DOCTYPE html> + +<html lang="en"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2012-05-13-jekyll-foreman-guard-bundler/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang="" class="gray"> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">Jekyll Forman Guard Bundler</h1><a href='https://vincent.demeester.fr/posts/2012-05-13-jekyll-foreman-guard-bundler/'></a> + <address class="signature"> + <span class="date">Sun, 13 May, 2012</span> + <span class="words">(300 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#developement">developement</a></li> + + + + + + <li class="tag tag-jekyll"><a href="/tags/#jekyll">jekyll<span>4</span></a></li> + + + <li class="tag tag-ruby"><a href="/tags/#ruby">ruby<span>1</span></a></li> + + + <li class="tag tag-bundler"><a href="/tags/#bundler">bundler<span>1</span></a></li> + + + <li class="tag tag-guard"><a href="/tags/#guard">guard<span>1</span></a></li> + + + <li class="tag tag-foreman"><a href="/tags/#foreman">foreman<span>1</span></a></li> + + <br/> + + </ul> + </header> + + + + + +<p>This post is a quick “How did I setup my Jekyll environnement ?”. We are going +all the tools that are quite awesome in Ruby.</p> + +<h1 id="goal">Goal</h1> + +<p>The goal is simple :</p> + +<ol> +<li>I want to be able to install any dependent <a href="http://rubygems.org">Gem</a> with a +<em>on-liner</em> command</li> +<li>I want to be able to run a <em>Jekyll server</em> that auto updates.</li> +</ol> + +<p>We are going to play with : <a href="http://gembundler.com/">Bundler</a>, +<a href="https://github.com/guard/guard">Guard</a> and <a href="https://github.com/ddollar/foreman">foreman</a>.</p> + +<h2 id="bundler">Bundler</h2> + +<p>Bundler let us run <code>bundle install</code> to get all Ruby Gems we will need ; It use +a file name <code>Gemfile</code>. The gems we need are simple : <code>jekyll</code>, <code>guard</code> and some +Guard extensions.</p> + +<div class="highlight"><pre class="chroma"><code class="language-ruby" data-lang="ruby"><span class="n">source</span> <span class="s2">"http://rubygems.org"</span> + +<span class="n">gem</span> <span class="s1">'jekyll'</span> +<span class="n">gem</span> <span class="s1">'guard'</span> +<span class="n">gem</span> <span class="s1">'guard-jekyll2'</span> +<span class="n">gem</span> <span class="s1">'guard-shell'</span> +<span class="n">gem</span> <span class="s1">'guard-bundler'</span></code></pre></div> + +<h2 id="guard">Guard</h2> + +<blockquote> +<p>Guard is a command line tool to easily handle events on file system modifications.</p> +</blockquote> + +<p>Guard will be watching file we told him and run action in consequence ; The file +is name <code>Guardfile</code>.</p> + +<div class="highlight"><pre class="chroma"><code class="language-ruby" data-lang="ruby"><span class="n">guard</span> <span class="s1">'jekyll2'</span> <span class="k">do</span> + <span class="n">watch</span> <span class="sr">%r{.*}</span> +<span class="k">end</span> + +<span class="n">guard</span> <span class="ss">:bundler</span> <span class="k">do</span> + <span class="n">watch</span><span class="p">(</span><span class="s1">'Gemfile'</span><span class="p">)</span> +<span class="k">end</span> +<span class="c1"># vim:filetype=ruby</span></code></pre></div> + +<h2 id="foreman">Foreman</h2> + +<p>Finally, foreman will let us declare our processes and will handle the start, +forward the output and handle the shutdown. It can then export its configuration +into more <em>production-ready</em> file (<code>init</code>, <code>upstard</code>, …) ; It uses a file named +<code>Procfile</code>.</p> + +<p>We will tell foreman to run :</p> + +<ul> +<li>The jekyll build-in server : <code>jekyll --server</code></li> +<li>Guard, to handle file changes <em>in background</em>.</li> +</ul> + +<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">web: bundle <span class="nb">exec</span> jekyll --server +guard: bundle <span class="nb">exec</span> guard</code></pre></div> + +<p>And that’s all folk. Now, you just need to run foreman in the Jekyll-powered +directory and edit your files.</p> + + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2012-07-21-news/" title="News">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2012-05-08-gitolite-quick-and-dirty-mirror/" title="Gitolite quick and dirty mirror">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2012-07-21-news/index.html b/www/vincent.demeester.fr/legacy/posts/2012-07-21-news/index.html @@ -0,0 +1,166 @@ +<!DOCTYPE html> + +<html lang="fr"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2012-07-21-news/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang="fr" class="gray"> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">News</h1><a href='https://vincent.demeester.fr/posts/2012-07-21-news/'></a> + <address class="signature"> + <span class="date">Sat, 21 July, 2012</span> + <span class="words">(300 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#me">me</a></li> + + + + + + <li class="tag tag-news"><a href="/tags/#news">news<span>1</span></a></li> + + + <li class="tag tag-cyclisme"><a href="/tags/#cyclisme">cyclisme<span>1</span></a></li> + + + <li class="tag tag-vaadin"><a href="/tags/#vaadin">vaadin<span>1</span></a></li> + + + <li class="tag tag-java"><a href="/tags/#java">java<span>4</span></a></li> + + <br/> + + </ul> + </header> + + + + <p>Je suis resté assez longtemps silencieux, à cause du boulot entre autre. Je +vais donc remédier un peu à ça en publiant un petit post <em>news</em> :-).</p> + +<ul> +<li>Je suis fan de cyclisme. Mon vélo de course est ma seconde paire de jambes. +Enfin, le mot juste serait plutôt était. Il y a quelques semaines, en +rentrant du boulot, j’ai sentis un grosse vibration, puis plus moyen de +tourner les pédales. Intrigué, j’ai jeté un coup d’œil au vélo, et <em>oh rage +oh désespoir</em> j’ai découvert que la <em>fourche arrière</em> du vélo c’était tout +simplement fendue.</li> +</ul> + +<p><img src="/images/2012/velo_casse.jpg" alt="Fourche arrière du vélo" title="Fourche arrière du vélo" /></p> + +<p>Il ne me restait plus que mes yeux pour pleurer et mes jambes pour faire les + 7kms qui restait à effectuer jusqu’à mon appartement.</p> + +<p>Bien entendu ce n’est que matériel mais ce vélo avait une grande importance + sentimentale : il avait mon âge et appartenait à mon père quand j’étais + petit.</p> + +<p>Maintenant il ne reste plus qu’à sortir la carte bleue pour m’en procurer un + nouveau…</p> + +<ul> +<li><p>Je suis actuellement en vacances pour deux petites semaines (ouf !). Cela me +permet, entre autre, de me réentrainer à monter des cols (vu que je suis +dans les Alpes) ; parce qu’à Bordeaux, c’est assez difficile d’en trouver. +Par contre il me reste beaucoup de boulot pour retrouver ma forme d’il +y a une dizaine d’année et de monter facilement le <em>Grand Colombier</em> !</p></li> + +<li><p>Je joue pas mal avec <a href="http://vaadin.com">Vaadin</a> en ce moment, +principalement pour le boulot. J’annonce donc deux petits projets en cours :</p> + +<ol> +<li><a href="http://vdemeester.github.com/vaadin-container-utils">vaadin-container-utils</a></li> +<li>librarie utilitaire pour la création de Container vaadin.</li> +<li><a href="http://vdemeester.github.com/vaadin-composite-layout">vaadin-composite-layout</a></li> +<li>un composant vaadin hautement flexible (mais je suis mauvais pour les +noms).</li> +</ol></li> +</ul> + +<p>Et “c’est tout pour le moment”.</p> + + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2012-07-23-maven-release-gitflow/" title="Maven Release Gitflow">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2012-05-13-jekyll-foreman-guard-bundler/" title="Jekyll Forman Guard Bundler">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2012-07-23-maven-release-gitflow/index.html b/www/vincent.demeester.fr/legacy/posts/2012-07-23-maven-release-gitflow/index.html @@ -0,0 +1,204 @@ +<!DOCTYPE html> + +<html lang="fr"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2012-07-23-maven-release-gitflow/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang="fr" class="gray"> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">Maven Release Gitflow</h1><a href='https://vincent.demeester.fr/posts/2012-07-23-maven-release-gitflow/'></a> + <address class="signature"> + <span class="date">Mon, 23 July, 2012</span> + <span class="words">(400 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#developement">developement</a></li> + + + + + + <li class="tag tag-maven"><a href="/tags/#maven">maven<span>2</span></a></li> + + + <li class="tag tag-java"><a href="/tags/#java">java<span>4</span></a></li> + + + <li class="tag tag-git"><a href="/tags/#git">git<span>3</span></a></li> + + + <li class="tag tag-gitflow"><a href="/tags/#gitflow">gitflow<span>1</span></a></li> + + + <li class="tag tag-release"><a href="/tags/#release">release<span>1</span></a></li> + + <br/> + + </ul> + </header> + + + + + +<p>I like a lot the <a href="http://nvie.com/posts/a-successful-git-branching-model/">gitflow</a> way of managing project. +When working on maven project, there is few great plugins that helps to get +the work done. One of them is <a href="http://maven.apache.org/plugins/maven-release-plugin">maven-release-plugin</a>.</p> + +<p>Inspired on this <a href="https://gist.github.com/1043970">gist</a>, I’ve come with +a cool way of doing things (let say we want to release a 0.1 version of an +artifact) :</p> + +<h1 id="prepare-the-pom-xml">Prepare the pom.xml.</h1> + +<p>It needs <code><scm></code> entries, <code><distributionManagement></code> entries +(to know where to deploy the release artifact) and few options for the +maven-release-plugin :</p> + +<div class="highlight"><pre class="chroma"><code class="language-xml" data-lang="xml"><span class="nt"><project></span> + + <span class="c"><!-- […] --></span> + <span class="nt"><build></span> + <span class="nt"><plugins></span> + <span class="c"><!-- […] --></span> + <span class="nt"><plugin></span> + <span class="nt"><groupId></span>org.apache.maven.plugins<span class="nt"></groupId></span> + <span class="nt"><artifactId></span>maven-release-plugin<span class="nt"></artifactId></span> + <span class="nt"><version></span>2.3.2<span class="nt"></version></span> + <span class="nt"><configuration></span> + <span class="nt"><tagNameFormat></span>v@{project.version}<span class="nt"></tagNameFormat></span> + <span class="nt"><pushChanges></span>false<span class="nt"></pushChanges></span> + <span class="nt"><localCheckout></span>true<span class="nt"></localCheckout></span> + <span class="nt"></configuration></span> + <span class="nt"></plugin></span> + <span class="c"><!-- […] --></span> + <span class="nt"></plugins></span> + <span class="nt"></build></span> + <span class="c"><!-- […] --></span> + +<span class="nt"></project></span></code></pre></div> + +<p>Few explanation here :</p> + +<ul> +<li><code>tagNameFormat</code> is here to change the default tag name (which is <code>${project.artifactId}-${project.version}</code>) to a better one.</li> +<li><code>pushChanges</code> set to <code>false</code> tells maven-release-plugin not to push +changes (this will become useful)</li> +<li><code>localCheckout</code> set to <code>true</code> tells maven-release-plugin to clone from +local repository (not distant). This is especially useful here because we +didn’t push anything (so not setting this option would result in a failure).</li> +</ul> + +<h1 id="the-real-stuff">The real stuff</h1> + +<p>First create a release branch from develop.</p> + +<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ git checkout -b release/v0.1 develop</code></pre></div> + +<p>Then run the maven release stuff.</p> + +<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ mvn release:prepare <span class="c1"># change the pom, commit and tag version, and</span> + <span class="c1"># re-change pom (by incrementing SNAPSHOT version)</span> +$ mvn release:perform <span class="c1"># get the tagged version, compile and deploy</span></code></pre></div> + +<p>And the real fun begins.</p> + +<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">$ git checkout develop <span class="c1"># get back to the develop branch</span> +$ git merge --no-ff release/v0.1 <span class="c1"># merge the version back into develop</span> +$ git checkout master <span class="c1"># go to the master branch</span> +$ git merge --no-ff release/v0.1~1 <span class="c1"># merge the version back into master but</span> + <span class="c1"># the tagged version instead of the release/v0.1 HEAD</span> +$ git branch -D release/v0.1 <span class="c1"># Removing the release branch</span> +$ git push --all <span class="o">&&</span> git push --tags <span class="c1"># Finally push everything</span></code></pre></div> + +<p>The real magic here is the <code>git merge --no-ff release/v0.1~1</code> which will +merge into master the commit before the HEAD of the branch <code>release/v0.1</code>.</p> + +<p>The next step would be to create a helper script that automates this and +verify that the <code>pom.xml</code> has the right configuration options.</p> + +<p><strong>Edit 17:58</strong> : You can take a look <a href="https://github.com/vdemeester/java-config/blob/master/bin/mvn-release-flow">here</a></p> + + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2012-12-16-gollum-comme-wiki-personnel/" title="Gollum Comme Wiki Personnel">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2012-07-21-news/" title="News">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2012-12-16-gollum-comme-wiki-personnel/index.html b/www/vincent.demeester.fr/legacy/posts/2012-12-16-gollum-comme-wiki-personnel/index.html @@ -0,0 +1,164 @@ +<!DOCTYPE html> + +<html lang="fr"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2012-12-16-gollum-comme-wiki-personnel/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang="fr" class="gray"> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">Gollum Comme Wiki Personnel</h1><a href='https://vincent.demeester.fr/posts/2012-12-16-gollum-comme-wiki-personnel/'></a> + <address class="signature"> + <span class="date">Sun, 16 December, 2012</span> + <span class="words">(500 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#me">me</a></li> + + + + + + <li class="tag tag-wiki"><a href="/tags/#wiki">wiki<span>1</span></a></li> + + + <li class="tag tag-gollum"><a href="/tags/#gollum">gollum<span>1</span></a></li> + + + <li class="tag tag-github"><a href="/tags/#github">github<span>2</span></a></li> + + + <li class="tag tag-personnel"><a href="/tags/#personnel">personnel<span>1</span></a></li> + + <br/> + + </ul> + </header> + + + + + +<p>Il y a environ 4 mois j’ai eu un accident de vélo ; un traumatisme crânien, des brulures sur la face, quelques points de sutures, un doigt cassé et une hernie discale m’ont cloué (et me clou encore) plus que d’habitude sur ma chaise de bureau. Le bon côté des choses, c’est que cela m’a permit de me poser et de réfléchir une bonne façon d’être efficace et organiser, au travail et à la maison :-).</p> + +<p>Une des principales <em>action</em> que j’ai pris est d’utiliser un wiki local et synchronisé sur <em>tout</em> mes PCs. Le <em>format</em> wiki est assez adapté à une prise de note et à la création de contenu plus complet (comme des <a href="http://shortbrain.org">articles</a> ou de la documentation pour des projets en cours). Les conditions étaient les suivantes :</p> + +<ul> +<li>Facilité de mise en place.</li> +<li>Pas de base de données.</li> +<li><em>Merging</em> facile (<a href="http://git-scm.com">git</a> <em>rules my world</em>).</li> +<li><a href="http://daringfireball.net/projects/markdown/">Markdown</a> comme syntaxe, car utilisé à peu près partout (blogs, articles, READMEs, documentations).</li> +<li>Éditable à partir d’une interface web ou de mon éditeur favoris.</li> +</ul> + +<p>L’outil qui remplit presque toutes ces conditions s’appelle <a href="https://github.com/github/gollum">gollum</a>. C’est un moteur wiki, écris en ruby, qui se base sur un repository <a href="http://git-scm.com">git</a>. Il est développer par l’équipe de <a href="http://github.com">Github</a> et c’est celui qui est utilisé par les pages wiki là-bas. Il permet d’utiliser à peu près n’importe quel syntaxe (dont <a href="https://github.com/github/github-flavored-markdown">github-markdown</a> qui est assez proche de celle de <a href="http://johnmacfarlane.net/pandoc">pandoc</a>). Par ailleurs, comme il se base sur <a href="http://git-scm.com">git</a>, les points <em>“pas de base de données”</em>, <em>“merging facile”</em> et <em>“éditable également à partir de mon éditeur favoris”</em> sont toutes remplies.</p> + +<p>Avec <a href="https://github.com/github/gollum">Gollum</a> vous avez un wiki markdown décentralisé, éditable via une interface web ou via votre éditeur favoris.</p> + +<h2 id="mise-en-place">Mise en place</h2> + +<p>La mise en place est relativement simple ; après tout dépend du besoin que vous avez. L’installation se fait par <a href="rubygems.org">RubyGem</a> ou en clonant le repository.</p> + +<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="c1"># Installation de gollum et du format markdown de github</span> +$ gem install gollum gitub-markdown</code></pre></div> + +<p>Si vous n’utilisez pas <a href="https://github.com/sstephenson/rbenv">rbenv</a> ou <a href="https://rvm.io/">rvm</a> il est probable qu’il faille lancer la commande en root ou utiliser sudo.</p> + +<p>Ensuite, il suffit de lancer <a href="https://github.com/github/gollum">Gollum</a> dans un dossier qui est un repository git ; le tour est joué</p> + +<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="c1"># J'ulitise ~/desktop/wiki pour mon wiki</span> +$ <span class="nb">cd</span> ~/desktop/wiki <span class="o">&&</span> gollum</code></pre></div> + +<p>L’idée finale est d’automatiser deux choses :</p> + +<ol> +<li>Le démarrage de gollum</li> +<li>La synchronisation du repository avec les différents autres <em>remotes</em></li> +</ol> + +<p>Suivant le système d’exploitation et/ou la distribution utilisées, il y a énormément de possibilité d’effectuer cette automatisation. Dans mon cas, j’ai une <a href="http://debian.org">Debian</a> assez light, avec surtout plein de scripts. Je démarre donc <a href="https://github.com/github/gollum">Gollum</a> au démarrage de ma session grâce à une script qui est lancé dans la foulée du gestionnaire de fenêtre. La synchronisation se fait grâce à une tâche planifiée <em>cron</em> qui est “distribué” sur chacune de mes machines.</p> + +<p><em>C’est tout pour le moment</em> ;-).</p> + + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2013-09-08-maven-tmpfs/" title="Maven Tmpfs">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2012-07-23-maven-release-gitflow/" title="Maven Release Gitflow">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2013-09-08-maven-tmpfs/index.html b/www/vincent.demeester.fr/legacy/posts/2013-09-08-maven-tmpfs/index.html @@ -0,0 +1,274 @@ +<!DOCTYPE html> + +<html lang="fr"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2013-09-08-maven-tmpfs/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang="fr"/> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">Maven Tmpfs</h1><a href='https://vincent.demeester.fr/posts/2013-09-08-maven-tmpfs/'></a> + <address class="signature"> + <span class="date">Sun, 8 September, 2013</span> + <span class="words">(600 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#developement">developement</a></li> + + + + + + <li class="tag tag-maven"><a href="/tags/#maven">maven<span>2</span></a></li> + + + <li class="tag tag-tmpfs"><a href="/tags/#tmpfs">tmpfs<span>1</span></a></li> + + + <li class="tag tag-ssd"><a href="/tags/#ssd">ssd<span>1</span></a></li> + + <br/> + + </ul> + </header> + + + + <p>Je suis un utilisateur convaincu de [maven](), malgré ces défauts, le moto +<strong>“Convention over configuration”</strong> me va vraiment bien. Que ce soit au boulot ou +à la maison, j’ai plus d’ordinateurs équipés de ssd (ou de mémoire flash) que de +disque traditionnel (mécanique ?). Pour augmenter un peu la durée de vie de +ces disques SSD, j’ai cherché à savoir comment <em>déporter</em> le <em>build</em> de maven +(qui, pour rappel, se passe dans le dossier <code>target/</code>) hors du SSD ; ici ce +sera dans le dossier <code>/tmp/</code> qui est monté en mémoire (merci <code>tmpfs</code>), +mais on peut imaginer déporter ça sur un autre disque, etc.. Après quelques +recherches j’ai trouvés quelques inspirations.</p> + +<blockquote> +<p><strong>Limitations</strong></p> + +<p>Dans la solution présentée ci-dessous les principales limitations sont +les suivantes (que j’essaierais de diminuer au fil du temp ;P) :</p> + +<ol> +<li>Il est nécessaire de modifier le pom.xml du projet ; cela ne s’appliquera +donc pas à tous les projets maven sans modification du pom.xml.</li> +<li>Cela ne fonctionne que sur une plateforme qui support les liens +symboliques (Linux, Mac OS X, et autre UNIX).</li> +<li>Cela ne fonctionne qu’avec Java 7 ou plus.</li> +<li>Si vous utilisez m2e, il va gentillement gueuler et c’est moche ; pour +résoudre le problème, il faut faire un tour vers <a href="http://wiki.eclipse.org/M2E_plugin_execution_not_covered">M2E plugin execution +not covered</a>.</li> +</ol> +</blockquote> + +<p>Pour [maven](), le dossier <code>target/</code> vient de la propriété +<code>project.build.directory</code>. Dans la théorie, il suffirait de modifier (dans +<code>$HOME/.m2/settings.xml</code>) cette propriété et le tour serait jouer. +Malheuresement ce n’est pas possible, <code>project.build.directory</code> est une +propriété interne et n’est, à priori, pas modifiable.</p> + +<p>Notre souhait est le suivant :</p> + +<ol> +<li>Le build doit se faire dans <code>/tmp/m2/</code>, ce qui pour un projet se traduit +par <code>/tmp/m2/${groupId}:${artifactId}</code>.</li> +<li>Le dossier <code>target/</code> dans les sources est un lien symbolique vers le +dossier dans <code>/tmp/m2/</code></li> +<li>On passe par un <strong>profile</strong> qui n’est <strong>pas actif</strong> par défaut (pour ne pas +faire chier le monde) mais <strong>activable via une propriété</strong> (maven nous permet +de le faire et c’est cool <code>^_^</code>). La propriété utilisée sera +<code>external.build.root</code>.</li> +</ol> + +<p>Le code ci-dessous est repris directement de mon inspiration<sup class="footnote-ref" id="fnref:inspiration"><a href="#fn:inspiration">1</a></sup>. Il +s’occupe de créer le dossier <code>${groupId}:${artifactId}</code> dans +<code>external.build.root</code> et de faire le lien dans le dossier courant.</p> + +<div class="highlight"><pre class="chroma"><code class="language-xml" data-lang="xml"><span class="nt"><project></span> + <span class="c"><!-- […] --></span> + <span class="nt"><profiles></span> + <span class="nt"><profile></span> + <span class="nt"><id></span>external-build-dir<span class="nt"></id></span> + <span class="nt"><activation></span> + <span class="nt"><activeByDefault></span>false<span class="nt"></activeByDefault></span> + <span class="nt"><property></span> + <span class="nt"><name></span>external.build.root<span class="nt"></name></span> + <span class="nt"></property></span> + <span class="nt"></activation></span> + <span class="nt"><build></span> + <span class="nt"><plugins></span> + <span class="nt"><plugin></span> + <span class="nt"><groupId></span>com.alexecollins.maven.plugin<span class="nt"></groupId></span> + <span class="nt"><artifactId></span>script-maven-plugin<span class="nt"></artifactId></span> + <span class="nt"><version></span>1.0.0<span class="nt"></version></span> + <span class="nt"><executions></span> + <span class="nt"><execution></span> + <span class="nt"><id></span>prep-work-tree<span class="nt"></id></span> + <span class="nt"><goals></span> + <span class="nt"><goal></span>execute<span class="nt"></goal></span> + <span class="nt"></goals></span> + <span class="nt"><phase></span>initialize<span class="nt"></phase></span> + <span class="nt"><configuration></span> + <span class="nt"><script></span> + import java.nio.file.* + def dir = + "${external.build.root}/${project.groupId}:${project.artifactId}" + println "using Maven dir ${dir}" + def dirPath = Paths.get(dir) + if (!Files.exists(dirPath)) { + Files.createDirectories(dirPath) + } + def target = Paths.get("${project.build.directory}") + if (!Files.exists(target)) { + Files.createSymbolicLink(target, dirPath) + }<span class="nt"></script></span> + <span class="nt"></configuration></span> + <span class="nt"></execution></span> + <span class="nt"><execution></span> + <span class="nt"><id></span>drop-symlink<span class="nt"></id></span> + <span class="nt"><goals></span> + <span class="nt"><goal></span>execute<span class="nt"></goal></span> + <span class="nt"></goals></span> + <span class="nt"><phase></span>clean<span class="nt"></phase></span> + <span class="nt"><configuration></span> + <span class="nt"><script></span> + import java.nio.file.* + def target = Paths.get("${project.build.directory}") + if (Files.isSymbolicLink(target)) { + Files.delete(target) + } + <span class="nt"></script></span> + <span class="nt"></configuration></span> + <span class="nt"></execution></span> + <span class="nt"></executions></span> + <span class="nt"><dependencies></span> + <span class="nt"><dependency></span> + <span class="nt"><groupId></span>org.codehaus.groovy<span class="nt"></groupId></span> + <span class="nt"><artifactId></span>groovy<span class="nt"></artifactId></span> + <span class="nt"><version></span>1.8.6<span class="nt"></version></span> + <span class="nt"></dependency></span> + <span class="nt"></dependencies></span> + <span class="nt"><configuration></span> + <span class="nt"><language></span>groovy<span class="nt"></language></span> + <span class="nt"></configuration></span> + <span class="nt"></plugin></span> + <span class="nt"></plugins></span> + <span class="nt"></build></span> + <span class="nt"></profile></span> + <span class="nt"></profiles></span> + <span class="c"><!-- […] --></span> +<span class="nt"></project></span></code></pre></div> + +<p>Ainsi, il suffit ensuite d’avoir quelques choses du genre dans son +<code>$HOME/.m2/settings.xml</code> pour que les builds qui ont ce profil se <em>build</em> +dans <code>/tmp/m2/</code>. On peut aussi ne rien avoir dans <code>$HOME/.m2/settings.xml</code> +et utilise <code>-Dexternal.build.root=/tmp/m2/</code> avec la commande <code>mvn</code>.</p> + +<div class="highlight"><pre class="chroma"><code class="language-xml" data-lang="xml"><span class="nt"><settings></span> + <span class="c"><!-- […] --></span> + <span class="nt"><profiles></span> + <span class="nt"><profile></span> + <span class="nt"><id></span>build-in-ramfs<span class="nt"></id></span> + <span class="nt"><properties></span> + <span class="nt"><external.build.root></span>/tmp/m2/<span class="nt"></external.build.root></span> + <span class="nt"></properties></span> + <span class="nt"></profile></span> + <span class="nt"></profiles></span> + <span class="nt"><activeProfiles></span> + <span class="nt"><activeProfile></span>build-in-ramfs<span class="nt"></activeProfile></span> + <span class="nt"></activeProfiles></span> + <span class="c"><!-- […] --></span> +<span class="nt"></settings></span></code></pre></div> +<div class="footnotes"> + +<hr /> + +<ol> +<li id="fn:inspiration"><a href="http://elehack.net/writings/programming/maven-target-in-tmpfs">Putting Maven build directories out-of-tree</a> par <a href="http://elehack.net/">Michal Ekstrand</a> + <a class="footnote-return" href="#fnref:inspiration"><sup>[return]</sup></a></li> +</ol> +</div> + + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2013-10-12-podcasts/" title="Podcasts">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2012-12-16-gollum-comme-wiki-personnel/" title="Gollum Comme Wiki Personnel">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2013-10-12-podcasts/index.html b/www/vincent.demeester.fr/legacy/posts/2013-10-12-podcasts/index.html @@ -0,0 +1,177 @@ +<!DOCTYPE html> + +<html lang="fr"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2013-10-12-podcasts/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang="fr" class="gray"> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">Podcasts</h1><a href='https://vincent.demeester.fr/posts/2013-10-12-podcasts/'></a> + <address class="signature"> + <span class="date">Sat, 12 October, 2013</span> + <span class="words">(400 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#me">me</a></li> + + + + + + <li class="tag tag-music"><a href="/tags/#music">music<span>1</span></a></li> + + + <li class="tag tag-podcast"><a href="/tags/#podcast">podcast<span>1</span></a></li> + + <br/> + + </ul> + </header> + + + + + +<blockquote> +<p>Voici un petit billet présentant les différents podcast que j’écoute plus ou +moins régulièrement.</p> +</blockquote> + +<p>J’écoute énormément de musique et de podcast ; je passe beaucoup de temps avec +des écouteurs sur la tête ou la chaine hifi en route. Les podcasts ont une grande +place. Voici une liste plus ou moins bien triés de ceux auxquels je suis +souscrit et/ou que j’écoute en ce moment. Je tiendrais peut-être ce post +à jour ou en créerait un nouveau sinon :-).</p> + +<h2 id="geek-co">Geek & co</h2> + +<p>Je suis un developpeur, un geek et convaincu des logiciels libres, les +podcasts qui suivent reflète assez cette partie là de mon identité.</p> + +<ul> +<li><a href="http://www.captainweb.net/">L’apéro de Captain (fr)</a> : dans le genre geeky, déjanté et sauvage, en français, on ne fait pas mieux. Pas vraiment safe for work, et pas tout à fait “libriste” comme j’aime, les tranches de rires sont garanties ; même si parfois on est un peu verreux d’avoir écouter jusqu’à la fin et le bien nommé “wazzuf”.</li> +<li><a href="http://www.agencetousgeeks.com/">Agence Tous Geeks (fr)</a> : fils caché de l’apéro du captain, on y retrouve des amis (et membres de ce dernier), mais c’est un peu plus calme.</li> +<li><a href="http://bazingcast.com/about/">Bazingcast (fr)</a> : podcast geek, plus posé que les deux précédents mais avec +des débats, des trolls et tout ce que l’on peut attendre de geeks.</li> +<li><a href="http://www.captainposix.net/">Parole de Tux (fr-be)</a> : podcast venant de nos voisin belge, pas trop long et plutôt sympa ; et si comme moi vous adorez l’accent belge, c’est le top.</li> +<li><a href="http://faif.us/">Free as in Freedom (en)</a> : podcast parlant de logiciel +libre principalement côté license, truc légal, etc.. en anglais, faut +parfois s’accrocher.</li> +<li><a href="http://episodes.gitminutes.com/">Git Minutes (en)</a> : podcast à propos de +Git et des outils de son écosystème (vcsh, etc..).</li> +</ul> + +<h2 id="radio">Radio</h2> + +<p>J’aime bien la radio, bien plus que la télévision (que j’allume si peu qu’à +chaque fois la box se met à jour <code>;-p</code>).</p> + +<ul> +<li><a href="http://www.franceinter.fr/emission-laura-leishman-project">France Inter - LLP (Laura leishman Project)</a></li> +<li><a href="http://radiofrance-podcast.net/podcast09/rss_12265.xml">Le Mouv’ - Laura Leishman Project</a></li> +<li><a href="http://www.franceinter.fr/emission-interception">France Inter - Interception</a></li> +<li><a href="http://www.la-bas.org/">France Inter - là-bas si j’y suis</a></li> +<li><a href="http://www.franceculture.fr/podcast/4689840">France Culture - Pixel</a></li> +<li><a href="http://www.franceculture.fr/podcast/4685228">France Culture - Place de la toile</a></li> +<li><a href="http://www.franceculture.fr/podcast/4689418">France Culture - Philippe Meyer</a></li> +<li><a href="http://radiofrance-podcast.net/podcast09/rss_12582.xml">Le Mouv’ - Glitch (sur le Mouv’)</a></li> +<li><a href="http://radiofrance-podcast.net/podcast09/rss_12691.xml">Le Mouv’ - Code Source</a></li> +<li><a href="http://radiofrance-podcast.net/podcast09/rss_12190.xml">Le Mouv’ - Suivez le geek</a></li> +<li><a href="http://www.divergence-fm.org/-http-www-divergence-fm-org-ecrire-exec-rubrique-id_rubrique-61-.html">Divergence Numerique</a></li> +</ul> + +<h2 id="musique">Musique</h2> + +<p>Les podcast suivant sont purement musique, tous musique <em>techno</em> ou +trance (que j’aime bien).</p> + +<ul> +<li><a href="http://podcasts.flaix.fr/corstencountdown">Ferry Corsten - Corsten Countdown</a></li> +<li><a href="http://www.galexmusic.com/podcast/gareth.xml">Gareth Emery Podcast</a></li> +<li><a href="http://feedproxy.feedburner.com/Tiestos_club_life">Tiesto Club Life</a></li> +<li><a href="http://oakenfold.libsyn.com/rss">Oakenfold Perfecto Podcast</a></li> +</ul> + + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2014-03-24-redesign-et-r%C3%A9solutions/" title="Redesign Et Résolutions">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2013-09-08-maven-tmpfs/" title="Maven Tmpfs">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2014-03-24-redesign-et-résolutions/index.html b/www/vincent.demeester.fr/legacy/posts/2014-03-24-redesign-et-résolutions/index.html @@ -0,0 +1,166 @@ +<!DOCTYPE html> + +<html lang="fr"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2014-03-24-redesign-et-r%C3%A9solutions/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang="fr"/> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">Redesign Et Résolutions</h1><a href='https://vincent.demeester.fr/posts/2014-03-24-redesign-et-r%C3%A9solutions/'></a> + <address class="signature"> + <span class="date">Mon, 24 March, 2014</span> + <span class="words">(600 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#me">me</a></li> + + + + + + <li class="tag tag-jekyll"><a href="/tags/#jekyll">jekyll<span>4</span></a></li> + + + <li class="tag tag-design"><a href="/tags/#design">design<span>1</span></a></li> + + + <li class="tag tag-images"><a href="/tags/#images">images<span>1</span></a></li> + + + <li class="tag tag-redesign"><a href="/tags/#redesign">redesign<span>1</span></a></li> + + <br/> + + </ul> + </header> + + + + + +<p>Un <em>tout petit</em> post pour parler rapidement, entre autre, du redesign de <a href="http://vincent.demeester.fr">vincent.demeester.fr</a> et de mes résolutions.</p> + +<h2 id="redesign">Redesign</h2> + +<p>Les raisons de ce redesign sont assez simple : je change d’employeur et de ville (retour sur Paris <code>\o/</code>). +Cela fait pas mal de changement, et en voulant mettre à jour la page d’accueil (ce que je n’ai toujours pas +fait <code>;-P</code>), j’avais envie de <em>déménager</em> le site en quelques sortes.</p> + +<p>Pour l’inspiration c’est assez facile à trouver, regarder <a href="http://medium.com">medium</a>, le site de +<a href="http://www.viksit.com/">Viksit Gaur</a> ou encore celui de <a href="http://silent-strength.com/">Michael</a> (coucou <code>:-P</code>). +J’adore ce genre de site, assez épuré mais avec une partie fixe (à gauche ou à droite) et avec des images changeantes.</p> + +<p>Les images en fond de l’espace de gauche peuvent changer d’une page à l’autre. Je suis en train de m’amuser un peu +avec <a href="http://jekyllrb.com">Jekyll</a>. Il faut que je trouve un moyen d’optimiser un peu les images que j’utilise +parce que là je joue un peu le bourrin.</p> + +<h2 id="résolutions">Résolutions</h2> + +<p>Nouvelle année, nouveau boulot, implique nouvelles résolutions. Il y a deux aspects à ces <em>résolutions</em> : sur un plan +informatique/geek/travail/organisation, et sur un plan purement <em>physique</em>.</p> + +<p>Mon retour sur Paris devrait me permettre de participer à un peu plus d’évènements, notamment du côté des Java User Group, mais +aussi FSFE, et j’en passe. Je compte également continué à jouer le <em>factotum</em> en me gardant un peu de temps au niveau +personnel pour travailler sur des aspects <em>geek</em> que je n’aurais peut-être pas l’occasion de pratiquer au travail. +Au niveau de mes <em>points d’entrées</em> sur le web, je compte remettre un peu en route la partie blog de ce site et +tourner shortbrain.org (ou autre) en un site plus “documentation” en me basant sur mes notes (powered by <a href="http://org-mode.org">org-mode</a>). +Je me suis également remis à <a href="https://www.gnu.org/software/emacs/">Gnu Emacs</a> et <em>oh god</em> qu’est-ce que c’est bon <code>:-D</code>.</p> + +<p>Au niveau phyisque, dans la continuité de la fin 2012, l’année 2013 a probablement été la pire de ma vie. Les +problèmes de dos c’est pas facile tous les jours.. En février, j’avais peur de ne jamais remonter sur un vélo ; en Mai +je remontais pour la première fois sur le vélo, en Septembre je faisais 40km par semaine et en décembre j’étais +presque capable de faire 30km en une journée. Mème si l’année 2014 a mal commencé (je me suis felé un côte en Janvier), +mes objectifs pour cette année et les futurs sont nettement plus positif. Le <a href="http://fitbit.com">fitbit</a> que j’ai +acquis en Février me permet d’avoir un objectif de marche (certes assez modeste) de 10000 pas et 8,05 km par jour. +Je compte bien me fixer d’autre objectifs : monter la barre plus haut (15000 voir plus), faire 50km de vélo +sur une journée.</p> + +<p>Les objectifs physiques à très long termes sont également assez simples : retrouver ma forme physique et ne plus être +géné à cause du dos. En gros c’est : être capable de faire 120km de vélo et monter des cols (Col du Sapenay, Mont Revard) +et à long terme, faire des courses de footing (i.e. 10km, Paris-Versaille, Semi-marathon, marathon :D).</p> + +<p>Sur ce, je vais retourner à mes cartons :-P.</p> + + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2014-10-27-docker-1.3-ecosystem/" title="Docker 1.3 et son écosystème">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2013-10-12-podcasts/" title="Podcasts">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2014-10-27-docker-1.3-ecosystem/index.html b/www/vincent.demeester.fr/legacy/posts/2014-10-27-docker-1.3-ecosystem/index.html @@ -0,0 +1,383 @@ +<!DOCTYPE html> + +<html lang="fr"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2014-10-27-docker-1.3-ecosystem/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang="fr"/> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">Docker 1.3 et son écosystème</h1><a href='https://vincent.demeester.fr/posts/2014-10-27-docker-1.3-ecosystem/'></a> + <address class="signature"> + <span class="date">Mon, 27 October, 2014</span> + <span class="words">(1700 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#zenika">zenika</a></li> + + + + + + <li class="tag tag-docker"><a href="/tags/#docker">docker<span>3</span></a></li> + + + <li class="tag tag-fig"><a href="/tags/#fig">fig<span>1</span></a></li> + + + <li class="tag tag-hub"><a href="/tags/#hub">hub<span>1</span></a></li> + + + <li class="tag tag-boot2docker"><a href="/tags/#boot2docker">boot2docker<span>1</span></a></li> + + + <li class="tag tag-container"><a href="/tags/#container">container<span>1</span></a></li> + + <br/> + + </ul> + </header> + + + + +<div class="notice">Cet article est disponible sur le <a href="http://blog.zenika.com/">Blog de Zenika</a> à l'adresse suivante : <a href="http://blog.zenika.com/index.php?post/2014/10/27/Docker-1-3-et-son-ecosysteme">http://blog.zenika.com/index.php?post/2014/10/27/Docker-1-3-et-son-ecosysteme</a>. Cet publication me sert de mirroir / sauvegarde.</div> + +<p>Le <strong>16 octobre dernier</strong>, <ins>Docker est passé en version 1.3</ins>. C'est une +bonne occasion de faire un point sur ce qu'apportent les mises à jour +qui sont sorties depuis la 1.0. Nous allons également en profiter pour +regarder les news <strong>importantes</strong> de l'écosystème Docker.</p> + + +<p><img src="/public/Billet_0511/docker_container_engine_logo.png" alt="Docker" title="Docker" /></p> + + +<p>Rappel très rapide, <strong>Docker est une plate-forme ouverte à destination des développeurs et administrateurs systèmes visant à faciliter la construction et le déploiement d'applications distribuées</strong>. De manière +moins marketing, l'idée derrière Docker est d'<strong>automatiser</strong> le <strong>déploiement</strong> d'environnements sous forme de <strong>conteneurs légers</strong>, portables et auto-suffisants ; les conteneurs permettant d'isoler +l'exécution des applications dans des contextes d'exécution. Pour ce +faire, Docker, écris en <a href="http://golang.org/">Go</a>, reprend les bases de <strong>LXC</strong>, utilise les fonctionnalités +du <strong>noyau Linux</strong> (CGroups, Namespaces, …) et se base <em>initialement</em> sur un +système de fichier "en oignons" AUFS ; D'autres backend sont supportés +également comme BTRFS ou <em>devicemapper</em> (LVM).</p> + + +<p>Depuis le 9 juin 2014 et la release de la version 1.0 "production-read", l'équipe derrière Docker n'a pas chomé et 3 +nouvelles mises à jour sont sorties depuis ; à savoir que les +<em>releases</em> de Docker se font à un rythme pré-défini, "à-la" Linux, +tout ce qui est prêt et testé est intégré à la release qui +suit. Voyons, de façon non-exhaustive, quelles sont les princiales +améliorations apportées par ces différentes versions.</p> + +<h4>Hub : Images officielles et language stack</h4> + + +<p>Le <strong>Hub</strong>, tel qu'il a été nommé après la sortie de Docker 1.0 est <strong>un dépôt des images Docker</strong> de tout-un-chacun qui souhaite les partager.</p> + + +<p>Au début de l'été, Docker Inc. a annoncé l'apparition des <strong>dépots officiels</strong>. L'idée est d'estampiller des images Docker comme officielles, +c'est à dire vérifiées et garanties comme étant issues et supportées par +les mainteneurs des projets. De nombreuses images officielles existent +déjà pour les principaux projets open-source, comme Ubuntu, MongoDB, +etc. Toute communauté open-source ou même tout éditeur logiciel peut +entrer en contact avec l'équipe Docker pour voir son/ses images +estampillées "officielles", après validation. Cette étiquette vient se +rajouter à l'étiquette "<em>automated build repository</em>", précédement +appelée <em>verified</em> (ce qui prétait à confusion) qui donne la garantie +à l'utilisateur que l'image a été construite de manière automatique +par l'infrastructure de Docker Inc. Il est également à noter que la version +1.3.0 de Docker apporte la vérification de la provenance et de +l'intégrité des images officielles via signature électronique ; même si +pour l'instant c'est en <em>work-in-progress</em>.</p> + + +<p>Un autre ajout récent au Docker Hub, datant de fin septembre, vaut le +détour : les l<strong>anguage stack</strong>, des images de bases <em>pre-construites</em> avec tous les outils nécessaires pour faire tourner une application dans un langage donné. Un développeur souhaitant rapidement construire +un conteneur avec, par exemple, une application Clojure, n'a plus +besoin de <em>réinventer la roue</em> (i.e. partir d'une image de base, installer +le JDK, installer lein, etc.). Il lui suffit de partir d'une des +<em>language stack</em>, ici clojure.</p> + +<pre class="bash code bash" style="font-family:inherit">FROM clojure +COPY . <span style="color: #000000; font-weight: bold;">/</span>usr<span style="color: #000000; font-weight: bold;">/</span>src<span style="color: #000000; font-weight: bold;">/</span>app +WORKDIR <span style="color: #000000; font-weight: bold;">/</span>usr<span style="color: #000000; font-weight: bold;">/</span>src<span style="color: #000000; font-weight: bold;">/</span>app +CMD <span style="color: #7a0874; font-weight: bold;">[</span><span style="color: #ff0000;">"lein"</span>, <span style="color: #ff0000;">"run"</span><span style="color: #7a0874; font-weight: bold;">]</span></pre> + + +<h4>Restart policies (1.2.0)</h4> + + +<p>Docker 1.2 a apporté une option en plus à la commande <code>run</code> : +<code>--restart</code>. Il permet de définir une <strong>politique de redémarrage</strong> dans +le cas où le conteneurs viendrait à mourrir, que ce soit de manière +normale (code de retour à 0) ou inattendue (<em>failure</em>, code de retour +différent de 0). Trois options sont disponibles pour l'instant :</p> + +<ul> +<li><strong>no</strong> : pas de redémarrage, fonctionnement par défaut.</li> +<li><strong>on-failure</strong>: redémarrage automatique si le conteneur s'est terminé de façon anormale. Il est possible d'ajouter un nombre maximum de redémarrage ; avec <code>--restart=on-failure:3</code> docker essaiera de redémarrer 3 fois avant d'abandonner.</li> +<li><strong>always</strong> : redémarrage automatique, tout le temps, erreurs ou pas.</li> +</ul> + +<h4>Injection de processus (1.3.0)</h4> + + +<p>La possibilité de voir ce qui se passe dans le container, et par +conséquent de s'en servir pour debugger, s'est averé longtemps +complexe. Dans les <em>premiers jours</em> de Docker, l'installation d'un +démon ssh était une solution commune. Cependant, celà complexifiait la +création d'un conteneur ; en effet, Docker est fait pour lancer et isoler +une seule commande, l'ajout d'un démon sshd imposait alors de mettre +en place une solution du type <em>init</em> comme supervisord, runit, ou +autres. Un article de Jérôme Petazonni, employé Docker Inc, +a mis les choses au point : +<strong><a href="http://jpetazzo.github.io/2014/06/23/docker-ssh-considered-evil/">If you run SSHD in your Docker containers, you're doing it wrong!</a></strong>, +littéralement "<em>Si vous faites tourner SSHD dans vos container Docker, vous vous trompez</em>". Jérôme avait créé à l'époque un outil, +<a href="https://github.com/jpetazzo/nsenter">nsenter</a>, qui était (et est toujours) installable en passant par un container, histoire de montrer +un peu de magie.</p> + + +<p>La version 1.3 de Docker intègre un nouvelle commande, <code>exec</code> qui +n'est autre que nsenter, en mieux, directement intégré à Docker, plus +besoin de passer par un outil externe. <strong>Il devient donc possible d'executer n'importe quelle process à l'intérieur donc container, qui est en cours d'exécution</strong>. Ainsi un simple <code>docker exec -it ubuntu_bash bash</code> et +nous voici dans une session bash à l'intérieur du conteneur. Bien +entendu, comme pour nsenter, cela ne change pas l'idée derrière Docker +qui est "une application par conteneur" ; la commande <code>exec</code> est +surtout présente pour répondre à des problématiques de <em>debug</em> et de +<em>developpement</em>.</p> + + +<h4>Cycle de vie d’un container (1.3.0)</h4> + + +<p>Une autre nouvelle commande arrive avec la version 1.3.0 de Docker, +c'est <code>create</code>. Beaucoup d'utilisateur ont demandé d'être capable +de séparer la création initiale de conteneur et son lancement ; +auparavant il n'existait que la commande <code>run</code> qui faisait les deux +d'un coup.</p> + +<pre class="bash code bash" style="font-family:inherit">$ docker create <span style="color: #660033;">-t</span> <span style="color: #660033;">-i</span> fedora <span style="color: #c20cb9; font-weight: bold;">bash</span> +6d8af538ec541dd581ebc2a24153a28329acb5268abe5ef868c1f1a261221752 +$ docker start <span style="color: #660033;">-a</span> <span style="color: #660033;">-i</span> 6d8af538ec5 +bash-<span style="color: #000000;">4.2</span><span style="color: #666666; font-style: italic;">#</span></pre> + + +<h4>Options de sécurité (1.3.0)</h4> + + +<p>Les utilisateurs de SELinux ou AppArmor vont être content, la commande +<code>--security-opt</code>, arrivée avec la version 1.3.0, permet les <em>labels</em> +et <em>profiles</em> de ces derniers, ce qui donne quelque chose comme :</p> + +<pre class="bash code bash" style="font-family:inherit">docker run <span style="color: #660033;">--security-opt</span> label:<span style="color: #7a0874; font-weight: bold;">type</span>:svirt_apache <span style="color: #660033;">-i</span> <span style="color: #660033;">-t</span> centos <span style="color: #c20cb9; font-weight: bold;">bash</span></pre> + + +<p>L'avantage principal de cette nouvelle commande, c'est, sur +les systèmes qui sont configurés avec SELinux ou AppArmor, de pouvoir +donner des privilèges de manière plus fine qu'avec l'option +<code>--privileged</code> (qui donne tout) et ainsi diminuer les risques +potentiels.</p> + + +<h4>Boot2docker</h4> + + +<p>Docker s'appuyant sur des fonctionnalitées de noyau Linux, son usage +est <em>limité</em> à un système hôte avec un noyau Linux. Le projet +boot2docker vise à enlever cette barrière en <strong>permettant d'avoir la commande docker sous Mac OS X et Windows</strong>. Il s'agit ni plus ni moins +d'une machine virtuelle VirtualBox légère, basée sur la distribution +Tiny Core Linux, pour avoir un <em>overhead</em> le plus faible possible. +L'utilisation de boot2docker n'est pas encore totalement transparente, +principalement pour la gestion des ports ou encore du montage des +volumes du Host (OS X ou Windows) dans le conteneur Docker. La version +1.3 de docker, et la version correspondante de boot2docker, permettent +maintenant aux utiliseurs de Mac OS X de monter leur dossier <em>hôtes</em> +dans le conteneur.</p> + + +<h4>Fig 1.0</h4> + + +<p><a href="http://www.fig.sh/">Fig</a> est un outil de développement basé sur Docker, écrit en Python. L'idée est de +définir son environnement via un fichier YAML, que ce soit pour le +code sur lequel nous travaillons mais également les services externes +desquels notre application dépend (Base de données, ''Message +queue'', etc.).</p> + + +<p>Nous avons donc, par exemple, un <code>Dockerfile</code> :</p> + +<pre class="bash code bash" style="font-family:inherit">FROM clojure:lein-2.5.0 +ADD . <span style="color: #000000; font-weight: bold;">/</span>code +WORKDIR <span style="color: #000000; font-weight: bold;">/</span>code +RUN lein run</pre> + + + +<p>Et un <code>fig.yml</code> :</p> + +<pre>[yaml] +web: + build: . + command: lein run + links: + - db + ports: + - "8000:8000" +db: + image: postgres +</pre> + + +<p>Enfin un petit <code>fig up</code> et c'est gagné, nous obtenons notre base de données +relationnelle qui tourne, notre appli qui a été construite et qui +tourne aussi, et qui est lié à notre conteneur db. Il existe bien d'autres +commandes (<code>start</code>, <code>stop</code>, <code>destroy</code>, etc.).</p> + + +<p>Fig était initialement développé par <a href="https://www.orchardup.com/">Orchard</a>, qui a été acquis cette +année par Docker Inc. Ce 17 octobre, l'équipe Docker a donc fait un release 1.0 de +Fig en ajoutant le support à docker 1.3 et à boot2docker. Un certain nombre de commandes et de +nouvelles fonctionnalitées ont étés ajoutées, notament :</p> + +<ul> +<li><code>fig port</code>, qui liste les ports par service,</li> +<li><code>fig pull</code>, qui récupère la dernière version d'un service,</li> +<li><code>fig restart</code>, qui redémarre les conteneurs (<code>stop</code> et <code>start</code>)</li> +<li>le support de <code>.dockerignore</code></li> +<li>le support de connection en TLS au daemon Docker</li> +<li>et pas mal d'autres options.</li> +</ul> + +<p>Mais l'annonce principale accompagnant cette version est que <strong>Fig ne recevra plus de mise à jour majeure à partir de cette version 1.0</strong> +puisque l'équipe Docker travaille pour <strong>intégrer les fonctionnalités</strong> +que Fig apporte, <strong>directement dans Docker</strong> ; ce qui est une +excellente nouvelle.</p> + + +<h4>Partenariat avec Microsoft</h4> + + +<p>Cela transpirait ces derniers mois, dans les différentes conférences +et meetups, Microsoft s'intéressait de très prêt à Docker. C'est +maintenant officiel, <strong>Docker Inc et Microsoft sont partenaires</strong>. Le +partenariat couvre pour l'instant les sujets suivants :</p> + +<ul> +<li>Ajouter le support de windows comme hôte Docker.</li> +<li>Pour Microsoft, supporter les API <em>open-orchestration</em> de Docker.</li> +<li>Intégration de Docker dans Microsoft Azure.</li> +<li>Collaboration étroite sur les applications qui ont besoin de plusieurs conteneur (i.e. ce que Fig fait de mieux), et le support d'application qui sont composés de conteneurs Linux et Windows.</li> +</ul> + +<p>Le but <em>ultime</em> de Docker quand il a été distribué de façon libre +était : "__Construire le 'bouton' qui permet à toutes applications +d'être construites et déployées sur n'importe quel serveur, n'importe +où.__" (C'est nettement plus classe en anglais : “''To build the ‘button’ +that enables any application to be built and deployed on any server, +anywhere.''”). Ce partenariat est donc une nouvelle marche en direction +de ce dernier.</p> + + +<h4>Conclusion</h4> + + +<p>Un an et demi après la première <em>release</em> publique de Docker et près +de 5 mois après la version 1.0, Docker et sa communauté avancent +toujours aussi vite. Comme Solomon Hykes (CTO et co-fondateur de dotCould) +avait dit lors de la dockerCon 14 : "<strong>la valeur réelle de docker n'est pas la technologie, mais le fait que les gens se mettent d'accord sur quelque chose</strong>" ; Docker Inc. pousse principalement dans le sens de la +standardisation. Le développement de +<a href="https://github.com/docker/libcontainer">libcontainer</a>, +<a href="https://github.com/docker/libchan">libchan</a> et +<a href="https://github.com/docker/libswarm">libswarm</a> sont des projets qui +vont dans ce sens. La communautée est toute aussi <em>bouillante</em> d'idées +et chaque jour voit de nouveau projet plus intéressant les uns que les +autres.</p> + + +<p>Enfin la <strong><a href="http://europe.dockercon.com/">dockerCon Europe</a>, les 4 et 5 décembre 2014</strong> viendra couronner une année très riche du côté de Docker et +de son écosystème. Rendez-vous mi-décembre pour faire un petit retour + sur la première conférence européenne Docker ;-).</> + + + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2015-05-01-orgmode-et-jekyll/" title="Orgmode et Jekyll">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2014-03-24-redesign-et-r%C3%A9solutions/" title="Redesign Et Résolutions">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2015-05-01-orgmode-et-jekyll/index.html b/www/vincent.demeester.fr/legacy/posts/2015-05-01-orgmode-et-jekyll/index.html @@ -0,0 +1,260 @@ +<!DOCTYPE html> + +<html lang="fr"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2015-05-01-orgmode-et-jekyll/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang="fr"/> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">Orgmode et Jekyll</h1><a href='https://vincent.demeester.fr/posts/2015-05-01-orgmode-et-jekyll/'></a> + <address class="signature"> + <span class="date">Fri, 1 May, 2015</span> + <span class="words">(900 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#developement">developement</a></li> + + + + + + <li class="tag tag-jekyll"><a href="/tags/#jekyll">jekyll<span>4</span></a></li> + + + <li class="tag tag-blog"><a href="/tags/#blog">blog<span>2</span></a></li> + + + <li class="tag tag-emacs"><a href="/tags/#emacs">emacs<span>2</span></a></li> + + + <li class="tag tag-orgmode"><a href="/tags/#orgmode">orgmode<span>2</span></a></li> + + + <li class="tag tag-html"><a href="/tags/#html">html<span>2</span></a></li> + + <br/> + + </ul> + </header> + + + + <div id="outline-container-sec-1" class="outline-2"> +<h2 id="sec-1">Un peu plus d'un an..</h2> +<div class="outline-text-2" id="text-1"> +<p> +.. que je n'ai rien posté ici. Le temps passe bien vite, mais c'est plutôt une bonne chose. Je crois que quelque chose me manquait pour poster ici plus souvent. +</p> + +<blockquote> +<p> +Au niveau de mes points d'entrées sur le web, je compte remettre un peu en route la partie blog de ce site et tourner shortbrain.org (ou autre) en un site plus "documentation" en me basant sur mes notes (powered by org-mode). <b>Je me suis également remis à Gnu Emacs et oh god qu'est-ce que c'est bon</b> :-D. +</p> +</blockquote> + +<p> +C'est ce que j'écrivais il y a un an, et effectivement c'est toujours aussi bon <code>;-D</code>. J'ai commencé à utiliser <code>org-mode</code> de manière assez intensive, pour mettre en place mon <i>personal kanban</i> mais également pour prendre mes notes. Et plus je l'utilise, plus j'ai du mal m'en passer et à aller dans mon dossier hébergeant ce site pour y écrire en Markdown. Il me fallait donc trouver une astuce pour convertir certaines notes en post pour Jekyll. C'est maintenant chose faite (après 6 mois dans ma TODO-list…), et voici comment je m'en sors. +</p> + +<p> +L'idée générale est assez simple : +</p> + +<ol class="org-ol"> +<li>Utiliser un dossier particulier pour les notes destiné à ce blog +</li> +<li>Exporter, au format html, ces notes dans le bon dossier (<code>_posts</code>) +</li> +<li>… et c'est tout en fait :) — c'était pas <i>ben</i> compliqué ! +</li> +</ol> +</div> +</div> + +<div id="outline-container-sec-2" class="outline-2"> +<h2 id="sec-2">Les fichiers org</h2> +<div class="outline-text-2" id="text-2"> +<p> +Tous mes fichiers <code>org-mode</code> sont dans un ensemble de dossiers bien particulier — cela n'a pas grand chose d'important à faire ici, mais ça permet de suivre la configuration qui suit. Il s'agit du dossier <code>~/desktop/org/</code>, et de ces fils : <code>todos</code> pour ce que je dois faire et <code>notes</code> pour ma prise de note. Dans <code>notes</code> on trouve donc des dossiers et en particulier un qui se nomme <code>vdf</code> (pour Vincent.Demeester.Fr). +</p> + +<p> +La seule différence que les fichiers <code>.org</code> ont dans ce dossier par rapport aux autres, c'est le début du fichier. En effet, <a href="http://jekyllrb.com/">Jekyll</a> a besoin d'une entête en <code>YAML</code> pour le layout, les tags, la catégorie et plein d'autres trucs. Le fichier <code>org</code> de ce billet ressemble à ça par example : +</p> + +<pre class="example"> +#+BEGIN_HTML +--- +layout: post +category: developement +tags: jekyll blog emacs orgmode html +lang: fr +--- +#+END_HTML +* Un peu plus d'un an.. + +.. que je n'ai rien posté ici. Le temps passe bien vite, mais c'est plutôt une bonne chose. Je crois que quelque chose me manquait pour poster ici. + +[…] +</pre> + +<p> +Les utilisateurs de Jekyll reconnaitront les <code>---</code> et le format du header. <code>#+BEGIN_HTML</code> et <code>#+END_HTML</code> sont là pour dire à <code>org-mode</code> d'exporter un bloc en HTML sans l'interpréter — c'est une façon de mettre du HTML dans un document <code>org</code> si on peut pas faire ce qu'on veut avec le <i>markup</i>. Ici ça nous permet d'exporter tel quel le header de <a href="http://jekyllrb.com/">Jekyll</a> en haut du HTML généré, pratique ! +</p> +</div> +</div> + +<div id="outline-container-sec-3" class="outline-2"> +<h2 id="sec-3">Configuration de org-mode</h2> +<div class="outline-text-2" id="text-3"> +<p> +Il reste maintenant à configurer le <i>projet de publication</i> (publishing project) dans Emacs. Je vais mettre ici juste la partie intéressante, donc pour plus d'information sur comment publier des fichiers org-mode avec Emacs, c'est par <a href="http://orgmode.org/worg/org-tutorials/org-publish-html-tutorial.html">ici</a> pour un tutoriel (j'adore <a href="http://orgmode.org/worg/org-tutorials/org-publish-html-tutorial.html">worg</a> soit dit en passant :P). +</p> + +<p> +L'idée c'est de définir un projet avec plusieurs <i>components</i>, un pour les fichiers <code>org</code>, un pour les fichiers <code>css</code> et un pour les <i>assets</i> (images, vidéos, …). On souhaite également ne pas générer de table des matières (TOC) et ne génerer que le <i>body</i> (pas de <code><head></code>, …). Et ça se présente comme suit. +</p> + +<div class="org-src-container"> + +<pre class="src src-emacs-lisp"><span class="org-comment-delimiter">;; </span><span class="org-comment">Variables</span> +<span class="org-rainbow-delimiters-depth-1">(</span><span class="org-keyword">setq</span> <span class="org-rainbow-identifiers-identifier-9">vdf-base-directory</span> <span class="org-string">"~/desktop/org/notes/vdf/"</span> + <span class="org-rainbow-identifiers-identifier-11">vdf-site-directory</span> <span class="org-string">"~/src/github/vdemeester/vdemeester.github.com"</span> + <span class="org-rainbow-identifiers-identifier-4">vdf-publishing-directory</span> <span class="org-rainbow-delimiters-depth-2">(</span><span class="org-rainbow-identifiers-identifier-15">expand-file-name</span> <span class="org-string">"_posts"</span> <span class="org-rainbow-identifiers-identifier-11">vdf-site-directory</span><span class="org-rainbow-delimiters-depth-2">)</span> + <span class="org-rainbow-identifiers-identifier-2">vdf-css-publishing-directory</span> <span class="org-rainbow-delimiters-depth-2">(</span><span class="org-rainbow-identifiers-identifier-15">expand-file-name</span> <span class="org-string">"css"</span> <span class="org-rainbow-identifiers-identifier-11">vdf-site-directory</span><span class="org-rainbow-delimiters-depth-2">)</span> + <span class="org-rainbow-identifiers-identifier-15">vdf-assets-publishing-directory</span> <span class="org-rainbow-delimiters-depth-2">(</span><span class="org-rainbow-identifiers-identifier-15">expand-file-name</span> <span class="org-string">"assets"</span> <span class="org-rainbow-identifiers-identifier-11">vdf-site-directory</span><span class="org-rainbow-delimiters-depth-2">)</span><span class="org-rainbow-delimiters-depth-1">)</span> + +<span class="org-comment-delimiter">;; </span><span class="org-comment">Project</span> +<span class="org-rainbow-delimiters-depth-1">(</span><span class="org-keyword">setq</span> <span class="org-rainbow-identifiers-identifier-12">org-publish-project-alist</span> + `<span class="org-rainbow-delimiters-depth-2">(</span><span class="org-rainbow-delimiters-depth-3">(</span><span class="org-string">"vdf-notes"</span> + <span class="org-builtin">:base-directory</span> ,<span class="org-rainbow-identifiers-identifier-9">vdf-base-directory</span> + <span class="org-builtin">:base-extension</span> <span class="org-string">"org"</span> + <span class="org-builtin">:publishing-directory</span> ,<span class="org-rainbow-identifiers-identifier-4">vdf-publishing-directory</span> + <span class="org-builtin">:exclude</span> <span class="org-string">"FIXME"</span> + <span class="org-builtin">:section-numbers</span> <span class="org-rainbow-identifiers-identifier-2">nil</span> + <span class="org-builtin">:with-toc</span> <span class="org-rainbow-identifiers-identifier-2">nil</span> + <span class="org-builtin">:with-drawers</span> <span class="org-rainbow-identifiers-identifier-8">t</span> + <span class="org-builtin">:htmlized-source</span> <span class="org-rainbow-identifiers-identifier-8">t</span> + <span class="org-builtin">:publishing-function</span> <span class="org-rainbow-identifiers-identifier-7">org-html-publish-to-html</span> + <span class="org-builtin">:headline-levels</span> <span class="org-rainbow-identifiers-identifier-8">4</span> + <span class="org-builtin">:body-only</span> <span class="org-rainbow-identifiers-identifier-8">t</span><span class="org-rainbow-delimiters-depth-3">)</span> + <span class="org-rainbow-delimiters-depth-3">(</span><span class="org-string">"vdf-static-css"</span> + <span class="org-builtin">:base-directory</span> ,<span class="org-rainbow-identifiers-identifier-9">vdf-base-directory</span> + <span class="org-builtin">:base-extension</span> <span class="org-string">"css"</span> + <span class="org-builtin">:publishing-directory</span> ,<span class="org-rainbow-identifiers-identifier-2">vdf-css-publishing-directory</span> + <span class="org-builtin">:recursive</span> <span class="org-rainbow-identifiers-identifier-8">t</span> + <span class="org-builtin">:publishing-function</span> <span class="org-rainbow-identifiers-identifier-5">org-publish-attachment</span> + <span class="org-rainbow-delimiters-depth-3">)</span> + <span class="org-rainbow-delimiters-depth-3">(</span><span class="org-string">"vdf-static-assets"</span> + <span class="org-builtin">:base-directory</span> ,<span class="org-rainbow-identifiers-identifier-9">vdf-base-directory</span> + <span class="org-builtin">:base-extension</span> <span class="org-string">"png</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">|</span></span><span class="org-string">jpg</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">|</span></span><span class="org-string">gif</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">|</span></span><span class="org-string">pdf</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">|</span></span><span class="org-string">mp3</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">|</span></span><span class="org-string">ogg"</span> + <span class="org-builtin">:publishing-directory</span> ,<span class="org-rainbow-identifiers-identifier-15">vdf-assets-publishing-directory</span> + <span class="org-builtin">:recursive</span> <span class="org-rainbow-identifiers-identifier-8">t</span> + <span class="org-builtin">:publishing-function</span> <span class="org-rainbow-identifiers-identifier-5">org-publish-attachment</span> + <span class="org-rainbow-delimiters-depth-3">)</span> + <span class="org-rainbow-delimiters-depth-3">(</span><span class="org-string">"vdf"</span> <span class="org-builtin">:components</span> <span class="org-rainbow-delimiters-depth-4">(</span><span class="org-string">"vdf-notes"</span> <span class="org-string">"vdf-static-css"</span> <span class="org-string">"vdf-static-assets"</span><span class="org-rainbow-delimiters-depth-4">)</span><span class="org-rainbow-delimiters-depth-3">)</span> + <span class="org-rainbow-delimiters-depth-2">)</span><span class="org-rainbow-delimiters-depth-1">)</span> +</pre> +</div> + +<p> +J'utilise des variables, c'est pas indispensable mais c'est plus pratique. Il y a trois <i>components</i>, chacun définit les fichiers qu'il traite grâce à <code>:base-extension</code>. Intéressons-nous surtout à <code>vdf-notes</code> puisque c'est celui qui nous sort les fichiers <code>HTML</code> à partir des fichiers <code>org</code>. On se passe de table des matières grâce à <code>:with-toc nil</code>, on ne génère que le <i>body</i> grâce à <code>:body-only t</code> et on évite les numéros de sections avec <code>:section-numbers nil</code>. Le bonus vient avec <code>:htmlized-source t</code> puisque du coup, les parties sources (entre <code>#+BEGIN_SRC</code> et <code>#+END_SRC</code>) sont exportés avec les mêmes couleurs que j'ai dans ma configuration Emacs (avec les <a href="https://github.com/vdemeester/emacs-config#raindow-identifiers">rainbow-identifiers</a>, voir ce post <a href="https://medium.com/@evnbr/coding-in-color-3a6db2743a1e">là</a> également). +</p> + +<p> +Il ne reste plus qu'à publier le projet quand on le souhaite avec <code>org-publish</code> — je vous laisse lire la documentation pour savoir comment on fait. +</p> + +<p> +Et c'est tout bon. Un petit <code>bundle exec jekyll serve --watch</code> pour voir le rendu — et quand on est content on commit. Et c'est la fête <code>\o/</code>. +</p> + +<p> +C'est tout pour le moment, la fréquence des billets de ce blog devrait s'incrémenter un peu maintenant. +</p> +</div> +</div> + + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2015-05-09-migration-to-hugo/" title="Migration vers hugo">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2014-10-27-docker-1.3-ecosystem/" title="Docker 1.3 et son écosystème">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2015-05-09-migration-to-hugo/index.html b/www/vincent.demeester.fr/legacy/posts/2015-05-09-migration-to-hugo/index.html @@ -0,0 +1,229 @@ +<!DOCTYPE html> + +<html lang="fr"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2015-05-09-migration-to-hugo/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang="fr"/> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">Migration vers hugo</h1><a href='https://vincent.demeester.fr/posts/2015-05-09-migration-to-hugo/'></a> + <address class="signature"> + <span class="date">Sat, 9 May, 2015</span> + <span class="words">(500 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#developement">developement</a></li> + + + + + + <li class="tag tag-hugo"><a href="/tags/#hugo">hugo<span>1</span></a></li> + + + <li class="tag tag-golang"><a href="/tags/#golang">golang<span>12</span></a></li> + + + <li class="tag tag-blog"><a href="/tags/#blog">blog<span>2</span></a></li> + + + <li class="tag tag-emacs"><a href="/tags/#emacs">emacs<span>2</span></a></li> + + + <li class="tag tag-orgmode"><a href="/tags/#orgmode">orgmode<span>2</span></a></li> + + + <li class="tag tag-html"><a href="/tags/#html">html<span>2</span></a></li> + + <br/> + + </ul> + </header> + + + + <p> +Et voilà, une semaine après m'être remis en route sur ce blog, je fais +déjà une migration <code>>_<</code>. Je me suis rendu compte que <a href="http://jekyllrb.com/">Jekyll</a> +m'énervait un peu, et comme je suis à fond dans <a href="http://golang.org/">go</a> en ce moment, j'ai +regardé du côté des générateurs de sites static et <a href="http://gohugo.io/">Hugo</a> a clairement +retenu mon attention. +</p> + +<div id="outline-container-sec-1" class="outline-2"> +<h2 id="sec-1">Pourquoi Hugo ?</h2> +<div class="outline-text-2" id="text-1"> +<blockquote> +<p> +<b>Make the Web Fun Again</b> +</p> + +<p> +Introducing Hugo, a new idea around making website creation simple again. Hugo flexibly works with many formats and is ideal for blogs, docs, portfolios and much more. Hugo’s speed fosters creativity and makes building a website fun again. +</p> +</blockquote> + +<p> +Presque tout est dit dans cette citation. +</p> + +<ul class="org-ul"> +<li>Hugo est rapide. +</li> +<li>Le système de content est un régale. +</li> +<li>L'organisation d'un <i>projet hugo</i> est simple : +<ul class="org-ul"> +<li><code>content</code> pour le contenu, avec ce qu'on veut dedans, +</li> +<li><code>static</code> pour le contenu statique (<code>css</code>, <code>javascript</code>, <code>images</code>, …), +</li> +<li><code>layouts</code> pour les templates (super facile de mettre des templates différents en fonction des contenus, voir <a href="http://gohugo.io/templates/content/">ici</a>), +</li> +<li><code>themes</code> pour les themes applicables (qui définissent eux même <code>layouts</code> et <code>static</code>. +</li> +</ul> +</li> +<li>Le système de template est <a href="http://gohugo.io/templates/go-templates/">puissant</a> et un vrai régale. +</li> +<li>Le mode <code>server</code> a la possiblité de surveiller les fichiers(avec le <i>flag</i> <code>-w</code>, et implémente <a href="http://gohugo.io/extras/livereload/">LiveReload</a> ce qui est un petit bonus non négligeable. +</li> +</ul> +</div> +</div> + +<div id="outline-container-sec-2" class="outline-2"> +<h2 id="sec-2">Ce qui changes</h2> +<div class="outline-text-2" id="text-2"> +<p> +Le principale changement est sur le/les repository utilisés. Avant tout était dans <a href="https://github.com/vdemeester/vdemeester.github.com">vdemeester.github.com</a>, puisque c'est github qui générait le résultat final avec Jekyll. Ce n'est maintenant plus le cas : <a href="https://github.com/vdemeester/blog">blog</a> héberge les sources du blog (les fichiers content en Markdown ou <i>HTML-from-orgmode</i> ainsi que les templates et la configuration <a href="http://gohugo.io/">Hugo</a> — <a href="https://github.com/vdemeester/vdemeester.github.com">vdemeester.github.com</a> héberge désormais le résultat. +</p> + +<p> +J'en ai profité également pour faire quelques ajustements graphique, principalement sur les tags qui ont maintenant, parfois, des couleurs différentes. +</p> +</div> +</div> + +<div id="outline-container-sec-3" class="outline-2"> +<h2 id="sec-3">Intégration avec Emacs & orgmode</h2> +<div class="outline-text-2" id="text-3"> +<p> +Il me manque encore quelques petits <i>trucs</i> pour rendre ça automatique, mais dans l'idée, voilà le workflow que j'utilise quand je publie un article (comme maintenant) : +</p> + +<ol class="org-ol"> +<li>Démarre <code>hugo server -w</code> dans mon repository <code>blog</code>. +</li> +<li>Je créer mon fichier <code>org</code> au bon endroit (soit <code>$HOME/desktop/org/notes/vdf/monfichier.org</code>). Par rapport au <a href="http://vincent.demeester.fr/posts/2015-05-01-orgmode-et-jekyll/">billet précédent</a>, le header de mes fichiers <code>org</code> change un peu : + +<pre class="example"> +#+begin_html ++++ +title="Migration vers hugo" +date="2015-05-09" +categories=["developement"] +tags=["hugo","golang","blog","emacs","orgmode","html"] +lang="fr" ++++ +#+end_html + +Et voilà, une semaine après m'être remis en route sur ce blog, je fais déjà une migration =>_<=. Je me suis rendu compte que [[http://jekyllrb.com/][Jekyll]] m'énervait un peu, et comme je suis à fond dans [[http://golang.org/][go]] en ce moment, j'ai regardé du côté des générateurs de sites static et [[http://gohugo.io/][Hugo]] a clairement retenu mon attention. +</pre> +</li> + +<li>J'exécute <code>org-publish-current-project</code> (qu'il faut que je <i>bind</i> pour éviter de me le taper à la main..) +</li> +<li>Et je regarde le résultat dans mon navigateur (merci encore <a href="http://gohugo.io/extras/livereload/">LiveReload</a>). +</li> +</ol> + +<p> +Pour me simplifier la vie, il me resterais à démarrer et arrêter facilement <code>hugo server -w</code> dans le bon dossier directement depuis emacs (un peu à-la <code>compile</code>), <i>binder</i> <code>org-publish-current-project</code> et un petit script final qui copie le rendu de <code>blog</code> vers <code>vincent.demeester.fr</code> et qui commit (le tout appellable depuis Emacs <code>;-P</code>). +</p> +</div> +</div> + + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2015-06-01-docker-1.6-ecosystem/" title="Docker 1.6 et son écosystème">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2015-05-01-orgmode-et-jekyll/" title="Orgmode et Jekyll">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2015-06-01-docker-1.6-ecosystem/index.html b/www/vincent.demeester.fr/legacy/posts/2015-06-01-docker-1.6-ecosystem/index.html @@ -0,0 +1,610 @@ +<!DOCTYPE html> + +<html lang="fr"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2015-06-01-docker-1.6-ecosystem/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang="fr"/> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">Docker 1.6 et son écosystème</h1><a href='https://vincent.demeester.fr/posts/2015-06-01-docker-1.6-ecosystem/'></a> + <address class="signature"> + <span class="date">Mon, 1 June, 2015</span> + <span class="words">(3100 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#zenika">zenika</a></li> + + + + + + <li class="tag tag-docker"><a href="/tags/#docker">docker<span>3</span></a></li> + + + <li class="tag tag-compose"><a href="/tags/#compose">compose<span>1</span></a></li> + + + <li class="tag tag-swarm"><a href="/tags/#swarm">swarm<span>1</span></a></li> + + + <li class="tag tag-machine"><a href="/tags/#machine">machine<span>1</span></a></li> + + + <li class="tag tag-cluster"><a href="/tags/#cluster">cluster<span>1</span></a></li> + + <br/> + + </ul> + </header> + + + + +<div class="notice">Cet article est disponible sur le <a href="http://blog.zenika.com/">Blog de Zenika</a> à l'adresse suivante : <a href="http://blog.zenika.com/index.php?post/2015/06/01/Docker-1-6-et-son-ecosysteme">http://blog.zenika.com/index.php?post/2015/06/01/Docker-1-6-et-son-ecosysteme</a>. Cet publication me sert de mirroir / sauvegarde.</div> + +<div id="outline-container-sec-1" class="outline-2"> +<h2 id="sec-1">Introduction</h2> +<div class="outline-text-2" id="text-1"> +<p> +Le 28 octobre dernier, nous avions parlé de la sortie de Docker 1.3, des évolutions entre la version 1 et cette dernière et de son écosystème. Je vous proposes de remettre ça, bientôt 6 mois après, avec un peu le même plan : les principales nouveautés entre la version 1.3 et 1.6 (et il y en a <code>;-)</code>), l'évolution de l'écosystème qui gravite autour et un peu de <i>social</i> avec les meetups et évènements qui se sont passés depuis. +</p> + + +<div class="figure"> +<p><img src="./img/docker-16/docker_container_engine_logo.png" alt="docker_container_engine_logo.png" /> +</p> +</div> + +<p> +Rappel <i>ultra</i> rapide, <b>Docker est une plate-forme ouverte à destination des développeurs et administrateurs systèmes visant à faciliter la construction et le déploiement d'applications distribuées</b>. De manière moins marketing, l'idée derrière Docker est d'automatiser le déploiement d'environnements sous forme de conteneurs légers, portables et auto-suffisants ; les conteneurs permettant d'isoler l'exécution des applications dans des contextes d'exécution. Pour ce +faire, Docker, écrit en Go, reprend les bases de LXC, utilise les fonctionnalités du noyau Linux (CGroups, Namespaces, …) et se base initialement sur un système de fichier "en oignons" AUFS ; D'autres backends sont supportés également comme BTRFS ou devicemapper (LVM). +</p> +</div> +</div> + +<div id="outline-container-sec-2" class="outline-2"> +<h2 id="sec-2">Ovelay filesystem storage driver (1.4.0)</h2> +<div class="outline-text-2" id="text-2"> +<p> +La release 1.4.0 de Docker (et la 1.3.3 en parallèle) a surtout été une gigantesque <i>bugfix party</i>, histoire de rendre les fonctionnalités arrivées auparavant plus stable — la release note se trouve <a href="https://github.com/docker/docker/blob/master/CHANGELOG.md#140-2014-12-11">ici</a>. +</p> + +<p> +La principale nouveautée de cette version est l'apparation d'un nouveau <i>storage driver</i>, il s'agit d'<b>OverlayFs</b>. Il s'agit d'un mécanisme de montage permettant de superposer dans un répertoire le contenu de plusieurs autres répertoires. +</p> + +<p> +Initialement Docker est basé <b><a href="http://en.wikipedia.org/wiki/Aufs">Aufs</a></b> qui fait, pour simplifier, la même chose. Le problème avec aufs est qu'il n'est pas intégré dans le noyau Linux (i.e. dans les sources officielles), contrairement à OverlayFS qui a fait son apparition avec le noyau <a href="https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=e9be9d5e76e34872f0c37d72e25bc27fe9e2c54c">3.18</a>. Il était donc nécessaire de disposer d'un noyau patché ; Le noyau Linux de Debian et de sess dérivés (Ubuntu, etc.) ont ce <i>patchset</i> aufs de base mais ce n'est pas le cas de toutes les distributions. L'idée de cette intégration est assez simple : supporter le maximum de distributions en se basant sur une <i>feature</i> du noyau. C'est donc bien évidemment le <i>driver</i> d'avenir pour Docker ; attention cependant, la peinture est encore un peu fraîche <code>;-P</code>. +</p> +</div> +</div> + +<div id="outline-container-sec-3" class="outline-2"> +<h2 id="sec-3">Support d'IPv6 (1.5.0)</h2> +<div class="outline-text-2" id="text-3"> +<p> +Les adresses IPv4 commencent a se faire <a href="http://en.wikipedia.org/wiki/IPv4_address_exhaustion">rare</a>, il est donc important que Docker supporte IPv6. C'est désormais le cas avec la version 1.5.0, même si ce n'est pas activé par défaut. Pour activer le support de l'IPv6 (en plus de l'IPv4), il faut ajouter le <i>flag</i> <code>--ipv6</code> au daemon. Docker va donc mettre en place le <i>bridge</i> <code>docker0</code> avec en plus un IPv6 en mode local, avec l'adresse <code>fe80::1</code>. +</p> + +<p> +Par défaut les containers qui seront créés n'auront qu'une adresse locale. Pour avoir une adresse IPv6 routable à votre conteneur, il est nécessaire de lui préciser un <i>sous-réseau</i> (subnet) dans lequel il va piocher son adresse. Cela se fait grâce à l'argument <code>--fixed-cidr-v6</code>. +</p> + + +<div class="org-src-container"> + +<pre class="src src-sh"><span style="color: #783778;">docker</span> <span style="color: #43783f;">-d</span> <span style="color: #374478;">--ipv6</span> <span style="color: #78683f;">--fixed-cidr-v6</span>=<span style="color: #008000;">"2001:db8:1::/64"</span> +</pre> +</div> + +<p> +Comme je ne suis pas un pro de l'IPv6, pour plus d'information, et si l'anglais ne vous fait pas peur, c'est dans la <a href="https://docs.docker.com/articles/networking/#ipv6">documentation "networking"</a> de Docker. +</p> +</div> +</div> + +<div id="outline-container-sec-4" class="outline-2"> +<h2 id="sec-4">Conteneurs en lecture seule (1.5.0)</h2> +<div class="outline-text-2" id="text-4"> +<p> +Une autre fonctionnalité assez sympathique qui est arrivé avec cette version 1.5.0 est les conteneurs en lecture seule — c'est Michael Crosby qui s'est occupé d'<a href="https://github.com/docker/docker/pull/10093">implémenter ça</a>. L'intérêt des conteneurs en lecture seule est de permettre de <b>contrôler où l'application</b> à l'intérieur de votre conteneur <b>peut écrire ou modifier des fichiers</b>. En combinant ceci avec les volumes, vous pouvez vous assurez des emplacements dans lesquels votre conteneur va persister des états ou données (le/les volumes), puisqu'il ne sera pas possible d'écrire ailleurs de toute façon. +</p> + +<p> +Pour activer cette fonctionnalité, c'est l'argument <code>--read-only</code>. +</p> + +<div class="org-src-container"> + +<pre class="src src-sh"><span style="color: #783778;">docker</span> <span style="color: #513f78;">run</span> <span style="color: #43783f;">--read-only</span> <span style="color: #5e7837;">-v</span> <span style="color: #5e7837;">/volume/writable</span> <span style="color: #3f7178;">busybox</span> <span style="color: #783778;">touch</span> <span style="color: #5e7837;">/volume/writable</span> +</pre> +</div> + +<p> +Une autre utilisation des conteneurs en lecture seule est que cela donne la possibilité de faire du debug <i>post-mortem</i> d'un conteneur (en production par exemple). Cela nous permet de redémarrer un conteneur qui aurait planté, en lecture seule avec le système de fichier dans l'état du crash. +</p> +</div> +</div> + +<div id="outline-container-sec-5" class="outline-2"> +<h2 id="sec-5">Les labels pour le « daemon », les images et les conteneurs (1.6.0)</h2> +<div class="outline-text-2" id="text-5"> +<blockquote> +<p> +One Meta Data to Rule Them All +</p> +</blockquote> + +<p> +Une des deux fonctionnalités très attendue de la récente version 1.6.0 sont les labels. En un mot, et pour le faire « à-la » <i>le seigneur des anneaux</i>, les labels peuvent se résumer en "<b>Une metadata pour les gouverner tous</b>" (ça le fait vachement mieux en anglais en fait). +</p> + +<p> +Les labels s'appliquent sur le <i>daemon</i>, les images et les conteneurs. C'est un peu un mélange entre des tags et des variables d'environnements puisque il s'agit d'un couple <b>clé/valeur</b>. +</p> + +<p> +L'ajout de label sur le <i>daemon</i> se fait grâce à l'argument — roulement de tambour — <code>--label</code> (<code>\o/</code>). La principale utilité pour l'instant est son utilisation conjointe avec Swarm dont nous parlerons un peu plus bas ; mais en deux mots, cela permet de filtrer les <i>engines</i> sur lesquels on va <i>taper</i>. +</p> + +<div class="org-src-container"> + +<pre class="src src-sh"><span style="color: #8D8D84;"># </span><span style="color: #8D8D84; font-style: italic;">Souvent, c'est dans DOCKER_OPTS du fichier /etc/default/docker</span> +<span style="color: #783778;">docker</span> <span style="color: #43783f;">-d</span> <span style="color: #784437;">-H</span> <span style="color: #4f5c7e;">unix://var/run/docker.sock</span> <span style="color: #7a4f7e;">--label</span> <span style="color: #BA36A5;">storage</span>=<span style="color: #5e7837;">ssd</span> <span style="color: #7a4f7e;">--label</span> <span style="color: #BA36A5;">type</span>=<span style="color: #5e7837;">laptop</span> +</pre> +</div> + +<p> +L'ajout d'un label sur une image se fait dans le fichier <code>Dockerfile</code>, et l'ajout d'un label sur un conteneur, grâce à l'argument <code>--label</code> pour rester cohérent. Construisons une image inutile mais en lui appliquant un label : +</p> + +<div class="org-src-container"> + +<pre class="src src-sh"><span style="color: #37785e;">FROM</span> <span style="color: #3f7178;">busybox</span> +<span style="color: #8D8D84;"># </span><span style="color: #8D8D84; font-style: italic;">Support du multi-line pour LABEL</span> +<span style="color: #783778;">LABEL</span> <span style="color: #BA36A5;">vendor</span>=<span style="color: #78683f;">zenika</span> <span style="color: #008000;">\</span> + <span style="color: #784437;">com.zenika.lang</span>=<span style="color: #374478;">golang</span> <span style="color: #008000;">\</span> + <span style="color: #78683f;">com.zenika.version</span>=<span style="color: #43783f;">0.1</span> +<span style="color: #7a4f7e;">CMD</span> [<span style="color: #008000;">"echo"</span><span style="color: #7e544f;">,</span> <span style="color: #008000;">"zenika"</span>] +</pre> +</div> + +<p> +Nous allons maintenant construire cette image et lancer un conteneur à partir de cette dernière avec un autre label. +</p> + +<div class="org-src-container"> + +<pre class="src src-sh">$ <span style="color: #783778;">docker</span> <span style="color: #7e544f;">build</span> <span style="color: #7a4f7e;">-t</span> <span style="color: #4f7e67;">zenikaapp</span> <span style="color: #374478;">.</span> +<span style="color: #8D8D84;"># </span><span style="color: #8D8D84; font-style: italic;">[…]</span> +$ <span style="color: #783778;">docker</span> <span style="color: #513f78;">run</span> <span style="color: #4f5c7e;">--name</span> <span style="color: #783f5a;">test</span> <span style="color: #7a4f7e;">--label</span> <span style="color: #783f5a;">com.zenika.foo</span>=<span style="color: #784437;">bar</span> <span style="color: #4f7e67;">zenikaapp</span> +<span style="color: #78683f;">zenika</span> +</pre> +</div> + +<p> +L'idée c'est que maintenant, lorsque l'on va regarder la liste d'images ou de conteneurs à disposition sur notre <i>engine</i>, nous allons pouvoir <b>filtrer</b> par label, comme suit : +</p> + +<div class="org-src-container"> + +<pre class="src src-sh">$ <span style="color: #783778;">docker</span> <span style="color: #707e4f;">images</span> <span style="color: #4f5c7e;">--filter</span> <span style="color: #008000;">"label=vendor=zenika"</span> <span style="color: #4f5c7e;">--filter</span> <span style="color: #008000;">"label=com.zenika.lang=golang"</span> +<span style="color: #784437;">REPOSITORY</span> <span style="color: #3f7178;">TAG</span> <span style="color: #707e4f;">IMAGE</span> <span style="color: #707e4f;">ID</span> <span style="color: #7e544f;">CREATED</span> <span style="color: #784437;">VIRTUAL</span> <span style="color: #4f7e67;">SIZE</span> +<span style="color: #4f7e67;">zenikaapp</span> <span style="color: #7a4f7e;">latest</span> <span style="color: #784437;">66ffda023118</span> <span style="color: #5e7837;">43</span> <span style="color: #7e544f;">seconds</span> <span style="color: #43783f;">ago</span> <span style="color: #783778;">2.433</span> <span style="color: #7e544f;">MB</span> +$ <span style="color: #783778;">docker</span> <span style="color: #43783f;">ps</span> <span style="color: #784437;">-a</span> <span style="color: #4f5c7e;">--filter</span> <span style="color: #008000;">"label=com.zenika.foo=bar"</span> +<span style="color: #374478;">CONTAINER</span> <span style="color: #707e4f;">ID</span> <span style="color: #707e4f;">IMAGE</span> <span style="color: #78683f;">COMMAND</span> <span style="color: #7e544f;">CREATED</span> [<span style="color: #374478;">…</span>] <span style="color: #707e4f;">NAMES</span> +<span style="color: #513f78;">37e9a37caf57</span> <span style="color: #513f78;">zenikaapp:latest</span> <span style="color: #008000;">"echo zenika"</span> <span style="color: #43783f;">About</span> <span style="color: #78683f;">a</span> <span style="color: #37785e;">minute</span> <span style="color: #43783f;">ago</span> [<span style="color: #374478;">…</span>] <span style="color: #783f5a;">test</span> +<span style="color: #8D8D84;"># </span><span style="color: #8D8D84; font-style: italic;">On peut également regarder les labels avec inspect</span> +$ <span style="color: #783778;">docker</span> <span style="color: #374478;">inspect</span> <span style="color: #3f7178;">-f</span> <span style="color: #008000;">"{{json .ContainerConfig.Labels }}"</span> <span style="color: #783f5a;">zenikaap</span> +{<span style="color: #008000;">"com.zenika.lang"</span><span style="color: #707e4f;">:</span><span style="color: #008000;">"golang"</span><span style="color: #7e544f;">,</span><span style="color: #008000;">"com.zenika.version"</span><span style="color: #707e4f;">:</span><span style="color: #008000;">"0.1"</span><span style="color: #7e544f;">,</span><span style="color: #008000;">"vendor"</span><span style="color: #707e4f;">:</span><span style="color: #008000;">"zenika"</span>} +$ <span style="color: #783778;">docker</span> <span style="color: #374478;">inspect</span> <span style="color: #3f7178;">-f</span> <span style="color: #008000;">"{{json .Config.Labels }}"</span> <span style="color: #783f5a;">test</span> +{<span style="color: #008000;">"com.zenika.foo"</span><span style="color: #707e4f;">:</span><span style="color: #008000;">"bar"</span><span style="color: #7e544f;">,</span><span style="color: #008000;">"com.zenika.lang"</span><span style="color: #707e4f;">:</span><span style="color: #008000;">"golang"</span><span style="color: #7e544f;">,</span><span style="color: #008000;">"com.zenika.version"</span><span style="color: #707e4f;">:</span><span style="color: #008000;">"0.1"</span><span style="color: #7e544f;">,</span><span style="color: #008000;">"vendor"</span><span style="color: #707e4f;">:</span><span style="color: #008000;">"zenika"</span>} +</pre> +</div> + +<p> +On peut imaginer beaucoup d'usage de ces labels. Par example, avec <a href="http://rancher.com">Rancher</a>, ils sont utilisés pour faciliter la configuration du load-balancer (<a href="http://rancher.com/docker-labels/">ici</a>) — ils utilisent un label <code>io.rancher.service.provides</code> qui permettra à ce dernier de trouver automatiquement ces petits. Je vous laisse imaginer vos propres <i>use-cases</i>. +</p> + +<p> +Il y a une partie de la documentation qui parle exclusivement des labels, avec une petite partie sur les <i>best-practice</i> de nommage des labels, c'est <a href="https://docs.docker.com/userguide/labels-custom-metadata/">ici</a>. +</p> +</div> +</div> + +<div id="outline-container-sec-6" class="outline-2"> +<h2 id="sec-6">Logging drivers \o/ (1.6.0)</h2> +<div class="outline-text-2" id="text-6"> +<p> +Un <i>gros reproche</i> qui était fait à Docker était sa gestion très <b>simpliste</b> des logs des conteneurs. Plusieurs critiques étaient faites : +</p> + +<ol class="org-ol"> +<li>Tout faire sortir sur <code>stdout</code> et <code>stderr</code> n'est pas vraiment une habitude de nos jours, surtout dans des langages comme Java où l'utilisation de <i>logger</i> (Log4j, Slf4j, …) est très répandue. Cela rend le portage vers docker de certaines applications un peu plus fastidieux. +</li> +<li>Il n'y avait aucun mécanisme de <i>rotation</i> de logs — et comme en plus le dossier dans lequel les logs étaient écris est un peu enfoui dans <code>/var/lib/docker</code>, cela pouvait poser quelques problème si des conteneurs étaient un peu trop bavards.. +</li> +<li>La solution utilisée pour sauvegarder ces logs et pourquoi pas les centraliser (avec ELK par exemple), était d'utiliser un volume, souvent partagé entre applications, et de démarrer un conteneur pour gérer cette analyse, centralisation, …. Non seulement ce n'est pas très optimal, mais cela nécessitait de configurer chaque application (donc chaque conteneur) — et <code>docker logs</code> perdait tout son intérêt. +</li> +</ol> + +<p> +Avec la version 1.6.0, les <i>logging driver</i> permettent une gestion des logs un peu plus optimale, ou au moins plus flexible. Il est donc maintenant possible de préciser le <i>logging driver</i> à utiliser. Ils en existent 3 pour l'instant : +</p> + +<ol class="org-ol"> +<li><code>json-file</code> correspond au comportement par défaut de Docker avant la 1.6 et reste la valeur par défaut +</li> +<li><code>syslog</code> qui permet de connecter les logs de nos conteneurs dans notre vénérable syslog (ou en tout cas quelqu'un qui parl le syslog). +</li> +<li><code>none</code> qui est le magicien puisqu'il nous permet de faire taire complètement un conteneur <code>o/</code>. +</li> +</ol> + +<p> +Il est possible de définir le logging driver à deux endroits : +</p> + +<ol class="org-ol"> +<li>sur le <b>daemon</b> pour la valeur par défaut de tous les conteneurs. +</li> +</ol> + +<div class="org-src-container"> + +<pre class="src src-sh">$ <span style="color: #783778;">docker</span> <span style="color: #43783f;">-d</span> <span style="color: #5e7837;">--log-driver</span>=<span style="color: #008000;">"json-file"</span> +<span style="color: #8D8D84;"># </span><span style="color: #8D8D84; font-style: italic;">Pour faire taire les conteneurs par défaut</span> +$ <span style="color: #783778;">docker</span> <span style="color: #43783f;">-d</span> <span style="color: #5e7837;">--log-driver</span>=<span style="color: #008000;">"none"</span> +</pre> +</div> + +<ol class="org-ol"> +<li>en option de la command <b>run</b> (ou de la commande <b>create</b>). +</li> +</ol> + +<div class="org-src-container"> + +<pre class="src src-sh">$ <span style="color: #783778;">docker</span> <span style="color: #513f78;">run</span> <span style="color: #5e7837;">--log-driver</span>=<span style="color: #3f7178;">syslog</span> <span style="color: #3f7178;">ubuntu</span> <span style="color: #008000;">\</span> + <span style="color: #37785e;">/bin/bash</span> <span style="color: #784437;">-c</span> <span style="color: #008000;">'while true; do echo "Hello"; sleep1; done'</span> +$ <span style="color: #707e4f;">tail</span> <span style="color: #3f7178;">-f</span> <span style="color: #784437;">/var/log/syslog</span> +<span style="color: #8D8D84;"># </span><span style="color: #8D8D84; font-style: italic;">[…]</span> +<span style="color: #374478;">May</span> <span style="color: #3f7178;">28</span> <span style="color: #3f7178;">17:39:01</span> <span style="color: #7e544f;">dev1</span> <span style="color: #783778;">docker</span>[<span style="color: #5e7837;">116314</span>]<span style="color: #707e4f;">:</span> <span style="color: #513f78;">0e5b67244c00:</span> <span style="color: #4f7e67;">Hello</span> +<span style="color: #8D8D84;"># </span><span style="color: #8D8D84; font-style: italic;">[…]</span> +</pre> +</div> + +<p> +Une option <code>--log-opts</code> est également présente pour passer des options additionnelles au driver si celui-ci les supporte. Notons également qu'un driver pour <code>systemd</code> devrait arriver avec la version 1.7. +</p> + +<p> +La pull-request ayant permis d'intégrer cette fonctionnalité se trouve <a href="https://github.com/docker/docker/pull/10568">ici</a>. +</p> +</div> +</div> + +<div id="outline-container-sec-7" class="outline-2"> +<h2 id="sec-7">Client Windows natif (1.6.0)</h2> +<div class="outline-text-2" id="text-7"> +<p> +Enfin, on s'en doutait un peu après l'annonce du partenariat entre Docker Microsoft, ça bosse beaucoup pour porter Docker vers Windows. La première étape était de fournir un client natif pour Windows. C'est chose faite avec cette version 1.6. Maintenant beaucoup de travail est effectué pour rendre le <i>engine</i> plus portable, il n'y a qu'à suivre un peu les pull-request avec un tag <code>os/windows</code> ou encore cette très récente pull-request avec un titre plutôt évocateur : « <a href="https://github.com/docker/docker/pull/13554">Windows: The real Windows exec driver is here</a> ». +</p> +</div> +</div> + +<div id="outline-container-sec-8" class="outline-2"> +<h2 id="sec-8">Divers</h2> +<div class="outline-text-2" id="text-8"> +<p> +Il y a pas mal d'autres options qui sont arrivées depuis la version 1.3.0, nous allons en parcourir certaines rapidement — parce que sinon cet article va faire 100 pages ;-p : +</p> + +<ul class="org-ul"> +<li><b>Stats</b> (1.5.0) : une commande <code>stats</code> (et donc une API derrière) permet de récupérer quelques statistiques par conteneur, c'est simple pour l'instant. +</li> +<li>Depuis la version 1.5.0 il est possible de spécifier le ficher Dockerfile grâce à l'option <code>-f</code> de la commande build — jusqu'à maintenant docker regardait uniquement le dossier spécifié et cherchait le fichier <code>Dockerfile</code>. Cela permet donc, par exemple, d'avoir plusieurs <code>Dockerfile</code> dans un dossier. +</li> +</ul> + +<div class="org-src-container"> + +<pre class="src src-sh">$ <span style="color: #783778;">docker</span> <span style="color: #7e544f;">build</span> <span style="color: #7a4f7e;">-t</span> <span style="color: #5e7837;">monimage</span> <span style="color: #3f7178;">-f</span> <span style="color: #43783f;">backend.Dockerfile</span> <span style="color: #374478;">.</span> +<span style="color: #8D8D84;"># </span><span style="color: #8D8D84; font-style: italic;">[…] Build the backend</span> +$ <span style="color: #783778;">docker</span> <span style="color: #7e544f;">build</span> <span style="color: #7a4f7e;">-t</span> <span style="color: #5e7837;">monimage</span> <span style="color: #3f7178;">-f</span> <span style="color: #4f7e67;">frontend.Dockerfile</span> <span style="color: #374478;">.</span> +<span style="color: #8D8D84;"># </span><span style="color: #8D8D84; font-style: italic;">[…] Build the frontend</span> +</pre> +</div> + +<ul class="org-ul"> +<li>Le <i>registry</i> voit son API passer en V2, principalement pour améliorer les transferts. L'implémentation officielle a été réécrite en Go (à la place de Python) et se nomme maintenant <a href="https://github.com/docker/distribution">distribution</a>. +</li> +<li>La commande <code>commit est dotée, depuis la version 1.6.0, d'une option =--change</code> qui permet d'appliquer une instruction supportée par les <code>Dockerfile</code> — voir <a href="https://docs.docker.com/reference/commandline/cli/#commit">ici</a>. +</li> +<li>Docker a publié un petit document « <a href="https://github.com/docker/docker/blob/master/image/spec/v1.md">Docker Image Specification</a> » qui a pour but de définir le format des images utilisées par Docker, permettant à d'autres notamment des potentiels conccurents, d'implémenter des images qui seraient compatible. +</li> +</ul> +</div> +</div> + +<div id="outline-container-sec-9" class="outline-2"> +<h2 id="sec-9">À venir</h2> +<div class="outline-text-2" id="text-9"> +<p> +L'une des principales nouveautés qui devrait arriver avec la version 1.7 de Docker, c'est l'intégration d'une <b>nouvelle stack réseau</b> avec l'intégration du projet <a href="https://github.com/docker/libnetwork">libnetwork</a>, si tout se passe bien. On pourra noter également de nouveaux <i>logging driver</i>, avec notamment un <code>rollover</code> driver ou encore le <code>systemd</code> driver. On peut noter également l'arrivée d'un <i>filesystem driver</i> pour ZFS (voici la <a href="https://github.com/docker/docker/pull/9411">pull-request</a>). Le Docker Birthday étant passé par là, beaucoup de corrections de bugs, de nouvelles petites fonctionnalités, une meilleure couverture de code par les tests unitaires (<code>o/</code>). +</p> + +<p> +La RC1 est disponible depuis le 28 mai, <a href="https://github.com/docker/docker/releases/tag/v1.7.0-rc1">ici</a> et la <a href="https://github.com/docker/docker/pull/13528">pull-request</a> associée, donc à vos tests ! +</p> +</div> +</div> + +<div id="outline-container-sec-10" class="outline-2"> +<h2 id="sec-10">Écosystème</h2> +<div class="outline-text-2" id="text-10"> +<p> +Trois projets « Docker » sont apparus depuis le dernier article : Compose, Swarm et Machine. Présentons les très rapidement. +</p> +</div> + +<div id="outline-container-sec-10-1" class="outline-3"> +<h3 id="sec-10-1">Compose</h3> +<div class="outline-text-3" id="text-10-1"> +<p> +Compose est le nouveau nom de Fig. Fig était développé par Orchard qui a été racheté par Docker. Pour rappel, l'idée est de définir son environnement via un fichier YAML, que ce soit pour le code sur lequel nous travaillons mais également les services externes desquels notre application dépend (Base de données, ''Message queue'', etc.). +</p> + +<div class="org-src-container"> + +<pre class="src src-yaml"><span style="color: #BA36A5;">web</span>: + <span style="color: #BA36A5;">build</span>: . + <span style="color: #BA36A5;">command</span>: lein run + <span style="color: #BA36A5;">links</span>: + - db + <span style="color: #BA36A5;">ports</span>: + - <span style="color: #008000;">"8000:8000"</span> +<span style="color: #BA36A5;">db</span>: + <span style="color: #BA36A5;">image</span>: postgres +</pre> +</div> + +<p> +Compose est en version 1.2 — depuis la version 1.0, la majorité des modifications sont des corrections de bugs et des ajouts pour suivre les modifications et nouvelles fonctionnalités de Docker (<code>env-file</code>, <code>dns_search</code>, <code>add_host</code>, <code>restart</code>, <code>volumes_from</code>, <code>net</code>, …). La commande est maintenant <code>docker-compose</code> à la place de <code>fig</code> et le fichier <code>docker-compose.yml</code> à la place de <code>fig.yml</code> — pour des raisons de rétro-compatibilité, Compose continue de lire les <code>fig.yml</code>. +</p> +</div> +</div> + +<div id="outline-container-sec-10-2" class="outline-3"> +<h3 id="sec-10-2">Swarm & Machine</h3> +<div class="outline-text-3" id="text-10-2"> +<p> +Deux nouveaux projets ont fait leur apparition dans l'escarcelle de Docker Inc. : Swarm et Machine. Swarm est le <i>clustering</i> à moyenne échelle vu par Docker. Machine permet de provisionner Docker sur différents providers : amazon aws, google compute engine, azure, Virtualbox pour ne citer qu'eux — mais beaucoup d'autres sont déjà supportés. +</p> + +<p> +Voilà ce que Jérôme Petazzoni dit à propos de Swarm : +</p> + +<blockquote> +<p> +Un système de cluster utilisant l’API Docker et compatible avec tous les outils de l’écosystème maison. On peut utiliser les commandes classiques Docker pour piloter le cluster +</p> +</blockquote> + +<p> +L'idée principale de Swarm est <b>Je veux parler à mon cluster Docker de la même façon que je parle avec mon daemon Docker</b>. Cela se traduit assez simplement par : <b>Swarm expose la même API que docker</b>. C'est une idée simple et terriblement puissante puisque cela veut dire que je peux administrer mon cluster avec les mêmes commandes que j'utilise quand je travaille en local. Swarm a pour but de piloter des clusters d'une taille relativement petite (moins de 1000 machines). Pour les clusters de plus grande taille, il existe de très bonnes solutions, comme <a href="http://mesos.apache.org/">Mesos</a>, et ce n'est pas le but de Docker Inc. de venir les concurrencer, bien au contraire. +</p> + +<p> +Pour faire simple, swarm c'est un <b>manager</b> et des <b>agents</b> (un par engine) — les agents s'enregistrent auprès du master par le biais d'un <i>service discovery</i>. Swarm dispose d'un petit service de discovery mais qui n'est là que pour <i>la démo</i> ; il est possible et conseillé de le connecter à des solutions existantes, pour l'instant <a href="https://github.com/hashicorp/consul">consul</a> et <a href="https://github.com/coreos/etcd">etcd</a>. +</p> + +<p> +Un bout de code vaut mieux qu'un long discours, voici comment <i>bootstraper</i> un cluster Swarm, avec l'aide de Machine pour être <i>funky</i>. +</p> + +<div class="org-src-container"> + +<pre class="src src-sh"><span style="color: #8D8D84;"># </span><span style="color: #8D8D84; font-style: italic;">On crée un cluster simple avec son id</span> +$ <span style="color: #7a4f7e;">swarm</span> <span style="color: #783f5a;">create</span> +<span style="color: #3f7178;">50862dcedd53c2f584adfb00e85bac4b</span> +<span style="color: #8D8D84;"># </span><span style="color: #8D8D84; font-style: italic;">On démarre des agents</span> +$ <span style="color: #43783f;">docker-machine</span> <span style="color: #783f5a;">create</span> <span style="color: #43783f;">-d</span> <span style="color: #374478;">azure</span> <span style="color: #4f5c7e;">--swarm</span> <span style="color: #4f5c7e;">--swarm-discovery</span> <span style="color: #4f7e67;">token://50862dcedd53c2f584adfb00e85bac4b</span> <span style="color: #43783f;">node1</span> +<span style="color: #3f7178;">INFO</span>[<span style="color: #7a4f7e;">0000</span>] <span style="color: #5e7837;">Creating</span> <span style="color: #513f78;">SSH</span> <span style="color: #5e7837;">key...</span> +<span style="color: #8D8D84;"># </span><span style="color: #8D8D84; font-style: italic;">[…]</span> +$ <span style="color: #43783f;">docker-machine</span> <span style="color: #783f5a;">create</span> <span style="color: #43783f;">-d</span> <span style="color: #7e544f;">digitalocean</span> <span style="color: #4f5c7e;">--swarm</span> <span style="color: #4f5c7e;">--swarm-discovery</span> <span style="color: #4f7e67;">token://50862dcedd53c2f584adfb00e85bac4b</span> <span style="color: #783f5a;">node2</span> +<span style="color: #3f7178;">INFO</span>[<span style="color: #7a4f7e;">0000</span>] <span style="color: #5e7837;">Creating</span> <span style="color: #513f78;">SSH</span> <span style="color: #5e7837;">key...</span> +<span style="color: #8D8D84;"># </span><span style="color: #8D8D84; font-style: italic;">[…]</span> +<span style="color: #8D8D84;"># </span><span style="color: #8D8D84; font-style: italic;">On démarre le master</span> +$ <span style="color: #43783f;">docker-machine</span> <span style="color: #783f5a;">create</span> <span style="color: #43783f;">-d</span> <span style="color: #78683f;">virtualbox</span> <span style="color: #4f5c7e;">--swarm</span> <span style="color: #707e4f;">--swarm-master</span> <span style="color: #4f5c7e;">--swarm-discovery</span> <span style="color: #4f7e67;">token://50862dcedd53c2f584adfb00e85bac4b</span> <span style="color: #374478;">manager</span> +</pre> +</div> + +<p> +Maintenant que l'on dispose d'un petit cluster, en pointant dessus (merci Machine) on va pouvoir lancer des commandes docker. +</p> + +<div class="org-src-container"> + +<pre class="src src-sh">$ $(<span style="color: #43783f;">docker-machine</span> <span style="color: #7a4f7e;">env</span> <span style="color: #4f5c7e;">--swarm</span> <span style="color: #374478;">manager</span>) +<span style="color: #8D8D84;"># </span><span style="color: #8D8D84; font-style: italic;">Le bon vieux info</span> +$ <span style="color: #783778;">docker</span> <span style="color: #784437;">info</span> +<span style="color: #3f7178;">Containers:</span> <span style="color: #4f5c7e;">4</span> +<span style="color: #4f5c7e;">Nodes:</span> <span style="color: #7e544f;">3</span> + <span style="color: #374478;">manager:</span> <span style="color: #7e544f;">192.168.99.103:2376</span> + <span style="color: #78683f;">└</span> <span style="color: #3f7178;">Containers:</span> <span style="color: #513f78;">2</span> + <span style="color: #78683f;">└</span> <span style="color: #4f7e67;">Reserved</span> <span style="color: #4f5c7e;">CPUs:</span> <span style="color: #784437;">0</span> <span style="color: #783778;">/</span> <span style="color: #4f5c7e;">4</span> + <span style="color: #78683f;">└</span> <span style="color: #4f7e67;">Reserved</span> <span style="color: #7a4f7e;">Memory:</span> <span style="color: #784437;">0</span> <span style="color: #4f7e67;">B</span> <span style="color: #783778;">/</span> <span style="color: #707e4f;">999.9</span> <span style="color: #783f5a;">MiB</span> + <span style="color: #78683f;">node1:</span> <span style="color: #513f78;">45.55.160.223:2376</span> + <span style="color: #78683f;">└</span> <span style="color: #3f7178;">Containers:</span> <span style="color: #78683f;">1</span> + <span style="color: #78683f;">└</span> <span style="color: #4f7e67;">Reserved</span> <span style="color: #4f5c7e;">CPUs:</span> <span style="color: #784437;">0</span> <span style="color: #783778;">/</span> <span style="color: #78683f;">1</span> + <span style="color: #78683f;">└</span> <span style="color: #4f7e67;">Reserved</span> <span style="color: #7a4f7e;">Memory:</span> <span style="color: #784437;">0</span> <span style="color: #4f7e67;">B</span> <span style="color: #783778;">/</span> <span style="color: #4f7e67;">490</span> <span style="color: #783f5a;">MiB</span> + <span style="color: #513f78;">node2:</span> <span style="color: #707e4f;">swarm-nwde2.cloudapp.net:2376</span> + <span style="color: #78683f;">└</span> <span style="color: #3f7178;">Containers:</span> <span style="color: #78683f;">1</span> + <span style="color: #78683f;">└</span> <span style="color: #4f7e67;">Reserved</span> <span style="color: #4f5c7e;">CPUs:</span> <span style="color: #784437;">0</span> <span style="color: #783778;">/</span> <span style="color: #78683f;">1</span> + <span style="color: #78683f;">└</span> <span style="color: #4f7e67;">Reserved</span> <span style="color: #7a4f7e;">Memory:</span> <span style="color: #784437;">0</span> <span style="color: #4f7e67;">B</span> <span style="color: #783778;">/</span> <span style="color: #7e544f;">1.639</span> <span style="color: #3f7178;">GiB</span> +<span style="color: #8D8D84;"># </span><span style="color: #8D8D84; font-style: italic;">On démarre des nginx</span> +$ <span style="color: #4f7e67;">for</span> <span style="color: #7a4f7e;">i</span><span style="color: #0000FF;"> in</span> <span style="color: #FF1493;">`seq 1 3`</span>; <span style="color: #0000FF;">do</span> <span style="color: #783778;">docker</span> <span style="color: #513f78;">run</span> <span style="color: #43783f;">-d</span> <span style="color: #783f5a;">-p</span> <span style="color: #7a4f7e;">80:80</span> <span style="color: #783778;">nginx</span>; <span style="color: #0000FF;">done</span> +$ <span style="color: #783778;">docker</span> <span style="color: #43783f;">ps</span> +<span style="color: #374478;">CONTAINER</span> <span style="color: #707e4f;">ID</span> <span style="color: #707e4f;">IMAGE</span> <span style="color: #78683f;">COMMAND</span> <span style="color: #78683f;">...</span> <span style="color: #3f7178;">PORTS</span> <span style="color: #707e4f;">NAMES</span> +<span style="color: #374478;">9bff07d8ee18</span> <span style="color: #78683f;">nginx:1.7</span> <span style="color: #008000;">"nginx -g 'daemon of ... 443/tcp, 104.210.33.180:80->80/tcp node1/loving_torvalds</span> +<span style="color: #008000;">457ed59c9bb3 nginx:1.7 "</span><span style="color: #783778;">nginx</span> <span style="color: #78683f;">-g</span> <span style="color: #008000;">'daemon of ... 443/tcp, 45.55.160.223:80->80/tcp node2/drunk_swartz</span> +<span style="color: #008000;">6013be18cdbc nginx:1.7 "nginx -g '</span><span style="color: #3f7178;">daemon</span> <span style="color: #5e7837;">of</span> <span style="color: #78683f;">...</span> <span style="color: #784437;">443/tcp,</span> <span style="color: #4f7e67;">192.168.99.103:80-</span>><span style="color: #5e7837;">80/tcp</span> <span style="color: #707e4f;">manager/condescending_galileo</span> +</pre> +</div> + +<p> +On voit qu'on a démarré nginx sur les 3 nœuds. Swarm a quelques stratégies pour démarrer un conteneur sur un nœud ou l'autre : +</p> + +<ul class="org-ul"> +<li><code>spread</code> va éparpiller les conteneurs pour que chaque nœud en ait le moins possible (répartis). +</li> +<li><code>binpack</code> va faire l'inverse (tout sur le même nœud jusqu'à ce que ses ressources soient épuisés). +</li> +<li><code>random</code> qui fait <i>au pif</i>. +</li> +</ul> + +<p> +Il est également possible de mettre des contraintes lors du lancement d'un conteneur, en utilisant le flag <code>-e</code> de <code>docker run</code> (<code>-e</code> = variables d'environnement). +</p> + + +<div class="org-src-container"> + +<pre class="src src-sh"><span style="color: #8D8D84;"># </span><span style="color: #8D8D84; font-style: italic;">Démarrer postgres sur un host qui a le label storage=ssd</span> +$ <span style="color: #783778;">docker</span> <span style="color: #513f78;">run</span> <span style="color: #43783f;">-d</span> <span style="color: #707e4f;">-e</span> <span style="color: #4f5c7e;">constraint:</span><span style="color: #4f5c7e;">storage</span>==<span style="color: #5e7837;">ssd</span> <span style="color: #4f5c7e;">--name</span> <span style="color: #4f7e67;">postgres</span> <span style="color: #4f7e67;">postgres</span> +<span style="color: #8D8D84;"># </span><span style="color: #8D8D84; font-style: italic;">Démarrer redis à coté du conteneur dont le nom est postgres</span> +$ <span style="color: #783778;">docker</span> <span style="color: #513f78;">run</span> <span style="color: #43783f;">-d</span> <span style="color: #707e4f;">-e</span> <span style="color: #4f5c7e;">affinity:</span><span style="color: #4f5c7e;">container</span>==<span style="color: #4f7e67;">postgres</span> <span style="color: #4f5c7e;">--name</span> <span style="color: #7e544f;">redis</span> <span style="color: #7e544f;">redis</span> +<span style="color: #8D8D84;"># </span><span style="color: #8D8D84; font-style: italic;">Démarer backend où tu veux, mais comme les links sont des contraintes</span> +<span style="color: #8D8D84;"># </span><span style="color: #8D8D84; font-style: italic;">implicites il démarrera sur le même host que postgres ET redis</span> +<span style="color: #8D8D84;"># </span><span style="color: #8D8D84; font-style: italic;">(ou ne démarrera pas ces derniers ne sont pas au même endroit)</span> +$ <span style="color: #783778;">docker</span> <span style="color: #513f78;">run</span> <span style="color: #43783f;">-d</span> <span style="color: #374478;">--link</span> <span style="color: #3f7178;">redis:redis</span> <span style="color: #374478;">--link</span> <span style="color: #784437;">postgres:db</span> <span style="color: #4f5c7e;">--name</span> <span style="color: #78683f;">backend</span> <span style="color: #78683f;">backend</span> +</pre> +</div> + +<p> +On pourrait faire un article dédié à Swarm (ce qui sera probablement le cas dans un avenir assez proche) donc je vous laisse le découvrir via <a href="https://github.com/docker/swarm/">github</a>. +</p> +</div> +</div> +</div> + +<div id="outline-container-sec-11" class="outline-2"> +<h2 id="sec-11">Levées de fonds et acquisitons</h2> +<div class="outline-text-2" id="text-11"> +<p> +Docker est sur toutes les lèvres en ce moment. Il est donc normal que cela attire également des capitaux. Le 14 avril dernier, Docker annonçait une nouvelle levée de fonds de <b>95 millions</b> de dollars. Après celle de <i>40 millions</i> en Septembre 2014, on peut se dire que Docker Inc. a de beaux jours à venir. +</p> + +<p> +Docker Inc. « mange » aussi quelques startups, puisque après Orchard, qui éditait fig (devenu docker-compose), ils ont fait l'acquisition de <a href="http://socketplane.io/">Socketplane</a> et <a href="https://kitematic.com/">Kitematic</a>. Kitematic est un outil <i>desktop</i> qui permet de facilement utiliser Docker sous Mac OS X, une belle application, un peu « clickodrome » <code>;-P</code>. <a href="http://socketplane.io/">Socketplane</a> est une solution réseau qui connectait Open vSwitch avec Docker — nulle doute que la récente libnetwork vient de là. +</p> +</div> +</div> + +<div id="outline-container-sec-12" class="outline-2"> +<h2 id="sec-12">Évènements</h2> +<div class="outline-text-2" id="text-12"> +<p> +Nous allons finir avec une liste non-exhaustive et un peu orientée des évènements <i>marquants</i> qui se sont passés ces derniers mois : +</p> + +<ul class="org-ul"> +<li>Le <a href="https://blog.docker.com/2014/11/docker-tour-de-france/">Docker Tour de France</a>, avec notament un <a href="http://www.meetup.com/Docker-Paris/events/218767688/">hackathon</a> organisé à l'<a href="http://www.epitech.eu/paris/ecole-informatique-paris.aspx">Epitech</a>, où notre <a href="https://twitter.com/mariolet">Mario Loriedo</a> national a bootstrapé son projet Sublime-docker avec <a href="https://github.com/mjbright">Mike Bright</a> et à du coup gagné sa place à la DockerCon de 2015. +</li> +<li>Les <a href="http://blog.zenika.com/index.php?post/2015/02/19/NightClazz-Docker-Avance">Nightclazz</a> <a href="http://zenika.github.io/NC-Docker-Decouverte/">découverte</a> et <a href="http://zenika.github.io/NC-Docker-Avance/#/">avancée</a> hébergé par Zenika, présenté par Mario Loriedo et moi-même ;-). +</li> +<li>La DockerCon Europe. +</li> +<li>Le <a href="http://docker.party/">Docker Birthday</a>, gigantesque <i>Open-source-athon</i> tout autour du monde — une véritable réussite, tant au niveau de l'organisation (des évènements, la préparation en amont des <i>issues</i>, etc.) que de ce qu'il en est <a href="https://blog.docker.com/2015/05/dockers-2nd-birthday-by-the-numbers/">ressorti</a>. +</li> +</ul> +</div> +</div> + + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2015-07-31-config-managment-intro/" title="Gestion de configuration : introduction">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2015-05-09-migration-to-hugo/" title="Migration vers hugo">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2015-07-31-config-managment-intro/index.html b/www/vincent.demeester.fr/legacy/posts/2015-07-31-config-managment-intro/index.html @@ -0,0 +1,387 @@ +<!DOCTYPE html> + +<html lang="fr"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2015-07-31-config-managment-intro/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang="fr"/> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">Gestion de configuration : introduction</h1><a href='https://vincent.demeester.fr/posts/2015-07-31-config-managment-intro/'></a> + <address class="signature"> + <span class="date">Fri, 31 July, 2015</span> + <span class="words">(1300 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#configurations">configurations</a></li> + + + + + + <li class="tag tag-git"><a href="/tags/#git">git<span>3</span></a></li> + + + <li class="tag tag-myrepos"><a href="/tags/#myrepos">myrepos<span>1</span></a></li> + + + <li class="tag tag-mr"><a href="/tags/#mr">mr<span>1</span></a></li> + + + <li class="tag tag-vcsh"><a href="/tags/#vcsh">vcsh<span>1</span></a></li> + + + <li class="tag tag-linux"><a href="/tags/#linux">linux<span>4</span></a></li> + + + <li class="tag tag-posix"><a href="/tags/#posix">posix<span>1</span></a></li> + + + <li class="tag tag-shell"><a href="/tags/#shell">shell<span>2</span></a></li> + + + <li class="tag tag-sharing"><a href="/tags/#sharing">sharing<span>1</span></a></li> + + <br/> + + </ul> + </header> + + + + <p> +Cela doit faire au moins 2 ans que je souhaite partager la façon dont +je gère mes configurations (en anglais <i>dotfiles</i>). Comme j'ai +longtemps repoussé l'échéance, probablement de peur d'avoir un roman à +écrire, je vais en faire une série de petits billets de blog dont +celui-ci est l'introduction. Nous y aborderons donc mon besoin, et mes +choix. +</p> + +<div id="outline-container-sec-1" class="outline-2"> +<h2 id="sec-1">Besoin(s)</h2> +<div class="outline-text-2" id="text-1"> +<p> +En bon <b>geek</b> que je suis, je suis fan des <i>dotfiles</i>. Les <i>dotfiles</i> +— fichiers de configurations — sont de petits fichiers, habituellement +dans notre dossier personnel (votre <i>$HOME</i>), qui nous permettent de +paramétrer et personnaliser nos outils de tous les jours. C'est +principalement vrai pour des outils en ligne de commande — et ça tombe +bien, j'adore — mais pas uniquement limité à ces derniers. +</p> + +<p> +Je vais faire un très petit aparté sur le pourquoi de cette +personnalisation : +</p> + +<ul class="org-ul"> +<li>C'est <b>fun</b> à faire et c'est relativement important de mon point de vue. +</li> +<li>C'est <b>éducatif</b> ou formateur ; on lit les documentations de nos +outils, leurs fonctionnalités un peu cachées. On va souvent découvrir +un peu la philosophie dans laquelle l'outil a été créé. C'est en +mettant les <i>mains dans le cambouis</i> et en <i>foutant un gros bordel</i> +que j'ai le plus appris (ça va du <i>langage</i> shell et d'autres +langages de scripts, de POSIX, au noyau linux ou encore au LISP avec +GNU/Emacs). +</li> +<li>Cela fait <b>gagner du temps</b> et de manière non négligeable. Je suis +né <span class="underline">courageux mais terriblement fainéant</span> (et oui c'est possible +<code>:-P</code>), j'aime pas trop me répéter quand ça devient un peu compliqué +/ chiant (e.g. <code>docker run monimage args</code> ça va, <code>docker run</code> avec +<code>-v /tmp:/tmp -v /var/run/docker.socket:/var/run/docker.socket […]</code> +et <code>run monimage arg1 arg2 arg3 […]</code> moins déjà). +</li> +</ul> + +<p> +Mais revenons à nos moutons et faisons une petite liste de mes besoins, un +peu en vrac : +</p> + +<ul class="org-ul"> +<li>J'ai plusieurs ordinateurs (laptop/desktop/serveurs) et je souhaite +avoir mes configurations <b>synchronisées</b> entre ceux-ci — et ce de +manière simple, c'est à dire <i>une commande à exécuter</i>. +</li> +<li>C'est lié au point précédent mais, je ne <i>peux pas vivre</i> sans +outil de gestion de version, comme <b>git</b>. Il me faut donc un outil +ou ensemble d'outil qui sache utiliser des outils de gestion de +version <i>du marché</i>. + + +<div class="figure"> +<p><img src="./img/git-all-the-thing.jpg" alt="git-all-the-thing.jpg" /> +</p> +</div> +</li> + +<li>En fonction de mes ordinateurs, mes besoins de configuration +changent. Il me faut donc un outil <b>flexible</b> qui me permette de dire +par exemple : sur ce PC j'ai un serveur <code>Xorg</code> donc j'ai besoin de +mes configuration xorg, de celle de mon <i>window manager</i>, etc. — et +inversement sur ce serveur j'ai besoin de python et haskell mais pas +de xorg.. +</li> +<li>Je ne souhaites pas avoir à faire des liens symboliques, ou de +scripts d'installation. Je trouves que ça rends les choses plus +compliquées. Du coup il faut que je puisse avoir <b>plusieurs <i>dépôts +de configuration</i></b> (repository) qui pointent au même endroit, sans +que ce soit le bordel. +</li> +<li>Le <b>partage</b> est important pour moi. Il en découle deux choses : +<ol class="org-ol"> +<li>Il faut que je puisse documenter un peu mon repository, avec un +bon vieux <code>README</code> ; sans que chaque <code>README</code> se marche dessus. +</li> +<li>Il y a quelques <b>informations</b> qui sont <b>personnelles</b>, comme par +exemple les clés ssh. Il me faut donc être capable d'avoir des +<i>dépôts publiques</i> et des <i>dépôts privés</i>. C'est grandement facilité +par l'aspect <i>flexibilité</i> <code>:-)</code>. +</li> +</ol> +</li> +<li>Un bonus que je souhaite, est de pouvoir disposer de <b>hooks</b>, un peu +à la manière de git (voir <a href="https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks">ici</a>). L'idée est de pouvoir <b>générer</b> un +fichier de configuration à partir d'un ensemble de fichiers qui +viendraient de différents dépôts. Le meilleur exemple que je peux +donner c'est <code>~/.ssh/config</code> dans lequel je vais y mettre des bouts +publiques que je souhaites partager (comme le <code>Host *</code> avec des +trucs cool comme <code>ControlPersist</code>, on en parlera plus tard) et des +bouts privés (mes hosts privés, avec mes configurations de +<i>rebonds</i>, etc..). +</li> +</ul> + +<p> +Cette liste a mis un certain temps à se former dans ma tête, mais une +fois qu'elle était formée, j'ai pu assez facilement faire des choix. +</p> +</div> +</div> + +<div id="outline-container-sec-2" class="outline-2"> +<h2 id="sec-2">Choix</h2> +<div class="outline-text-2" id="text-2"> +<p> +Deux outils et un peu d'organisation permettent de répondre à mes +besoins. Les deux outils sont <code>vcsh</code> et <code>myrepos</code> (anciennement appelé +<code>mr</code>), fait par respectivement Richard Hartmann et Joey Hess (tout +deux assez impliqué dans la communauté Debian). +</p> +</div> + +<div id="outline-container-sec-2-1" class="outline-3"> +<h3 id="sec-2-1">vcsh</h3> +<div class="outline-text-3" id="text-2-1"> +<p> +En un mot, <a href="https://github.com/RichiH/vcsh">vcsh</a> permet de maintenir plusieurs <i>repository</i> <a href="http://git-scm.com/">git</a> dans +un seul dossier. Par défaut tous les <i>repository</i> git maintenus par +<code>vcsh</code> pointent vers le dossier <code>$HOME</code>, mais il est possible +d'utiliser un autre dossier. +</p> + +<p> +L'idée est de pouvoir disposer de plusieurs repository par <i>famille +d'application</i>, par exemple <code>vim</code>, <code>ssh</code>, <code>emacs</code>, <code>zsh</code>, etc. Cela +permet ainsi d'avoir différents ensemble de configurations sur +différentes machines et pour différents utilisateurs. Cela apporte +une très grande flexibilité et facilite le partage de configuration +(au sein d'une entreprise ou d'un projet par exemple) tout en +laissant la place à la définition de configuration(s) +personnalisé(s). +</p> + +<p> +En bonus, <code>vcsh</code> supporte un système de hook, permettant d'exécuter +des commandes à différents moments du <i>workflow</i> — c'est la seule +partie qui manquant à <code>vcsh</code> de mon point de vue alors j'y ai +apporté ma petit pierre. +</p> + +<p> +<code>vcsh</code> est la clé de voûte de ma gestion de configuration. Sans le +travail formidable de <a href="http://richardhartmann.de/">Richard Hartmann</a>, je ne sais pas comment je +ferais.. +</p> +</div> +</div> + +<div id="outline-container-sec-2-2" class="outline-3"> +<h3 id="sec-2-2">myrepos</h3> +<div class="outline-text-3" id="text-2-2"> +<p> +En un mot, <a href="https://myrepos.branchable.com/">myrepos</a> est un outil permettant de <i>gérer</i> plusieurs +repository (git, subversion, mercurial, …) avec une seule +commande : <code>mr</code>. C'est simple et efficace : +</p> + +<ul class="org-ul"> +<li><code>mr u</code> (ou <code>mr update</code>) pour récupérer les dernières modifications (<code>git pull</code>, +<code>svn up</code>, …). +</li> +<li><code>mr -d $HOME/.config u</code> pour récupérer les dernières modifications +des repository qui sont dans <code>$HOME/.config</code>. +</li> +<li><code>mr -j 6 u</code> pour paralléliser la récupération (ici 6 jobs en parallèle). +</li> +<li><code>mr p</code> pour pousser des modifications (<code>git push</code>, …). +</li> +<li><code>mr run</code> pour lancer un commande (j'utilise ça tous les jours). +</li> +</ul> + +<p> +Il est également possible de personnaliser les commandes à lancer lors +d'un <i>update</i> ou autre (toutes les commandes), et même en définir des +nouvelles. Cela se présente comme suit : +</p> + + +<div class="org-src-container"> + +<pre class="src src-conf">[<span class="org-type">foo</span>] +<span class="org-variable-name">checkout</span> = git@github.com:joeyh/foo.git +<span class="org-variable-name">update</span> = git pull --rebase + +[<span class="org-type">bar</span>] +<span class="org-comment-delimiter"># </span><span class="org-comment">This repository has an upstream, which I've forked;</span> +<span class="org-comment-delimiter"># </span><span class="org-comment">set up a remote on checkout.</span> +<span class="org-variable-name">checkout</span> = + git clone git@github.com:joeyh/bar.git + cd bar + git remote add upstream git@github.com:barbar/bar.git +<span class="org-comment-delimiter"># </span><span class="org-comment">make `mr zap` integrate from upstream</span> +<span class="org-variable-name">zap</span> = + git pull upstream + git merge upstream/master + git push origin master + +[<span class="org-type">mystuff</span>] +<span class="org-variable-name">checkout</span> = git@github.com:joeyh/foo.git +<span class="org-comment-delimiter"># </span><span class="org-comment">Skip if the current user is not joey</span> +<span class="org-variable-name">skip</span> = test `whoami` != joey + +[<span class="org-type">DEFAULT</span>] +<span class="org-comment-delimiter"># </span><span class="org-comment">Teach mr how to `mr gc` in git repos.</span> +<span class="org-variable-name">git_gc</span> = git gc <span class="org-string">"$@"</span> +</pre> +</div> + +<p> +Une autre fonctionnalité qui m'est totalement indispensable est ce +qu'on appel les <code>fixup(s)</code>. Il est en effet possible d'exécuter une ou +plusieurs commandes (shell) après un <code>update</code> (ou via la commande +<code>fixup</code>). C'est grâce à ce système la que je génère mes fichiers de +configuration en provenance de plusieurs repository (comme +<code>$HOME/.ssh/config</code> ou encore <code>$HOME/.gitconfig</code>). +</p> + +<p> +<code>myrepos</code> est l'exemple de l'outil simple et efficace qui fait une +chose et le fait très bien, et dont je n'arrive pas à me passer +<code>:-D</code>. Je l'utilise également dans plein d'autres cas, comme par +exemple pour mettre à jour mes <i>forks</i> de projets open-source. +</p> +</div> +</div> +</div> + +<div id="outline-container-sec-3" class="outline-2"> +<h2 id="sec-3">Conclusion</h2> +<div class="outline-text-2" id="text-3"> +<p> +Et voilà, c'est tout pour cette introduction <code>;-)</code>. La prochaine +partie se penchera sur la <b>structure</b> que j'utilise ainsi que le +repository principal qui est <a href="https://github.com/vdemeester/vcsh-home"><b>vcsh-home</b></a>. Dans les parties suivantes +on parlera des autres repository et donc des configurations +spécifiques pour les différents outils (comme <a href="https://github.com/vdemeester/sh-config">sh-config</a>, +<a href="https://github.com/vdemeester/emacs-config">emacs-config</a> ou encore <a href="https://github.com/vdemeester/go-config">go-config</a>). On parlera aussi probablement de +<code>git-annex</code> dans le futur. +</p> +</div> +</div> + + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2016-09-18-firefox-places-and-bookmarks/" title="Firefox hidden feature — places in bookmarks">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2015-06-01-docker-1.6-ecosystem/" title="Docker 1.6 et son écosystème">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2016-09-18-firefox-places-and-bookmarks/index.html b/www/vincent.demeester.fr/legacy/posts/2016-09-18-firefox-places-and-bookmarks/index.html @@ -0,0 +1,332 @@ +<!DOCTYPE html> + +<html lang="fr"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2016-09-18-firefox-places-and-bookmarks/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang="fr"/> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">Firefox hidden feature — places in bookmarks</h1><a href='https://vincent.demeester.fr/posts/2016-09-18-firefox-places-and-bookmarks/'></a> + <address class="signature"> + <span class="date">Mon, 19 September, 2016</span> + <span class="words">(1100 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#configurations">configurations</a></li> + + + + + + <li class="tag tag-firefox"><a href="/tags/#firefox">firefox<span>1</span></a></li> + + + <li class="tag tag-place"><a href="/tags/#place">place<span>1</span></a></li> + + + <li class="tag tag-bookmark"><a href="/tags/#bookmark">bookmark<span>1</span></a></li> + + + <li class="tag tag-hidden"><a href="/tags/#hidden">hidden<span>1</span></a></li> + + + <li class="tag tag-tips"><a href="/tags/#tips">tips<span>2</span></a></li> + + <br/> + + </ul> + </header> + + + + <p> +Je réalise que j'utilise Firefox depuis bien plus de 10 ans maintenant +(on doit être à minimum 15 ans en fait…). Et je me souviens d'un temps +où je souhaitais rendre mes <i>bookmarks</i> (marque-pages 👼) +dynamique. Après tout ils existent de base dans Firefox (e.g. <code>Most +visited</code>), il doit donc être possible d'en faire soit même. +</p> + +<p> +Il fut un temps où c'était facile à mettre en place — ouvrir la +gestion des marques pages, chercher quelque chose et <i>cliquer</i> sur le +bouton <code>save</code> pour sauvegarder cette recherche en tant de bookmark +dynamique. +</p> + +<p> +<span class="underline">Note</span>: comme j'avais grave lutté à l'époque, et que j'ai encore lutté +cette fois ci, cet article va me servir de pense-bête pour le futur. +</p> + +<div id="outline-container-sec-1" class="outline-2"> +<h2 id="sec-1">Places</h2> +<div class="outline-text-2" id="text-1"> +<p> +C'est le petit nom que possède cette fonctionnalité dans le cœur de +Firefox. C'est un nom relativement commun, du coup ça n'aide pas trop +la recherche d'information… Le meilleur endroit pour se documenter +reste alors le <a href="https://developer.mozilla.org">Mozilla Developer Network</a>… +</p> + +<p> +Et ce qu'on peut lire sur cette <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Tech/Places">page</a> c'est : +</p> + +<blockquote> +<p> +Places is the bookmarks and history management system introduced in +Firefox 3. It offers increased flexibility and complex querying to +make handling the places the user goes easier and more +convenient. It also includes new features including favicon storage +and the ability to annotate pages with arbitrary information. +</p> +</blockquote> + +<p> +C'est donc un mécanisme interne qui permet d'annoter des pages +(bookmarks, history, …) et surtout un <i>requêtage</i> plus complexe et +flexible que le système précédent. On peut noter également que c'est +arrivé en version 3, et qu'à la date du jour, la version courante de +Firefox est la 48. En espérant donc que ce soit toujours le système en +place (<i>spoiler alert: ça a l'air d'être le cas</i>). +</p> + + +<div class="figure"> +<p><img src="/images/2016/firefox-place-awesomebar-example.png" alt="firefox-place-awesomebar-example.png" /> +</p> +<p><span class="figure-number">Figure 1:</span> An example of places usage in the awesome bar</p> +</div> + +<p> +Les petits <i>encarts</i> en noir ci-dessus sont un exemple de ces +<i>places</i>. Ici il s'agit des <b>tags</b> qui ont étés apposer sur les +bookmarks ; on voit d'ailleurs que ce sont des bookmarks puisqu'ils +ont une petite étoile à côté du joli petit <code>favicon</code>. Firefox a pris +les termes que j'ai écrit dans la barre d'url et à faire une recherche +dans les <i>places</i> suivant différents critères (l'url, les tags pour +les bookmarks, …). +</p> +</div> +</div> + +<div id="outline-container-sec-2" class="outline-2"> +<h2 id="sec-2">Places dans les bookmarks</h2> +<div class="outline-text-2" id="text-2"> +<p> +L'idée est d'utiliser cette fonctionnalité pour faire des bookmarks +dynamique. Pour poser un peu de contexte, j'utilise surtout la +bookmark toolbar pour accéder à des pages dont j'ai besoin (au hasard, +le webmail du boulot, une page précise d'issues sur github, des pages +de guide/référence/manuel sur des outils que j'utilise…) et également +en mode <i>read that later</i>. +</p> + + +<div class="figure"> +<p><img src="/images/2016/firefox-place-bookmark-toolbar.png" alt="firefox-place-bookmark-toolbar.png" /> +</p> +<p><span class="figure-number">Figure 2:</span> Ma <i>bookmark</i> toolbar</p> +</div> + +<p> +Par exemple, dans mon dossier <code>inbox</code>, il y a plusieurs sous-dossiers +(en fait des dossiers dynamique, mais j'y reviendrais plus tard), +genre <code>docker</code>, <code>nix</code>, <code>golang</code>, <code>java</code>, etc. Tout ce qui <i>traîne</i> +dans <code>inbox</code> est à lire ou à trier (<code>inbox</code> se réfère un peu à ce que +l'on peut voir avec GTD). Chaque sous dossier était une sorte de +<i>filtre</i> par projet, langage ou /whatever/… +</p> + +<p> +Nous allons donc ruser un peu et utiliser les <i>places</i> dans nos +bookmarks pour en faire des dossiers dynamique, à l'instar de <code>Most +Visited</code>. Pour se faire, il faut déjà trouver un peu de documentation +sur l'utilisation de ces fameuses <i>places</i> au sein de bookmarks — et +ça pèche un peu… Toujours sur le <a href="https://developer.mozilla.org">Mozilla Developer Network</a>, une page +<i>un peu caché</i> nous révèle quelques informations : <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Tech/Places/Places_query_URIs">Places query URIs</a>. +</p> + +<blockquote> +<p> +You can use a <code>place</code> URI as a bookmark. For example, if you right-click +on the toolbar and choose "New Bookmark," you can enter a place URI +there to create a new query on your toolbar that, when clicked, will +reveal a popup containing the results of the query. +</p> +</blockquote> + +<p> +Victoire <code>\o/</code>, il <i>suffit</i> de créer un bookmark avec comme <code>URI</code> +une <code>place:…</code> URI et c'est gagné. Ainsi, un bookmark avec comme <code>URI</code> +<code>place:terms=nixos</code> me retournera un dossier dynamique qui contiens +tous les liens (<i>history</i>, <i>bookmarks</i>, …) qui contiennent le mot +<code>nixos</code> — en gros l'équivalent de ce que nous aurions si nous tapions +<code>nixos</code> dans la <i>awesomebar</i>. +</p> + +<p> +Si l'on regarde d'un peu plus près <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Tech/Places/Places_query_URIs">Places query URIs</a>, il y a une +longue liste de paramètres possible. Il y en a pour presque tous les +goûts : le temps (utile pour afficher l'historique, mais pas trop), +les visites, le type de <i>place</i> (<i>bookmark</i>, <i>history</i>, …), le +domaine de l'élément, le dossier dans lequel il se trouve, l'<code>URI</code>, +les tags… j'en passe et des meilleurs. +</p> + +<p> +Si par exemple, je souhaite n'afficher que les pages visités +aujourd'hui, triés par nombre de visites et limités à 10 résultats, je +peux mettre comme <code>URI</code> : +</p> + +<div class="org-src-container"> + +<pre class="src src-http">place:queryType=0&sort=8&maxResults=10&beginTimeRef=1&beginTime=0 +</pre> +</div> + +<p> +Et on obtiens ça : +</p> + + +<div class="figure"> +<p><img src="/images/2016/firefox-place-most-visited-today.png" alt="firefox-place-most-visited-today.png" /> +</p> +</div> + +<p> +Mais revenons à nos moutons, moi ce que je souhaite, c'est faire des +dossiers dynamique par <code>tag</code>, i.e. les tags que l'on peut setter +lorsqu'on ajoute un bookmark. Et ça tombe bien, un des paramètres +s'appelle <code>tag</code> et fait exactement ça. Je souhaite également pouvoir +faire des <code>ET</code> sur mes tags — par exemple, dans mon dossier +<code>inbox/nixos</code> je veux tous les bookmarks qui sont taggués <code>inbox</code> <b>et</b> +<code>nixos</code>. Et c'est simple, il suffit de lui donner les deux. +</p> + +<div class="org-src-container"> + +<pre class="src src-http">place:tag=inbox&tag=nixos +</pre> +</div> +</div> +</div> + +<div id="outline-container-sec-3" class="outline-2"> +<h2 id="sec-3">Rétrospective</h2> +<div class="outline-text-2" id="text-3"> +<p> +Voilà, vous avez maintenant tout ce qu'il faut pour faire des dossiers +dynamiques dans vos bookmarks Firefox et vous organiser au mieux ( +<i>ou pas</i>). Quelques points à noter : +</p> + +<ul class="org-ul"> +<li>L'icône du dossier dynamique — ou même la fonctionnalité à +proprement parler= n'apparaît pas de suite. Il faut parfois +redémarrez Firefox pour profiter du nouveau dossier dynamique. +</li> +<li>Une fois que le bookmark est vu comme un dossier dynamique, il n'est +plus possible d'éditer l'<code>URI</code>. Si vous voulez en changer un, il +faut tout bonnement le recréer. +</li> +<li>Bien que ce soit une fonctionnalité que je trouve géniale, il y a +peu de <i>presse</i> sur le sujet et à mon grand étonnement, aucun plugin +pour gérer ça plus proprement. Est-ce le signe qu'un jour ce ne sera +plus supporter, je n'en ai aucune idée. +</li> +<li>Un point très positif si vous utilisez Firefox Sync : ça marche bien +(pas vraiment étonnant en fait), ces dossiers se synchronise comme +le reste et seront donc à disponible quelque soit l'appareil sur lequel +vous utilisez firefox <code>\o/</code>. +</li> +</ul> +</div> +</div> + + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2017-01-01-go-testing-functionnal-builders/" title="Golang testing — functional arguments for wonderful builders">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2015-07-31-config-managment-intro/" title="Gestion de configuration : introduction">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2017-01-01-go-testing-functionnal-builders/index.html b/www/vincent.demeester.fr/legacy/posts/2017-01-01-go-testing-functionnal-builders/index.html @@ -0,0 +1,585 @@ +<!DOCTYPE html> + +<html lang="en"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2017-01-01-go-testing-functionnal-builders/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang="en"/> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">Golang testing — functional arguments for wonderful builders</h1><a href='https://vincent.demeester.fr/posts/2017-01-01-go-testing-functionnal-builders/'></a> + <address class="signature"> + <span class="date">Sun, 1 January, 2017</span> + <span class="words">(1600 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#developement">developement</a></li> + + + + + + <li class="tag tag-golang"><a href="/tags/#golang">golang<span>12</span></a></li> + + + <li class="tag tag-testing"><a href="/tags/#testing">testing<span>11</span></a></li> + + + <li class="tag tag-builder"><a href="/tags/#builder">builder<span>1</span></a></li> + + + <li class="tag tag-functionnal"><a href="/tags/#functionnal">functionnal<span>2</span></a></li> + + + <li class="tag tag-java"><a href="/tags/#java">java<span>4</span></a></li> + + + <li class="tag tag-featured"><a href="/tags/#featured">featured<span>2</span></a></li> + + <br/> + + </ul> + </header> + + + + <blockquote> +<p> +Programming is not easy; even the best programmers are incapable of +writing programs that work exactly as intended every time. Therefore +an important part of the software development process is +testing. Writing tests for our code is a good way to ensure quality +and improve reliability. +</p> +</blockquote> + +<p> +Go programs, when properly implemented, are fairly simple to test +programatically. The <code>testing</code> built-in library and the features of +the language itself offer plenty of ways to write good tests. As this +is a subject I particularly like, I'm gonna write a bunch of articles +about it, that, <i>hopefully</i> do not get old or boring. +</p> + +<p> +I'm not going to start by introducing how <code>testing</code> works, it's +already widely described in <a href="https://golang.org/pkg/testing/">the <code>testing</code> godoc</a>, <a href="https://blog.golang.org/examples">some</a> <a href="https://www.golang-book.com/books/intro/12">articles</a> and +<a href="https://jonathanmh.com/golang-unit-testing-for-absolute-beginners/">blogs</a>. I'm going to jump ahead on a more advanced techinque to write +tests, the <code>builders</code> for tests. +</p> + +<p> +One of the most important characteristic of a <b>unit test</b> (and any +type of test really) is <b>readability</b>. This means it should be <i>easy +to read</i> but most importantly it should <b>clearly show the intent of +the test</b>. The setup (and cleanup) of the tests should be as small as +possible to avoid the noise. And as we are going to see below, <code>go</code> +makes it pretty easy to do so. +</p> + +<div id="outline-container-org1afcfe7" class="outline-2"> +<h2 id="org1afcfe7">Builders in tests</h2> +<div class="outline-text-2" id="text-org1afcfe7"> +<p> +Sometimes, your need to create data structure for your test that +might take a lot of line and introduce noise. In <code>golang</code> we don't +have method overload or even <i>constructors</i> as some other language +have. This means most of the time, we end up building our data using +directly the struct expression, as below. +</p> + +<div class="org-src-container"> +<pre class="src src-go"><span class="org-rainbow-identifiers-identifier-5">node</span> := &<span class="org-type">Node</span>{ + <span class="org-constant">Name</span>: <span class="org-string">"carthage"</span>, + <span class="org-constant">Hostname</span>: <span class="org-string">"carthage.sbr.pm"</span>, + <span class="org-constant">Platform</span>: <span class="org-type">Platform</span>{ + <span class="org-constant">Architecture</span>: <span class="org-string">"x86_64"</span>, + <span class="org-constant">OS</span>: <span class="org-string">"linux"</span>, + }, +} +</pre> +</div> + +<p> +Let's imagine we have a <code>Validate</code> function that make sure the +specified <code>Node</code> is supported on our structure. We would write some +tests that ensure that. +</p> + +<div class="org-src-container"> +<pre class="src src-go"><span class="org-keyword">func</span> <span class="org-function-name">TestValidateLinuxIsSupported</span>(<span class="org-rainbow-identifiers-identifier-8">t</span> *<span class="org-type">testing.T</span>) { + <span class="org-rainbow-identifiers-identifier-6">valid</span> := <span class="org-function-name">Validate</span>(&<span class="org-type">Node</span>{ + <span class="org-constant">Name</span>: <span class="org-string">"carthage"</span>, + <span class="org-constant">Hostname</span>: <span class="org-string">"carthage.sbr.pm"</span>, + <span class="org-constant">Platform</span>: &<span class="org-type">Platform</span>{ + <span class="org-constant">Architecture</span>: <span class="org-string">"x86_64"</span>, + <span class="org-constant">OS</span>: <span class="org-string">"linux"</span>, + }, + }) + <span class="org-keyword">if</span> !<span class="org-rainbow-identifiers-identifier-6">valid</span> { + <span class="org-rainbow-identifiers-identifier-8">t</span>.<span class="org-function-name">Fatal</span>(<span class="org-string">"linux should be supported, it was not"</span>) + } +} + +<span class="org-keyword">func</span> <span class="org-function-name">TestValidateDarwinIsNotSupported</span>(<span class="org-rainbow-identifiers-identifier-8">t</span> *<span class="org-type">testing.T</span>) { + <span class="org-rainbow-identifiers-identifier-6">valid</span> := <span class="org-function-name">Validate</span>(&<span class="org-type">Node</span>{ + <span class="org-constant">Name</span>: <span class="org-string">"babylon"</span>, + <span class="org-constant">Hostname</span>: <span class="org-string">"babylon.sbr.pm"</span>, + <span class="org-constant">Platform</span>: &<span class="org-type">Platform</span>{ + <span class="org-constant">Architecture</span>: <span class="org-string">"x86_64"</span>, + <span class="org-constant">OS</span>: <span class="org-string">"darwin"</span>, + }, + }) + <span class="org-keyword">if</span> <span class="org-rainbow-identifiers-identifier-6">valid</span> { + <span class="org-rainbow-identifiers-identifier-8">t</span>.<span class="org-function-name">Fatal</span>(<span class="org-string">"darwin should not be supported, it was"</span>) + } +} +</pre> +</div> + +<p> +This is quickly hard to read, there is too much noise on that +test. We setup a whole <code>Node</code> struct, but the only thing we really +intend to test is the <code>Platform.OS</code> part. The rest is just required +fields for the function to correctly compile and run. +</p> + +<p> +This is where test builders (and builders in general) comes into +play. In <a href="http://www.growing-object-oriented-software.com/">Growing Object-Oriented Software Guided by Tests</a>, the +Chapter 22 "Constructing Complex Test Data" is exactly about that +and guide us through the why and the how of these builders. The +examples in the book are in <code>java</code> and uses wisely the +object-oriented nature of the language. Here is an example from the +book. +</p> + +<div class="org-src-container"> +<pre class="src src-java"><span class="org-comment-delimiter">// </span><span class="org-comment">I just want an order from a customer that has no post code</span> +<span class="org-type">Order</span> <span class="org-variable-name">order</span> = <span class="org-rainbow-identifiers-identifier-12">anOrder</span>() + .<span class="org-rainbow-identifiers-identifier-7">from</span>(<span class="org-rainbow-identifiers-identifier-13">aCustomer</span>().<span class="org-rainbow-identifiers-identifier-11">with</span>(<span class="org-rainbow-identifiers-identifier-13">anAddress</span>().<span class="org-rainbow-identifiers-identifier-3">withNotPostCode</span>())) + .<span class="org-rainbow-identifiers-identifier-10">build</span>() +</pre> +</div> + +<p> +These builders helps <b>keep tests expressive</b>, as it's pretty obvious +when reading it, what we want to test. They remove the <b>visual +noise</b> you have when building an object (or a <code>struct{}</code> in Go) and +allows you to put sane default. They also make <b>tests resilient to +change</b>. If the structure changes, only the builder has to be +updated, not the tests depending on it. They also make default case +really simple to write, and special cases not much more complicated. +</p> +</div> +</div> + +<div id="outline-container-orgff90129" class="outline-2"> +<h2 id="orgff90129">Builder in Go</h2> +<div class="outline-text-2" id="text-orgff90129"> +<p> +The naive way to create builders in <code>go</code> could be to create a +<code>builder</code> struct that have methods to construct the final struct and +a <code>build</code> method. Let's see how it looks. +</p> + +<div class="org-src-container"> +<pre class="src src-go"><span class="org-keyword">func</span> <span class="org-function-name">ANode</span>() *<span class="org-type">NodeBuilder</span> { + <span class="org-keyword">return</span> &<span class="org-type">NodeBuilder</span>{ + <span class="org-constant">node</span>: &<span class="org-type">Node</span>{ + <span class="org-constant">Name</span>: <span class="org-string">"node"</span>, + <span class="org-comment-delimiter">// </span><span class="org-comment">Other defaults</span> + }, + } +} + +<span class="org-keyword">type</span> <span class="org-type">NodeBuilder</span> <span class="org-keyword">struct</span> { + <span class="org-rainbow-identifiers-identifier-5">node</span> *<span class="org-rainbow-identifiers-identifier-12">Node</span> +} + +<span class="org-keyword">func</span> (<span class="org-rainbow-identifiers-identifier-1">b</span> *<span class="org-type">NodeBuilder</span>) <span class="org-function-name">Build</span>() *<span class="org-type">Node</span> { + <span class="org-keyword">return</span> <span class="org-rainbow-identifiers-identifier-1">b</span>.<span class="org-rainbow-identifiers-identifier-5">node</span> +} + +<span class="org-keyword">func</span> (<span class="org-rainbow-identifiers-identifier-1">b</span> *<span class="org-type">NodeBuilder</span>) <span class="org-function-name">Hostname</span>(<span class="org-rainbow-identifiers-identifier-3">hostname</span> <span class="org-type">string</span>) *<span class="org-type">NodeBuilder</span> { + <span class="org-rainbow-identifiers-identifier-1">b</span>.<span class="org-rainbow-identifiers-identifier-5">node</span>.<span class="org-rainbow-identifiers-identifier-10">Hostname</span> = <span class="org-rainbow-identifiers-identifier-3">hostname</span> + <span class="org-keyword">return</span> <span class="org-rainbow-identifiers-identifier-1">b</span> +} + +<span class="org-keyword">func</span> (<span class="org-rainbow-identifiers-identifier-1">b</span> *<span class="org-type">NodeBuilder</span>) <span class="org-function-name">Name</span>(<span class="org-rainbow-identifiers-identifier-3">name</span> <span class="org-type">string</span>) *<span class="org-type">NodeBuilder</span> { + <span class="org-rainbow-identifiers-identifier-1">b</span>.<span class="org-rainbow-identifiers-identifier-5">node</span>.<span class="org-rainbow-identifiers-identifier-8">Name</span> = <span class="org-rainbow-identifiers-identifier-3">name</span> + <span class="org-keyword">return</span> <span class="org-rainbow-identifiers-identifier-1">b</span> +} + +<span class="org-keyword">func</span> (<span class="org-rainbow-identifiers-identifier-1">b</span> *<span class="org-type">NodeBuilder</span>) <span class="org-function-name">Platform</span>(<span class="org-rainbow-identifiers-identifier-14">platform</span> *<span class="org-type">Platform</span>) *<span class="org-type">NodeBuilder</span> { + <span class="org-rainbow-identifiers-identifier-1">b</span>.<span class="org-rainbow-identifiers-identifier-5">node</span>.<span class="org-rainbow-identifiers-identifier-4">Platform</span> = <span class="org-rainbow-identifiers-identifier-14">platform</span> + <span class="org-keyword">return</span> <span class="org-rainbow-identifiers-identifier-1">b</span> +} +</pre> +</div> + +<p> +This looks decent, and using it is pretty straightforward. At least +it make building the <code>struct</code> more expressive, less noisy and +resilient to change. We can update the previous test as follow. +</p> + +<div class="org-src-container"> +<pre class="src src-go"><span class="org-keyword">func</span> <span class="org-function-name">TestValidateLinuxIsSupported</span>(<span class="org-rainbow-identifiers-identifier-8">t</span> *<span class="org-type">testing.T</span>) { + <span class="org-rainbow-identifiers-identifier-6">valid</span> := <span class="org-function-name">Validate</span>(<span class="org-function-name">ANode</span>().<span class="org-function-name">Platform</span>(&<span class="org-type">Platform</span>{ + <span class="org-constant">Architecture</span>: <span class="org-string">"x86_64"</span>, + <span class="org-constant">OS</span>: <span class="org-string">"linux"</span>, + }).<span class="org-function-name">Build</span>()) + <span class="org-keyword">if</span> !<span class="org-rainbow-identifiers-identifier-6">valid</span> { + <span class="org-rainbow-identifiers-identifier-8">t</span>.<span class="org-function-name">Fatal</span>(<span class="org-string">"linux should be supported, it was not"</span>) + } +} + +<span class="org-keyword">func</span> <span class="org-function-name">TestValidateDarwinIsNotSupported</span>(<span class="org-rainbow-identifiers-identifier-8">t</span> *<span class="org-type">testing.T</span>) { + <span class="org-rainbow-identifiers-identifier-6">valid</span> := <span class="org-function-name">Validate</span>(<span class="org-function-name">ANode</span>().<span class="org-function-name">Platform</span>(&<span class="org-type">Platform</span>{ + <span class="org-constant">Architecture</span>: <span class="org-string">"x86_64"</span>, + <span class="org-constant">OS</span>: <span class="org-string">"darwin"</span>, + }).<span class="org-function-name">Build</span>()) + <span class="org-keyword">if</span> <span class="org-rainbow-identifiers-identifier-6">valid</span> { + <span class="org-rainbow-identifiers-identifier-8">t</span>.<span class="org-function-name">Fatal</span>(<span class="org-string">"darwin should not be supported, it was"</span>) + } +} +</pre> +</div> + +<p> +There is room for improvement : +</p> + +<ul class="org-ul"> +<li>There is still some noise, mainly <code>build()</code> and the platform +<code>struct</code>, as it still shows too much.</li> +<li>It's not that extensible yet. If you want to update the <code>Node</code> a +certain way that the builder is not written for, you have to +update the builder.</li> +<li>The <code>NodeBuilder</code> struct feels a little empty, it's just there to +hold on the <code>Node</code> being constructed until it is <code>build</code>.</li> +</ul> + +<p> +One improvement we could make is to have a <code>Platform</code> builder, even +if it's a small struct here. Let's do that in the same way we did +with <code>Node</code>. +</p> + +<div class="org-src-container"> +<pre class="src src-go"><span class="org-keyword">func</span> <span class="org-function-name">APlatform</span>() *<span class="org-type">PlatformBuilder</span> { + <span class="org-keyword">return</span> &<span class="org-type">PlatformBuilder</span>{ + <span class="org-constant">platform</span>: &<span class="org-type">Platform</span>{ + <span class="org-constant">Architecture</span>: <span class="org-string">"x64_86"</span>, + <span class="org-constant">OS</span>: <span class="org-string">"linux"</span>, + }, + } +} + +<span class="org-keyword">type</span> <span class="org-type">PlatformBuilder</span> <span class="org-keyword">struct</span>{ + <span class="org-rainbow-identifiers-identifier-14">platform</span> *<span class="org-rainbow-identifiers-identifier-4">Platform</span> +} + +<span class="org-keyword">func</span> (<span class="org-rainbow-identifiers-identifier-1">b</span> *<span class="org-type">PlatformBuilder</span>) <span class="org-function-name">Build</span>() *<span class="org-type">Platform</span> { + <span class="org-keyword">return</span> <span class="org-rainbow-identifiers-identifier-1">b</span>.<span class="org-rainbow-identifiers-identifier-14">platform</span> +} + +<span class="org-keyword">func</span> (<span class="org-rainbow-identifiers-identifier-1">b</span> *<span class="org-type">PlatformBuilder</span>) <span class="org-function-name">OS</span>(<span class="org-rainbow-identifiers-identifier-2">os</span> <span class="org-type">string</span>) *<span class="org-type">PlatformBuilder</span> { + <span class="org-rainbow-identifiers-identifier-1">b</span>.<span class="org-rainbow-identifiers-identifier-14">platform</span>.<span class="org-rainbow-identifiers-identifier-12">OS</span> = <span class="org-rainbow-identifiers-identifier-2">os</span> + <span class="org-keyword">return</span> <span class="org-rainbow-identifiers-identifier-1">b</span> +} +</pre> +</div> + +<p> +And our tests becomes 🐻. +</p> + +<div class="org-src-container"> +<pre class="src src-go"><span class="org-keyword">func</span> <span class="org-function-name">TestValidateLinuxIsSupported</span>(<span class="org-rainbow-identifiers-identifier-8">t</span> *<span class="org-type">testing.T</span>) { + <span class="org-rainbow-identifiers-identifier-6">valid</span> := <span class="org-function-name">Validate</span>(<span class="org-function-name">ANode</span>().<span class="org-function-name">Platform</span>( + <span class="org-function-name">APlatform</span>().<span class="org-function-name">OS</span>(<span class="org-string">"linux"</span>).<span class="org-function-name">Build</span>() + ).<span class="org-function-name">Build</span>()) + <span class="org-keyword">if</span> !<span class="org-rainbow-identifiers-identifier-6">valid</span> { + <span class="org-rainbow-identifiers-identifier-8">t</span>.<span class="org-function-name">Fatal</span>(<span class="org-string">"linux should be supported, it was not"</span>) + } +} + +<span class="org-keyword">func</span> <span class="org-function-name">TestValidateDarwinIsNotSupported</span>(<span class="org-rainbow-identifiers-identifier-8">t</span> *<span class="org-type">testing.T</span>) { + <span class="org-rainbow-identifiers-identifier-6">valid</span> := <span class="org-function-name">Validate</span>(<span class="org-function-name">ANode</span>().<span class="org-function-name">Platform</span>( + <span class="org-function-name">APlatform</span>().<span class="org-function-name">OS</span>(<span class="org-string">"darwin"</span>).<span class="org-function-name">Build</span>() + ).<span class="org-function-name">Build</span>()) + <span class="org-keyword">if</span> <span class="org-rainbow-identifiers-identifier-6">valid</span> { + <span class="org-rainbow-identifiers-identifier-8">t</span>.<span class="org-function-name">Fatal</span>(<span class="org-string">"darwin should not be supported, it was"</span>) + } +} +</pre> +</div> + +<p> +It does not really improve the visual noise as there is now quite a +few duplication : several <code>build</code>, <code>APlatform</code> inside <code>Platform</code>, … +It is a small improvement on readability but not that much compared +to the previous one. This is were the Go language features comes +into play. +</p> +</div> +</div> + +<div id="outline-container-org28e3042" class="outline-2"> +<h2 id="org28e3042">Functional arguments to the rescue</h2> +<div class="outline-text-2" id="text-org28e3042"> +<p> +Go has two interesting feature that are going to be useful here. +</p> + +<p> +First, a function in Go is a type on its own and thus is considered +a <i>first class citizen</i>. It means it's possible to pass a function +as argument, or define a variable that holds it. +</p> + +<div class="org-src-container"> +<pre class="src src-go"><span class="org-keyword">func</span> <span class="org-function-name">ApplyTo</span>(<span class="org-rainbow-identifiers-identifier-8">s</span> <span class="org-type">string</span>, <span class="org-rainbow-identifiers-identifier-12">fn</span> <span class="org-keyword">func</span>(<span class="org-type">string</span>) <span class="org-type">string</span>) <span class="org-type">string</span> { + <span class="org-keyword">return</span> <span class="org-function-name">fn</span>(<span class="org-rainbow-identifiers-identifier-8">s</span>) +} + +<span class="org-keyword">func</span> <span class="org-function-name">world</span>(<span class="org-rainbow-identifiers-identifier-8">s</span> <span class="org-type">string</span>) <span class="org-type">string</span> { + <span class="org-keyword">return</span> <span class="org-rainbow-identifiers-identifier-9">fmt</span>.<span class="org-function-name">Sprintf</span>(<span class="org-string">"%s, world!"</span>, <span class="org-rainbow-identifiers-identifier-8">s</span>) +} + +<span class="org-comment-delimiter">// </span><span class="org-comment">Usage</span> +<span class="org-rainbow-identifiers-identifier-1">a</span> := <span class="org-function-name">ApplyTo</span>(<span class="org-string">"hello"</span>, <span class="org-rainbow-identifiers-identifier-9">world</span>) +<span class="org-comment-delimiter">// </span><span class="org-comment">a == "hello, world!"</span> +</pre> +</div> + +<p> +The second feature that comes into play here, is the possiblity to +have <i>variadic</i> functions. A variadic function is a function that +takes a variable number of arguments (from <code>0</code> to any number of +argument). +</p> + +<div class="org-src-container"> +<pre class="src src-go"><span class="org-keyword">func</span> <span class="org-function-name">Print</span>(<span class="org-rainbow-identifiers-identifier-9">strs</span> ...<span class="org-type">string</span>) <span class="org-type">string</span> { + <span class="org-keyword">for</span> <span class="org-rainbow-identifiers-identifier-8">_</span>, <span class="org-rainbow-identifiers-identifier-8">s</span> := <span class="org-keyword">range</span> <span class="org-rainbow-identifiers-identifier-9">strs</span> { + <span class="org-rainbow-identifiers-identifier-9">fmt</span>.<span class="org-function-name">Println</span>(<span class="org-rainbow-identifiers-identifier-8">s</span>) + } +} +</pre> +</div> + +<p> +As we are going to see below, combining these two feature makes our +builders pretty easy to write and to use with simple case, while +staying very customizable, even outside of the builder. This is +really well described in a talk from Dave Cheney : <a href="https://www.youtube.com/watch?v=24lFtGHWxAQ&index=15&list=PLMW8Xq7bXrG58Qk-9QSy2HRh2WVeIrs7e">Functional +options for friendly APIs</a> (<a href="https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis">transcription</a>). +</p> + +<p> +Let's apply that to our new builders. +</p> + +<div class="org-src-container"> +<pre class="src src-go"><span class="org-keyword">func</span> <span class="org-function-name">ANode</span>(<span class="org-rainbow-identifiers-identifier-2">nodeBuilders</span> ...<span class="org-keyword">func</span>(*<span class="org-type">Node</span>)) *<span class="org-type">Node</span> { + <span class="org-rainbow-identifiers-identifier-5">node</span> := &<span class="org-type">Node</span>{ + <span class="org-constant">Name</span>: <span class="org-string">"node"</span>, + <span class="org-comment-delimiter">// </span><span class="org-comment">Other defaults</span> + } + + <span class="org-keyword">for</span> <span class="org-rainbow-identifiers-identifier-8">_</span>, <span class="org-rainbow-identifiers-identifier-10">build</span> := <span class="org-keyword">range</span> <span class="org-rainbow-identifiers-identifier-2">nodeBuilders</span> { + <span class="org-function-name">build</span>(<span class="org-rainbow-identifiers-identifier-5">node</span>) + } + + <span class="org-keyword">return</span> <span class="org-rainbow-identifiers-identifier-5">node</span> +} + +<span class="org-keyword">func</span> <span class="org-function-name">APlatform</span>(<span class="org-rainbow-identifiers-identifier-8">platformBuilders</span> ...<span class="org-keyword">func</span>(*<span class="org-type">Platform</span>)) *<span class="org-type">Platform</span> { + <span class="org-rainbow-identifiers-identifier-14">platform</span> := &<span class="org-type">Platform</span>{ + <span class="org-constant">Architecture</span>: <span class="org-string">"x64_86"</span>, + <span class="org-constant">OS</span>: <span class="org-string">"linux"</span>, + } + + <span class="org-keyword">for</span> <span class="org-rainbow-identifiers-identifier-8">_</span>, <span class="org-rainbow-identifiers-identifier-10">build</span> := <span class="org-keyword">range</span> <span class="org-rainbow-identifiers-identifier-8">platformBuilders</span> { + <span class="org-function-name">build</span>(<span class="org-rainbow-identifiers-identifier-14">platform</span>) + } + + <span class="org-keyword">return</span> <span class="org-rainbow-identifiers-identifier-14">platform</span> +} +</pre> +</div> + +<p> +And that is it for the actual builder code. It is <b>small</b> and +simple, there is <b>no more <code>NodeBuilder</code></b> struct, and this is highly +extensible. Let's see how to use it. +</p> + +<div class="org-src-container"> +<pre class="src src-go"><span class="org-comment-delimiter">// </span><span class="org-comment">a default node</span> +<span class="org-rainbow-identifiers-identifier-2">node1</span> := <span class="org-function-name">ANode</span>() +<span class="org-comment-delimiter">// </span><span class="org-comment">a node with a specific Hostname</span> +<span class="org-rainbow-identifiers-identifier-5">node2</span> := <span class="org-function-name">ANode</span>(<span class="org-keyword">func</span>(<span class="org-rainbow-identifiers-identifier-15">n</span> *<span class="org-type">Node</span>) { + <span class="org-rainbow-identifiers-identifier-15">n</span>.<span class="org-rainbow-identifiers-identifier-10">Hostname</span> = <span class="org-string">"custom-hostname"</span> +}) +<span class="org-comment-delimiter">// </span><span class="org-comment">a node with a specific name and platform</span> +<span class="org-rainbow-identifiers-identifier-8">node3</span> := <span class="org-function-name">ANode</span>(<span class="org-keyword">func</span>(<span class="org-rainbow-identifiers-identifier-15">n</span> *<span class="org-type">Node</span>) { + <span class="org-rainbow-identifiers-identifier-15">n</span>.<span class="org-rainbow-identifiers-identifier-8">Name</span> = <span class="org-string">"custom-name"</span> +}, <span class="org-keyword">func</span> (<span class="org-rainbow-identifiers-identifier-15">n</span> *<span class="org-type">Node</span>) { + <span class="org-rainbow-identifiers-identifier-15">n</span>.<span class="org-rainbow-identifiers-identifier-4">Platform</span> = <span class="org-function-name">APlatform</span>(<span class="org-keyword">func</span> (<span class="org-rainbow-identifiers-identifier-10">p</span> *<span class="org-type">Platform</span>) { + <span class="org-rainbow-identifiers-identifier-10">p</span>.<span class="org-rainbow-identifiers-identifier-12">OS</span> = <span class="org-string">"darwin"</span> + }) +}) +</pre> +</div> + +<p> +The last step is to define some <i>function builder</i> for common or +widely used customization, to make this <b>expressive</b>. And let +complex, <i>one-time</i> function builder in the end of the user. Now our +tests looks like. +</p> + +<div class="org-src-container"> +<pre class="src src-go"><span class="org-keyword">func</span> <span class="org-function-name">TestValidateLinuxIsSupported</span>(<span class="org-rainbow-identifiers-identifier-8">t</span> *<span class="org-type">testing.T</span>) { + <span class="org-rainbow-identifiers-identifier-6">valid</span> := <span class="org-function-name">Validate</span>(<span class="org-function-name">ANode</span>(<span class="org-function-name">WithAPlatform</span>(<span class="org-rainbow-identifiers-identifier-2">Linux</span>))) + <span class="org-keyword">if</span> !<span class="org-rainbow-identifiers-identifier-6">valid</span> { + <span class="org-rainbow-identifiers-identifier-8">t</span>.<span class="org-function-name">Fatal</span>(<span class="org-string">"linux should be supported, it was not"</span>) + } +} + +<span class="org-keyword">func</span> <span class="org-function-name">TestValidateDarwinIsNotSupported</span>(<span class="org-rainbow-identifiers-identifier-8">t</span> *<span class="org-type">testing.T</span>) { + <span class="org-rainbow-identifiers-identifier-6">valid</span> := <span class="org-function-name">Validate</span>(<span class="org-function-name">ANode</span>(<span class="org-function-name">WithAPlatform</span>(<span class="org-rainbow-identifiers-identifier-15">Darwin</span>))) + <span class="org-keyword">if</span> <span class="org-rainbow-identifiers-identifier-6">valid</span> { + <span class="org-rainbow-identifiers-identifier-8">t</span>.<span class="org-function-name">Fatal</span>(<span class="org-string">"darwin should not be supported, it was"</span>) + } +} + +<span class="org-comment-delimiter">// </span><span class="org-comment">Function builders</span> +<span class="org-keyword">func</span> <span class="org-function-name">WithAPlatform</span>(<span class="org-rainbow-identifiers-identifier-4">builders</span> ...<span class="org-keyword">func</span>(*<span class="org-type">Platform</span>)) <span class="org-keyword">func</span> (<span class="org-rainbow-identifiers-identifier-15">n</span> *<span class="org-type">Node</span>) { + <span class="org-keyword">return</span> <span class="org-keyword">func</span>(<span class="org-rainbow-identifiers-identifier-15">n</span> *<span class="org-type">Node</span>) { + <span class="org-rainbow-identifiers-identifier-15">n</span>.<span class="org-rainbow-identifiers-identifier-4">Platform</span> = <span class="org-function-name">Platform</span>(<span class="org-rainbow-identifiers-identifier-4">builders</span>...) + } +} + +<span class="org-keyword">func</span> <span class="org-function-name">Linux</span>(<span class="org-rainbow-identifiers-identifier-10">p</span> *<span class="org-type">Platform</span>) { + <span class="org-rainbow-identifiers-identifier-10">p</span>.<span class="org-rainbow-identifiers-identifier-12">OS</span> = <span class="org-string">"linux"</span> +} + +<span class="org-keyword">func</span> <span class="org-function-name">Darwin</span>(<span class="org-rainbow-identifiers-identifier-10">p</span> *<span class="org-type">Platform</span>) { + <span class="org-rainbow-identifiers-identifier-10">p</span>.<span class="org-rainbow-identifiers-identifier-12">OS</span> = <span class="org-string">"darwin"</span> +} +</pre> +</div> + +<p> +The intent is now clear. It's readable and still resilient to +change. The code <code>Node(WithPlatform(Linux))</code> is easy to understand +for a human. It makes what are the <i>tested</i> characteristics of +<code>struct</code> pretty clear. It's easy to combine multiple builders as the +<code>WithPlatform</code> function shows 👼. It's also easy to create a +<i>function builder</i>, even in a different package (as long as the ways +to modify the struct are exported) and complex or <i>on-off</i> builder +can be embedded in the function call (<code>Node(func(n *Node) { // … + })</code>). +</p> + +<p> +In summary, using these types of builder have several advantages : +</p> + +<ul class="org-ul"> +<li>tests are <b>easy to read</b>, and reduce the visual noise</li> +<li>tests are <b>resilient to change</b></li> +<li>builders are <b>easy to compose</b> and very extensible</li> +<li>builders could even be <b>shared</b> with production code as there is +nothing tied to <code>testing</code>.</li> +</ul> +</div> +</div> + + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2017-04-22-golang-testing-golden-file/" title="Golang testing — golden file">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2016-09-18-firefox-places-and-bookmarks/" title="Firefox hidden feature — places in bookmarks">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2017-04-22-golang-testing-golden-file/index.html b/www/vincent.demeester.fr/legacy/posts/2017-04-22-golang-testing-golden-file/index.html @@ -0,0 +1,359 @@ +<!DOCTYPE html> + +<html lang="en"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2017-04-22-golang-testing-golden-file/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang="en"/> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">Golang testing — golden file</h1><a href='https://vincent.demeester.fr/posts/2017-04-22-golang-testing-golden-file/'></a> + <address class="signature"> + <span class="date">Sat, 22 April, 2017</span> + <span class="words">(900 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#developement">developement</a></li> + + + + + + <li class="tag tag-golang"><a href="/tags/#golang">golang<span>12</span></a></li> + + + <li class="tag tag-testing"><a href="/tags/#testing">testing<span>11</span></a></li> + + + <li class="tag tag-golden"><a href="/tags/#golden">golden<span>1</span></a></li> + + + <li class="tag tag-file"><a href="/tags/#file">file<span>1</span></a></li> + + + <li class="tag tag-functionnal"><a href="/tags/#functionnal">functionnal<span>2</span></a></li> + + + <li class="tag tag-java"><a href="/tags/#java">java<span>4</span></a></li> + + <br/> + + </ul> + </header> + + + + <p> +Tests are all about <b>maintainability</b> and <b>readability</b>. You want the +least visual noise possible and it should not be a hassle to +maintain. When testing functions that output a long of string, in case +of a command line output testing, readability and maintainance tend to +be tricky to achieve. +</p> + + +<div id="outline-container-org2f1fc39" class="outline-2"> +<h2 id="org2f1fc39">The problem</h2> +<div class="outline-text-2" id="text-org2f1fc39"> +<p> +As an example let's say we want to test out the output of a command +that displays a list as a table. The output would look like the +following: +</p> + +<div class="org-src-container"> +<pre class="src src-sh"><span class="org-rainbow-identifiers-identifier-3">ID:</span> <span class="org-rainbow-identifiers-identifier-15">nodeID</span> +<span class="org-rainbow-identifiers-identifier-4">Name:</span> <span class="org-rainbow-identifiers-identifier-8">defaultNodeName</span> +<span class="org-rainbow-identifiers-identifier-10">Hostname:</span> <span class="org-rainbow-identifiers-identifier-6">defaultNodeHostname</span> +<span class="org-rainbow-identifiers-identifier-4">Joined</span> <span class="org-rainbow-identifiers-identifier-1">at:</span> <span class="org-rainbow-identifiers-identifier-11">2009-11-10</span> <span class="org-rainbow-identifiers-identifier-11">23:00:00</span> <span class="org-rainbow-identifiers-identifier-13">+0000</span> <span class="org-rainbow-identifiers-identifier-15">utc</span> +<span class="org-rainbow-identifiers-identifier-3">Status:</span> + <span class="org-rainbow-identifiers-identifier-4">State:</span> <span class="org-rainbow-identifiers-identifier-10">Ready</span> + <span class="org-rainbow-identifiers-identifier-11">Availability:</span> <span class="org-rainbow-identifiers-identifier-8">Active</span> + <span class="org-rainbow-identifiers-identifier-11">Address:</span> <span class="org-rainbow-identifiers-identifier-13">127.0.0.1</span> +<span class="org-rainbow-identifiers-identifier-9">Manager</span> <span class="org-rainbow-identifiers-identifier-3">Status:</span> + <span class="org-rainbow-identifiers-identifier-11">Address:</span> <span class="org-rainbow-identifiers-identifier-13">127.0.0.1</span> + <span class="org-rainbow-identifiers-identifier-3">Raft</span> <span class="org-rainbow-identifiers-identifier-3">Status:</span> <span class="org-rainbow-identifiers-identifier-8">Reachable</span> + <span class="org-rainbow-identifiers-identifier-8">Leader:</span> <span class="org-rainbow-identifiers-identifier-15">No</span> +<span class="org-rainbow-identifiers-identifier-3">Platform:</span> + <span class="org-rainbow-identifiers-identifier-15">Operating</span> <span class="org-rainbow-identifiers-identifier-13">System:</span> <span class="org-rainbow-identifiers-identifier-12">linux</span> + <span class="org-rainbow-identifiers-identifier-10">Architecture:</span> <span class="org-rainbow-identifiers-identifier-2">x86_64</span> +<span class="org-rainbow-identifiers-identifier-8">Resources:</span> + <span class="org-rainbow-identifiers-identifier-8">CPUs:</span> <span class="org-rainbow-identifiers-identifier-12">0</span> + <span class="org-rainbow-identifiers-identifier-9">Memory:</span> <span class="org-rainbow-identifiers-identifier-15">20</span> <span class="org-rainbow-identifiers-identifier-5">MiB</span> +<span class="org-rainbow-identifiers-identifier-14">Plugins:</span> + <span class="org-rainbow-identifiers-identifier-5">Network:</span> <span class="org-rainbow-identifiers-identifier-3">bridge,</span> <span class="org-rainbow-identifiers-identifier-12">overlay</span> + <span class="org-rainbow-identifiers-identifier-9">Volume:</span> <span class="org-rainbow-identifiers-identifier-12">local</span> +<span class="org-rainbow-identifiers-identifier-10">Engine</span> <span class="org-rainbow-identifiers-identifier-14">Version:</span> <span class="org-rainbow-identifiers-identifier-15">1.13.0</span> +<span class="org-rainbow-identifiers-identifier-10">Engine</span> <span class="org-rainbow-identifiers-identifier-12">Labels:</span> + <span class="org-rainbow-identifiers-identifier-11">-</span> <span class="org-variable-name">engine</span> = <span class="org-rainbow-identifiers-identifier-12">label</span> +</pre> +</div> + +<p> +Let's see how we would test that output, naively. +</p> + +<div class="org-src-container"> +<pre class="src src-go"><span class="org-keyword">func</span> <span class="org-function-name">TestNodeInspectPretty</span>(<span class="org-rainbow-identifiers-identifier-8">t</span> *<span class="org-type">testing.T</span>) { + <span class="org-rainbow-identifiers-identifier-14">expected</span> := <span class="org-string">```</span> +<span class="org-string">ID: nodeID</span> +<span class="org-string">Name: defaultNodeName</span> +<span class="org-string">Hostname: defaultNodeHostname</span> +<span class="org-string">Joined at: 2009-11-10 23:00:00 +0000 utc</span> +<span class="org-string">Status:</span> +<span class="org-string"> State: Ready</span> +<span class="org-string"> Availability: Active</span> +<span class="org-string"> Address: 127.0.0.1</span> +<span class="org-string">Manager Status:</span> +<span class="org-string"> Address: 127.0.0.1</span> +<span class="org-string"> Raft Status: Reachable</span> +<span class="org-string"> Leader: No</span> +<span class="org-string">Platform:</span> +<span class="org-string"> Operating System: linux</span> +<span class="org-string"> Architecture: x86_64</span> +<span class="org-string">Resources:</span> +<span class="org-string"> CPUs: 0</span> +<span class="org-string"> Memory: 20 MiB</span> +<span class="org-string">Plugins:</span> +<span class="org-string"> Network: bridge, overlay</span> +<span class="org-string"> Volume: local</span> +<span class="org-string">Engine Version: 1.13.0</span> +<span class="org-string">Engine Labels:</span> +<span class="org-string"> - engine = label</span> +<span class="org-string">```</span> + <span class="org-rainbow-identifiers-identifier-3">buf</span> := <span class="org-builtin">new</span>(<span class="org-type">bytes.Buffer</span>) + <span class="org-rainbow-identifiers-identifier-3">cmd</span> := <span class="org-function-name">newInspectCommand</span>( + <span class="org-rainbow-identifiers-identifier-5">test</span>.<span class="org-function-name">NewFakeCli</span>(&<span class="org-type">fakeClient</span>{ + <span class="org-constant">nodeInspectFunc</span>: <span class="org-keyword">func</span>() (<span class="org-type">swarm.Node</span>, []<span class="org-type">byte</span>, <span class="org-type">error</span>) { + <span class="org-keyword">return</span> *<span class="org-function-name">Node</span>(<span class="org-function-name">Manager</span>()), []<span class="org-type">byte</span>{}, <span class="org-constant">nil</span> + }, + }, <span class="org-rainbow-identifiers-identifier-3">buf</span>)) + <span class="org-rainbow-identifiers-identifier-3">cmd</span>.<span class="org-function-name">SetArgs</span>([]<span class="org-type">string</span>{<span class="org-string">"nodeID"</span>}) + <span class="org-rainbow-identifiers-identifier-3">cmd</span>.<span class="org-function-name">Flags</span>().<span class="org-function-name">Set</span>(<span class="org-string">"pretty"</span>, <span class="org-string">"true"</span>) + <span class="org-rainbow-identifiers-identifier-11">assert</span>.<span class="org-function-name">NilError</span>(<span class="org-rainbow-identifiers-identifier-8">t</span>, <span class="org-rainbow-identifiers-identifier-3">cmd</span>.<span class="org-function-name">Execute</span>()) + <span class="org-rainbow-identifiers-identifier-15">actual</span> := <span class="org-rainbow-identifiers-identifier-3">buf</span>.<span class="org-function-name">String</span>() + <span class="org-rainbow-identifiers-identifier-11">assert</span>.<span class="org-function-name">EqualNormalizedString</span>(<span class="org-rainbow-identifiers-identifier-8">t</span>, <span class="org-rainbow-identifiers-identifier-11">assert</span>.<span class="org-rainbow-identifiers-identifier-2">RemoveSpace</span>, <span class="org-rainbow-identifiers-identifier-15">actual</span>, <span class="org-function-name">string</span>(<span class="org-rainbow-identifiers-identifier-14">expected</span>)) +} +</pre> +</div> + +<p> +This might look ok as is, but a few problem are present: +</p> + +<ul class="org-ul"> +<li>The output is quite large and adds some noise to the test</li> +<li><p> +Lot's of value in the <code>expected</code> string comes from default values +of our <code>Node</code> builder. +</p> + +<p> +This means any time we change our builder default values, we would +need to update this test, <i>painful</i>. +</p></li> +<li>If the output changes for a good reason (add a field, fix a typo, +…), this test has to be updated too.</li> +</ul> +</div> +</div> + +<div id="outline-container-org0519f3e" class="outline-2"> +<h2 id="org0519f3e">Golden files to the rescue</h2> +<div class="outline-text-2" id="text-org0519f3e"> +<p> +First, let's get back at what is our test about and what we actually +want to test. +</p> + +<ul class="org-ul"> +<li>We want to ensure, the output of the function does not change by +mistake (i.e. change that wasn't supposed to change the output)</li> +<li>We want to have an update version of the output if that was the +purpose of our change. And we want this update to be the least +painful possible.</li> +<li>We don't really care about the final outputs as long as it stays +the same for the same inputs (i.e. we don't test for any number of +space, or that word are valid English, or …).</li> +</ul> + +<p> +This is where the concept of <b>golden file</b> is useful. In a +nutshell, a golden file is a file where we store the output and that +will be used by the test as the expected output. This file should be +updated any time the output changes for good reason. That's that +simple 👼. +</p> + +<p> +Once again, the way go <code>testing</code> works, introducing and using golden +files in our tests is pretty straightforward and easy to use. +</p> + +<p> +Let's write a small <i>golden file helper</i> so that our test has no +visual noise, in a <code>golden</code> package. +</p> + +<div class="org-src-container"> +<pre class="src src-go"><span class="org-keyword">var</span> <span class="org-rainbow-identifiers-identifier-3">update</span> = <span class="org-rainbow-identifiers-identifier-15">flag</span>.<span class="org-function-name">Bool</span>(<span class="org-string">"test.update"</span>, <span class="org-constant">false</span>, <span class="org-string">"update golden file"</span>) + +<span class="org-comment-delimiter">// </span><span class="org-comment">Get returns the golden file content. If the `test.update` is specified, it updates the</span> +<span class="org-comment-delimiter">// </span><span class="org-comment">file with the current output and returns it.</span> +<span class="org-keyword">func</span> <span class="org-function-name">Get</span>(<span class="org-rainbow-identifiers-identifier-8">t</span> *<span class="org-type">testing.T</span>, <span class="org-rainbow-identifiers-identifier-15">actual</span> []<span class="org-type">byte</span>, <span class="org-rainbow-identifiers-identifier-5">filename</span> <span class="org-type">string</span>) []<span class="org-type">byte</span> { + <span class="org-rainbow-identifiers-identifier-1">golden</span> := <span class="org-rainbow-identifiers-identifier-9">filepath</span>.<span class="org-function-name">Join</span>(<span class="org-string">"testdata"</span>, <span class="org-rainbow-identifiers-identifier-5">filename</span>) + <span class="org-keyword">if</span> *<span class="org-rainbow-identifiers-identifier-3">update</span> { + <span class="org-keyword">if</span> <span class="org-rainbow-identifiers-identifier-10">err</span> := <span class="org-rainbow-identifiers-identifier-1">ioutil</span>.<span class="org-function-name">WriteFile</span>(<span class="org-rainbow-identifiers-identifier-1">golden</span>, <span class="org-rainbow-identifiers-identifier-15">actual</span>, <span class="org-rainbow-identifiers-identifier-4">0644</span>); <span class="org-rainbow-identifiers-identifier-10">err</span> != <span class="org-constant">nil</span> { + <span class="org-rainbow-identifiers-identifier-8">t</span>.<span class="org-function-name">Fatal</span>(<span class="org-rainbow-identifiers-identifier-10">err</span>) + } + } + <span class="org-rainbow-identifiers-identifier-14">expected</span>, <span class="org-rainbow-identifiers-identifier-10">err</span> := <span class="org-rainbow-identifiers-identifier-1">ioutil</span>.<span class="org-function-name">ReadFile</span>(<span class="org-rainbow-identifiers-identifier-1">golden</span>) + <span class="org-keyword">if</span> <span class="org-rainbow-identifiers-identifier-10">err</span> != <span class="org-constant">nil</span> { + <span class="org-rainbow-identifiers-identifier-8">t</span>.<span class="org-function-name">Fatal</span>(<span class="org-rainbow-identifiers-identifier-10">err</span>) + } + <span class="org-keyword">return</span> <span class="org-rainbow-identifiers-identifier-14">expected</span> +} +</pre> +</div> + +<ul class="org-ul"> +<li>We define a <i>global</i> flag, <code>-test.update</code> that will <i>enhance</i> the +<code>go test</code> command with it (as soon as we import this <code>golden</code> package).</li> +<li><p> +We define a <code>golden.Get</code> function that takes the current output +and the path of the golden file. It also takes <code>testing.T</code> so any +failure happening here (like reading file, …) will make the test +fail (one less thing to write in the test calling this function). +</p> + +<p> +If the flag is present when running the test, it will update the +file with the actual content. +</p></li> +</ul> + +<p> +The initial test becomes. +</p> + +<div class="org-src-container"> +<pre class="src src-go"><span class="org-keyword">func</span> <span class="org-function-name">TestNodeInspectPretty</span>(<span class="org-rainbow-identifiers-identifier-8">t</span> *<span class="org-type">testing.T</span>) { + <span class="org-rainbow-identifiers-identifier-3">buf</span> := <span class="org-builtin">new</span>(<span class="org-type">bytes.Buffer</span>) + <span class="org-rainbow-identifiers-identifier-3">cmd</span> := <span class="org-function-name">newInspectCommand</span>( + <span class="org-rainbow-identifiers-identifier-5">test</span>.<span class="org-function-name">NewFakeCli</span>(&<span class="org-type">fakeClient</span>{ + <span class="org-constant">nodeInspectFunc</span>: <span class="org-keyword">func</span>() (<span class="org-type">swarm.Node</span>, []<span class="org-type">byte</span>, <span class="org-type">error</span>) { + <span class="org-keyword">return</span> *<span class="org-function-name">Node</span>(<span class="org-function-name">Manager</span>()), []<span class="org-type">byte</span>{}, <span class="org-constant">nil</span> + }, + }, <span class="org-rainbow-identifiers-identifier-3">buf</span>)) + <span class="org-rainbow-identifiers-identifier-3">cmd</span>.<span class="org-function-name">SetArgs</span>([]<span class="org-type">string</span>{<span class="org-string">"nodeID"</span>}) + <span class="org-rainbow-identifiers-identifier-3">cmd</span>.<span class="org-function-name">Flags</span>().<span class="org-function-name">Set</span>(<span class="org-string">"pretty"</span>, <span class="org-string">"true"</span>) + <span class="org-rainbow-identifiers-identifier-11">assert</span>.<span class="org-function-name">NilError</span>(<span class="org-rainbow-identifiers-identifier-8">t</span>, <span class="org-rainbow-identifiers-identifier-3">cmd</span>.<span class="org-function-name">Execute</span>()) + <span class="org-rainbow-identifiers-identifier-15">actual</span> := <span class="org-rainbow-identifiers-identifier-3">buf</span>.<span class="org-function-name">String</span>() + <span class="org-rainbow-identifiers-identifier-14">expected</span> := <span class="org-rainbow-identifiers-identifier-1">golden</span>.<span class="org-function-name">Get</span>(<span class="org-rainbow-identifiers-identifier-8">t</span>, []<span class="org-function-name">byte</span>(<span class="org-rainbow-identifiers-identifier-15">actual</span>), <span class="org-string">"myfile.golden"</span>) + <span class="org-rainbow-identifiers-identifier-11">assert</span>.<span class="org-function-name">EqualNormalizedString</span>(<span class="org-rainbow-identifiers-identifier-8">t</span>, <span class="org-rainbow-identifiers-identifier-11">assert</span>.<span class="org-rainbow-identifiers-identifier-2">RemoveSpace</span>, <span class="org-rainbow-identifiers-identifier-15">actual</span>, <span class="org-function-name">string</span>(<span class="org-rainbow-identifiers-identifier-14">expected</span>)) +} +</pre> +</div> + +<p> +If we change the output, the workflow becomes the following : +</p> + +<ul class="org-ul"> +<li>run <code>go test</code> and make sure it's failing,</li> +<li>Validate that the current output is correct,</li> +<li>run <code>go test -test.update</code> to update the golden file(s),</li> +<li>re-run <code>go test</code> to make sure it's now green,</li> +<li>you are done 👼.</li> +</ul> + +<p> +With this simple trick, our test now <b>contains less noise</b> and is +way more <b>maintainable</b> (you just have a command to run to update +the expected content). +</p> +</div> +</div> + + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2018-07-28-gotest-tools-intro/" title="Golang testing — gotest.tools introduction">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2017-01-01-go-testing-functionnal-builders/" title="Golang testing — functional arguments for wonderful builders">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2018-07-28-gotest-tools-intro/index.html b/www/vincent.demeester.fr/legacy/posts/2018-07-28-gotest-tools-intro/index.html @@ -0,0 +1,154 @@ +<!DOCTYPE html> + +<html lang="en"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2018-07-28-gotest-tools-intro/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang=""/> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">Golang testing — gotest.tools introduction</h1><a href='https://vincent.demeester.fr/posts/2018-07-28-gotest-tools-intro/'></a> + <address class="signature"> + <span class="date">Sat, 28 July, 2018</span> + <span class="words">(400 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#developement">developement</a></li> + + + + + + <li class="tag tag-testing"><a href="/tags/#testing">testing<span>11</span></a></li> + + + <li class="tag tag-golang"><a href="/tags/#golang">golang<span>12</span></a></li> + + + <li class="tag tag-featured"><a href="/tags/#featured">featured<span>2</span></a></li> + + <br/> + + </ul> + </header> + + I already wrote 2 previous posts about golang and testing. It’s something I care deeply about and I wanted to continue + writing about it. It took me a bit more time than I thought, but getting back to it. Since the <a href="http://vincent.demeester.fr/posts/2017-04-22-golang-testing-golden-file/">last post</a>, Daniel Nephin + and I worked (but mainly Daniel 🤗) on bootstrapping a testing helper library. + + <p>Let me introduce it to you this library : <a href="https://gotest.tools"><code>gotest.tools</code></a>. As described in the <a href="https://godoc.org/gotest.tools">godoc</a> package comment, <code>gotest.tools</code> is a +collection of packages to augment <code>testing</code> and support common patterns. It’s an enhanced and growing version of the +initial helpers we (the docker/moby maintainers) wrote initially in <a href="https://github.com/docker/docker"><code>docker/docker</code></a> repository. We are using in quite some +project here at <a href="https://github.com">Docker</a>.</p> + +<p>There is a bunch of packages that will all have their own post (linked here when available) :</p> + +<ul> +<li><a href="/posts/2018-08-16-gotest-tools-assertions/"><code>assert</code></a> (with <code>assert/cmp</code> and <code>assert/opt</code>) that provides assertions for comparing expected values to actual values.</li> +<li><code>env</code> that provides functions to test code that read environment variable or the current working directory.</li> +<li><a href="/posts/2018-09-14-gotest-tools-fs/"><code>fs</code></a> that provides tools for creating temporary files, and testing the contents and structure of a directory.</li> +<li><a href="/posts/2018-09-06-gotest-tools-golden/"><code>golden</code></a> that provides tools for comparing large multi-line strings.</li> +<li><a href="/posts/2018-09-18-gotest-tools-icmd/"><code>icmd</code></a> that executes binaries and provides convenient assertions for testing the results.</li> +<li><code>poll</code> that provides tools for testing asynchronous code.</li> +<li><a href="/posts/2018-09-01-gotest-tools-skip/"><code>skip</code></a> that provides functions for skipping a test and printing the source code of the condition used to skip the test.</li> +</ul> + +<p>There is also experimental package, using the <code>x</code> notation (as the golang team uses, for example with <code>golang.org/x/sync</code>) :</p> + +<ul> +<li><code>x/subtest</code> that provides a <code>TestContext</code> to subtests which handles cleanup and provides a <code>testing.TB</code> and <code>context.Context</code>.</li> +</ul> + +<p>There is already some good <code>testing</code> helpers in the Go ecosystem : <a href="https://github.com/stretchr/testify"><code>testify</code></a>, <a href="http://labix.org/gocheck"><code>gocheck</code></a>, <a href="https://github.com/onsi/ginkgo"><code>ginkgo</code></a> and a lot more — so +why create a new one ? There is multiple reason for it, most of them can be seen in the following <a href="https://github.com/gotestyourself/gotest.tools/issues/49#issuecomment-362436026">GitHub issue</a>.</p> + +<p><a href="https://github.com/dnephin/">Daniel</a> also wrote a very useful converter if your code base is currently using <code>testify</code> : <code>gty-migrate-from-testify</code>.</p> +<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ go get -u gotest.tools/assert/cmd/gty-migrate-from-testify +<span class="c1"># […]</span> +$ go list <span class="se">\ +</span><span class="se"></span> -f <span class="s1">'{{.ImportPath}} {{if .XTestGoFiles}}{{"\n"}}{{.ImportPath}}_test{{end}}'</span> <span class="se">\ +</span><span class="se"></span> ./... <span class="p">|</span> xargs gty-migrate-from-testify</code></pre></div> +<p>In the next post, let’s dig into the assertion part of the library, package <code>assert</code> 👼.</p> + + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2018-08-16-gotest-tools-assertions/" title="Golang testing — gotest.tools assertions">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2017-04-22-golang-testing-golden-file/" title="Golang testing — golden file">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2018-08-16-gotest-tools-assertions/index.html b/www/vincent.demeester.fr/legacy/posts/2018-08-16-gotest-tools-assertions/index.html @@ -0,0 +1,443 @@ +<!DOCTYPE html> + +<html lang="en"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2018-08-16-gotest-tools-assertions/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang=""/> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">Golang testing — gotest.tools assertions</h1><a href='https://vincent.demeester.fr/posts/2018-08-16-gotest-tools-assertions/'></a> + <address class="signature"> + <span class="date">Thu, 16 August, 2018</span> + <span class="words">(2000 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#developement">developement</a></li> + + + + + + <li class="tag tag-testing"><a href="/tags/#testing">testing<span>11</span></a></li> + + + <li class="tag tag-golang"><a href="/tags/#golang">golang<span>12</span></a></li> + + + <li class="tag tag-assert"><a href="/tags/#assert">assert<span>1</span></a></li> + + <br/> + + </ul> + </header> + + <p>Faster way to send the same command to each and every <em>pane</em> in your + tmux <em>session</em>.</p> + +<p>Let’s take a closer look at <a href="https://gotest.tools"><code>gotest.tools</code></a> assertions packages. This is mainly about <code>assert</code>, <code>assert/cmp</code> and + <code>assert/opt</code>.</p> + +<blockquote> +<p>Package assert provides assertions for comparing expected values to actual values. When assertion fails a helpful error +message is printed.</p> +</blockquote> + +<p>There is two main functions (<code>Assert</code> and <code>Check</code>) and some helpers (like <code>NilError</code>, …). They all take a <code>*testing.T</code> as + a first argument, pretty common across testing Go libraries. Let’s dive into those !</p> + + + + +<div class="ox-hugo-toc toc"> +<div></div> + +<div class="heading">Table of Contents</div> + +<ul> +<li><a href="#assert-and-check"><code>Assert</code> and <code>Check</code></a></li> +<li><a href="#more-assert-helpers">More <code>assert</code> helpers</a></li> +<li><a href="#cmp-dot-comparison"><code>cmp.Comparison</code></a> + +<ul> +<li><a href="#equality-with-equal-and-deepequal">Equality with <code>Equal</code> and <code>DeepEqual</code></a></li> +<li><a href="#errors-with-error-errorcontains-and-errortype">Errors with <code>Error</code>, <code>ErrorContains</code> and <code>ErrorType</code></a></li> +<li><a href="#bonus-with-panics">Bonus with <code>Panics</code></a></li> +<li><a href="#miscellaneous-with-contains-len-and-nil">Miscellaneous with <code>Contains</code>, <code>Len</code> and <code>Nil</code></a></li> +<li><a href="#write-your-own-comparison">Write your own <code>Comparison</code></a></li> +</ul></li> +<li><a href="#conclusion">Conclusion…</a></li> +</ul> + +<p></div> +<!--endtoc--></p> + +<h2 id="assert-and-check"><code>Assert</code> and <code>Check</code></h2> + +<p>Both those functions accept a <code>Comparison</code> (we’ll check what it is later on) and fail the test when that comparison +fails. The one difference is that <code>Assert</code> will end the test execution at immediately whereas <code>Check</code> will fail the test +and proceed with the rest of the test case. This is similar to <code>FailNow</code> and <code>Fail</code> from the standard library +<code>testing</code>. Both have their use cases.</p> + +<p>We’ll Use <code>Assert</code> for the rest of the section but any example here would work with <code>Check</code> too. When we said +<code>Comparison</code> above, it’s mainly the <a href="https://godoc.org/gotest.tools/assert#BoolOrComparison">BoolOrComparison</a> interface — it can either be a boolean expression, or a +<a href="https://godoc.org/gotest.tools/assert/cmp#Comparison">cmp.Comparison</a> type. <code>Assert</code> and <code>Check</code> code will be <em>smart</em> enough to detect which one it is.</p> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="nx">assert</span><span class="p">.</span><span class="nf">Assert</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">ok</span><span class="p">)</span> +<span class="nx">assert</span><span class="p">.</span><span class="nf">Assert</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span><span class="p">)</span> +<span class="nx">assert</span><span class="p">.</span><span class="nf">Assert</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">foo</span><span class="p">.</span><span class="nf">IsBar</span><span class="p">())</span></code></pre></div> +<p>So far not anything extra-ordinary. Let’s first look at some more <em>helper</em> functions in the <code>assert</code> package and quickly +dive a bit deeper with <code>Comparison</code>.</p> + +<h2 id="more-assert-helpers">More <code>assert</code> helpers</h2> + +<p>The additional helper functions are the following</p> + +<ul> +<li><code>Equal</code> uses the <code>==</code> operator to assert two values are equal.</li> +<li><code>DeepEqual</code> uses <code>google/go-cmp</code> to assert two values are equal (it’s <em>close</em> to <code>reflect.DeepEqual</code> but not +quite). We’ll detail a bit more the <em>options</em> part of this function with <code>cmp.DeepEqual</code>.</li> +<li><code>Error</code> fails if the error is <code>nil</code> <strong>or</strong> the error message is not the expected one.</li> +<li><code>ErrorContains</code> fails if the error is <code>nil</code> <strong>or</strong> the error message does not contain the expected substring.</li> +<li><code>ErrorType</code> fails if the error is <code>nil</code> <strong>or</strong> the error type is not the expected type.</li> +<li><code>NilError</code> fails if the error is not <code>nil</code>.</li> +</ul> + +<p>All those helper functions have a equivalent function in the <code>cmp</code> package that returns a <code>Comparison</code>. I, personally, +prefer to use <code>assert.Check</code> or <code>assert.Assert</code> in combination with <code>cmp.Comparison</code> as it allows me to write all my +assertions the same way, with built-ins comparison or with my own — i.e. <code>assert.Assert(t, is.Equal(…), "message"</code> or +<code>assert.Assert(t, stackIsUp(c, time…), "another message")</code>.</p> + +<h2 id="cmp-dot-comparison"><code>cmp.Comparison</code></h2> + +<p>This is where it get really interesting, <code>gotest.tools</code> tries to make it as easy as possible for you to create +appropriate comparison — making you test readable as much as possible.</p> + +<p>Let’s look a bit at the <code>cmp.Comparison</code> type.</p> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="kd">type</span> <span class="nx">Comparison</span> <span class="kd">func</span><span class="p">()</span> <span class="nx">Result</span></code></pre></div> +<p>It’s just a function that returns a <code>cmp.Result</code>, so let’s look at <code>cmp.Result</code> definition.</p> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="kd">type</span> <span class="nx">Result</span> <span class="kd">interface</span> <span class="p">{</span> + <span class="nf">Success</span><span class="p">()</span> <span class="kt">bool</span> +<span class="p">}</span></code></pre></div> +<p>Result is an <code>interface</code>, thus any <em>struct</em> that provide a function <code>Success</code> that returns a <code>bool</code> can be used as a +comparison result, making it really easy to use in your code. There is also existing type of result to make it even +quicker to write your own comparison.</p> + +<ul> +<li><code>ResultSuccess</code> is a constant which is returned to indicate success.</li> +<li><code>ResultFailure</code> and <code>ResultFailureTemplate</code> return a failed Result with a failure message.</li> +<li><code>ResultFromError</code> returns <code>ResultSuccess</code> if <code>err</code> is nil. Otherwise <code>ResultFailure</code> is returned with the error +message as the failure message. It works a bit like the <code>errors.Wrap</code> function of the <a href="https://github.com/pkg/errors"><code>github.com/pkgs/errors</code></a> +package.</li> +</ul> + +<p>The <code>cmp</code> package comes with a few defined comparison that, we think, should cover a high number of use-cases. Let’s +look at them.</p> + +<h3 id="equality-with-equal-and-deepequal">Equality with <code>Equal</code> and <code>DeepEqual</code></h3> + +<blockquote> +<p>Equal uses the == operator to assert two values are equal and fails the test if they are not equal.</p> + +<p>If the comparison fails Equal will use the variable names for x and y as part of the failure message to identify the +actual and expected values.</p> + +<p>If either x or y are a multi-line string the failure message will include a unified diff of the two values. If the +values only differ by whitespace the unified diff will be augmented by replacing whitespace characters with visible +characters to identify the whitespace difference.</p> +</blockquote> + +<p>On the other hand…</p> + +<blockquote> +<p>DeepEqual uses google/go-cmp (<a href="http://bit.do/go-cmp">http://bit.do/go-cmp</a>) to assert two values are equal and fails the test if they are not +equal.</p> + +<p>Package <a href="https://godoc.org/gotest.tools/assert/opt">https://godoc.org/gotest.tools/assert/opt</a> provides some additional commonly used Options.</p> +</blockquote> + +<p>Using one or the other is as simple as : if you wrote your <code>if</code> with <code>==</code> then use <code>Equal</code>, otherwise use <code>DeepEqual</code>. +<code>DeepEqual</code> (and usually <code>reflect.DeepEqual</code>) is used when you want to compare anything more complex than primitive +types. One advantage of using <code>cmp.DeepEqual</code> over <code>reflect.DeepEqual</code> (in an if), is that you get a well crafted +message that shows the diff between the expected and the actual structs compared – and you can pass options to it.</p> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="nx">assert</span><span class="p">.</span><span class="nf">Assert</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">cmp</span><span class="p">.</span><span class="nf">DeepEqual</span><span class="p">([]</span><span class="kt">string</span><span class="p">{</span><span class="s">"a"</span><span class="p">,</span> <span class="s">"b"</span><span class="p">},</span> <span class="p">[]</span><span class="kt">string</span><span class="p">{</span><span class="s">"b"</span><span class="p">,</span> <span class="s">"a"</span><span class="p">}))</span> +<span class="c1">// Will print something like +</span><span class="c1">// --- result +</span><span class="c1">// +++ exp +</span><span class="c1">// {[]string}[0]: +</span><span class="c1">// -: "a" +</span><span class="c1">// +: "b" +</span><span class="c1">// {[]string}[1]: +</span><span class="c1">// -: "b" +</span><span class="c1">// +: "a" +</span><span class="c1"></span><span class="nx">foo</span> <span class="o">:=</span> <span class="o">&</span><span class="nf">someType</span><span class="p">(</span><span class="nx">a</span><span class="p">:</span> <span class="s">"with"</span><span class="p">,</span> <span class="nx">b</span><span class="p">:</span> <span class="s">"value"</span><span class="p">)</span> +<span class="nx">bar</span> <span class="o">:=</span> <span class="o">&</span><span class="nf">someType</span><span class="p">(</span><span class="nx">a</span><span class="p">:</span> <span class="s">"with"</span><span class="p">,</span> <span class="nx">b</span><span class="p">:</span> <span class="s">"value"</span><span class="p">)</span> +<span class="c1">// the following will succeed as foo and bar are _DeepEqual_ +</span><span class="c1"></span><span class="nx">assert</span><span class="p">.</span><span class="nf">Assert</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">cmp</span><span class="p">.</span><span class="nf">DeepEqual</span><span class="p">(</span><span class="nx">foo</span><span class="p">,</span> <span class="nx">bar</span><span class="p">))</span></code></pre></div> +<p>When using <code>DeepEqual</code>, you may end up with really weird behavior(s). You may want to ignore some fields, or consider +<code>nil</code> slice or map the same as empty ones ; or more common, your <em>struct</em> contains some unexported fields that you +cannot use when comparing (as they are not exported 😓). In those case, you can use <code>go-cmp</code> options.</p> + +<p>Some existing one are :</p> + +<ul> +<li><a href="https://godoc.org/github.com/google/go-cmp/cmp/cmpopts#EquateEmpty"><code>EquateEmpty</code></a> returns a Comparer option that determines all maps and slices with a length of zero to be equal, +regardless of whether they are nil.</li> +<li><a href="https://godoc.org/github.com/google/go-cmp/cmp/cmpopts#IgnoreFields"><code>IgnoreFields</code></a> returns an Option that ignores exported fields of the given names on a single struct type. The struct +type is specified by passing in a value of that type.</li> +<li><a href="https://godoc.org/github.com/google/go-cmp/cmp/cmpopts#IgnoreUnexported"><code>IgnoreUnexported</code></a> returns an Option that only ignores the immediate unexported fields of a struct, including anonymous +fields of unexported types.</li> +<li><a href="https://godoc.org/github.com/google/go-cmp/cmp/cmpopts#SortSlices"><code>SortSlices</code></a> returns a Transformer option that sorts all <code>[]V</code></li> +<li>… and <a href="https://godoc.org/github.com/google/go-cmp/cmp/cmpopts">more</a> 👼</li> +</ul> + +<p><code>gotest.tools</code> also defines some <strong>and</strong> you can define yours ! As an example, <code>gotest.tools</code> defines <code>TimeWithThreshold</code> +and <code>DurationWithThreshold</code> that allows to not fails if the time (or duration) is not exactly the same but in the +specified threshold we specified. Here is the code for <code>DurationWithThreshold</code> for inspiration.</p> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="c1">// DurationWithThreshold returns a gocmp.Comparer for comparing time.Duration. The +</span><span class="c1">// Comparer returns true if the difference between the two Duration values is +</span><span class="c1">// within the threshold and neither value is zero. +</span><span class="c1"></span><span class="kd">func</span> <span class="nf">DurationWithThreshold</span><span class="p">(</span><span class="nx">threshold</span> <span class="nx">time</span><span class="p">.</span><span class="nx">Duration</span><span class="p">)</span> <span class="nx">gocmp</span><span class="p">.</span><span class="nx">Option</span> <span class="p">{</span> + <span class="k">return</span> <span class="nx">gocmp</span><span class="p">.</span><span class="nf">Comparer</span><span class="p">(</span><span class="nf">cmpDuration</span><span class="p">(</span><span class="nx">threshold</span><span class="p">))</span> +<span class="p">}</span> + +<span class="kd">func</span> <span class="nf">cmpDuration</span><span class="p">(</span><span class="nx">threshold</span> <span class="nx">time</span><span class="p">.</span><span class="nx">Duration</span><span class="p">)</span> <span class="kd">func</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span> <span class="nx">time</span><span class="p">.</span><span class="nx">Duration</span><span class="p">)</span> <span class="kt">bool</span> <span class="p">{</span> + <span class="k">return</span> <span class="kd">func</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span> <span class="nx">time</span><span class="p">.</span><span class="nx">Duration</span><span class="p">)</span> <span class="kt">bool</span> <span class="p">{</span> + <span class="k">if</span> <span class="nx">x</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">||</span> <span class="nx">y</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">{</span> + <span class="k">return</span> <span class="kc">false</span> + <span class="p">}</span> + <span class="nx">delta</span> <span class="o">:=</span> <span class="nx">x</span> <span class="o">-</span> <span class="nx">y</span> + <span class="k">return</span> <span class="nx">delta</span> <span class="o"><=</span> <span class="nx">threshold</span> <span class="o">&&</span> <span class="nx">delta</span> <span class="o">>=</span> <span class="o">-</span><span class="nx">threshold</span> + <span class="p">}</span> +<span class="p">}</span></code></pre></div> +<p>Another good example for those options is when you want to skip some field. In <a href="https://github.com/docker/docker"><code>docker/docker</code></a> we want to be able to +easily check for equality between two service specs, but those might have different <code>CreatedAt</code> and <code>UpdatedAt</code> values +that we usually don’t care about – what we want is to make sure it happens in the past 20 seconds. You can easily define +an option for that.</p> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="kd">func</span> <span class="nf">cmpServiceOpts</span><span class="p">()</span> <span class="nx">cmp</span><span class="p">.</span><span class="nx">Option</span> <span class="p">{</span> + <span class="kd">const</span> <span class="nx">threshold</span> <span class="p">=</span> <span class="mi">20</span> <span class="o">*</span> <span class="nx">time</span><span class="p">.</span><span class="nx">Second</span> + + <span class="c1">// Apply withinThreshold only for the following fields +</span><span class="c1"></span> <span class="nx">metaTimeFields</span> <span class="o">:=</span> <span class="kd">func</span><span class="p">(</span><span class="nx">path</span> <span class="nx">cmp</span><span class="p">.</span><span class="nx">Path</span><span class="p">)</span><span class="kt">bool</span> <span class="p">{</span> + <span class="k">switch</span> <span class="nx">path</span><span class="p">.</span><span class="nf">String</span><span class="p">()</span> <span class="p">{</span> + <span class="k">case</span> <span class="s">"Meta.CreatedAt"</span><span class="p">,</span> <span class="s">"Meta.UpdatedAt"</span><span class="p">:</span> + <span class="k">return</span> <span class="kc">true</span> + <span class="p">}</span> + <span class="k">return</span> <span class="kc">false</span> + <span class="p">}</span> + <span class="c1">// have a 20s threshold for the time value that will be passed +</span><span class="c1"></span> <span class="nx">withinThreshold</span> <span class="o">:=</span> <span class="nx">cmp</span><span class="p">.</span><span class="nf">Comparer</span><span class="p">(</span><span class="kd">func</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span> <span class="nx">time</span><span class="p">.</span><span class="nx">Time</span><span class="p">)</span> <span class="kt">bool</span> <span class="p">{</span> + <span class="nx">delta</span> <span class="o">:=</span> <span class="nx">x</span><span class="p">.</span><span class="nf">Sub</span><span class="p">(</span><span class="nx">y</span><span class="p">)</span> + <span class="k">return</span> <span class="nx">delta</span> <span class="p"><</span> <span class="nx">threshold</span> <span class="o">&&</span> <span class="nx">delta</span> <span class="p">></span> <span class="o">-</span><span class="nx">threshold</span> + <span class="p">})</span> + + <span class="k">return</span> <span class="nx">cmp</span><span class="p">.</span><span class="nf">FilterPath</span><span class="p">(</span><span class="nx">metaTimeFields</span><span class="p">,</span> <span class="nx">withinThreshold</span><span class="p">)</span> +<span class="p">}</span></code></pre></div> +<p>I recommend you look at the <a href="https://godoc.org/gotest.tools/assert/opt">gotest.tools/assert/opt</a> documentation to see which one are defined and how to use them.</p> + +<h3 id="errors-with-error-errorcontains-and-errortype">Errors with <code>Error</code>, <code>ErrorContains</code> and <code>ErrorType</code></h3> + +<p>Checking for errors is <strong>very common</strong> in Go, having <code>Comparison</code> function for it was a requirement.</p> + +<ul> +<li><code>Error</code> fails if the error is <code>nil</code> <strong>or</strong> the error message is not the expected one.</li> +<li><code>ErrorContains</code> fails if the error is <code>nil</code> <strong>or</strong> the error message does not contain the expected substring.</li> +<li><code>ErrorType</code> fails if the error is <code>nil</code> <strong>or</strong> the error type is not the expected type.</li> +</ul> + +<p>Let’s first look at the most used : <code>Error</code> and <code>ErrorContains</code>.</p> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="kd">var</span> <span class="nx">err</span> <span class="kt">error</span> +<span class="c1">// will fail with : expected an error, got nil +</span><span class="c1"></span><span class="nx">assert</span><span class="p">.</span><span class="nf">Check</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">cmp</span><span class="p">.</span><span class="nf">Error</span><span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="s">"message in a bottle"</span><span class="p">))</span> +<span class="nx">err</span> <span class="p">=</span> <span class="nx">errors</span><span class="p">.</span><span class="nf">Wrap</span><span class="p">(</span><span class="nx">errors</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">"other"</span><span class="p">),</span> <span class="s">"wrapped"</span><span class="p">)</span> +<span class="c1">// will fail with : expected error "other", got "wrapped: other" +</span><span class="c1"></span><span class="nx">assert</span><span class="p">.</span><span class="nf">Check</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">cmp</span><span class="p">.</span><span class="nf">Error</span><span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="s">"other"</span><span class="p">))</span> +<span class="c1">// will succeed +</span><span class="c1"></span><span class="nx">assert</span><span class="p">.</span><span class="nf">Check</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">cmp</span><span class="p">.</span><span class="nf">ErrorContains</span><span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="s">"other"</span><span class="p">))</span></code></pre></div> +<p>As you can see <code>ErrorContains</code> is especially useful when working with <em>wrapped</em> errors. +Now let’s look at <code>ErrorType</code>.</p> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="kd">var</span> <span class="nx">err</span> <span class="kt">error</span> +<span class="c1">// will fail with : error is nil, not StubError +</span><span class="c1"></span><span class="nx">assert</span><span class="p">.</span><span class="nf">Check</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">cmp</span><span class="p">.</span><span class="nf">ErrorType</span><span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="nx">StubError</span><span class="p">{}))</span> + +<span class="nx">err</span> <span class="o">:=</span> <span class="nx">StubError</span><span class="p">{</span><span class="s">"foo"</span><span class="p">}</span> +<span class="c1">// will succeed +</span><span class="c1"></span><span class="nx">assert</span><span class="p">.</span><span class="nf">Check</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">cmp</span><span class="p">.</span><span class="nf">ErrorType</span><span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="nx">StubError</span><span class="p">{}))</span> + +<span class="c1">// Note that it also work with a function returning an error +</span><span class="c1"></span><span class="kd">func</span> <span class="nf">foo</span><span class="p">()</span> <span class="kt">error</span> <span class="p">{}</span> +<span class="nx">assert</span><span class="p">.</span><span class="nf">Check</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">cmp</span><span class="p">.</span><span class="nf">ErrorType</span><span class="p">(</span><span class="nx">foo</span><span class="p">,</span> <span class="nx">StubError</span><span class="p">{}))</span></code></pre></div> +<h3 id="bonus-with-panics">Bonus with <code>Panics</code></h3> + +<p>Sometimes, a code is supposed to <em>panic</em>, see <a href="https://golang.org/doc/effective%5Fgo.html#panic">Effective Go (#Panic)</a> for more information. And thus, you may want to make +sure you’re code panics in such cases. It’s always a bit tricky to test a code that panic as you have to use a deferred +function to recover the panic — but then if the panic doesn’t happen how do you fail the test ?</p> + +<p>This is where <code>Panics</code> comes handy.</p> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="kd">func</span> <span class="nf">foo</span><span class="p">(</span><span class="nx">shouldPanic</span> <span class="kt">bool</span><span class="p">)</span> <span class="p">{</span> + <span class="k">if</span> <span class="nx">shouldPanic</span> <span class="p">{</span> + <span class="nb">panic</span><span class="p">(</span><span class="s">"booooooooooh"</span><span class="p">)</span> + <span class="p">}</span> + <span class="c1">// don't worry, be happy +</span><span class="c1"></span><span class="p">}</span> +<span class="c1">// will fail with : did not panic +</span><span class="c1"></span><span class="nx">assert</span><span class="p">.</span><span class="nf">Check</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">cmp</span><span class="p">.</span><span class="nf">Panics</span><span class="p">(</span><span class="nf">foo</span><span class="p">(</span><span class="kc">false</span><span class="p">)))</span> +<span class="c1">// will succeed +</span><span class="c1"></span><span class="nx">assert</span><span class="p">.</span><span class="nf">Check</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">cmp</span><span class="p">.</span><span class="nf">Panics</span><span class="p">(</span><span class="nf">foo</span><span class="p">(</span><span class="kc">true</span><span class="p">)))</span></code></pre></div> +<h3 id="miscellaneous-with-contains-len-and-nil">Miscellaneous with <code>Contains</code>, <code>Len</code> and <code>Nil</code></h3> + +<p>Those last three <em>built-in</em> <code>Comparison</code> are pretty straightforward.</p> + +<ul> +<li><p><code>Contains</code> succeeds if item is in collection. Collection may be a string, map, slice, or array.</p> + +<p>If collection is a string, item must also be a string, and is compared using <code>strings.Contains()</code>. If collection is a +Map, contains will succeed if item is a key in the map. If collection is a slice or array, item is compared to each +item in the sequence using <code>=reflect.DeepEqual()=</code>.</p></li> + +<li><p><code>Len</code> succeeds if the sequence has the expected length.</p></li> + +<li><p><code>Nil</code> succeeds if obj is a nil interface, pointer, or function.</p></li> +</ul> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="c1">// Contains works on string, map, slice or arrays +</span><span class="c1"></span><span class="nx">assert</span><span class="p">.</span><span class="nf">Check</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">cmp</span><span class="p">.</span><span class="nf">Contains</span><span class="p">(</span><span class="s">"foobar"</span><span class="p">,</span> <span class="s">"foo"</span><span class="p">))</span> +<span class="nx">assert</span><span class="p">.</span><span class="nf">Check</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">cmp</span><span class="p">.</span><span class="nf">Contains</span><span class="p">([]</span><span class="kt">string</span><span class="p">{</span><span class="s">"a"</span><span class="p">,</span> <span class="s">"b"</span><span class="p">,</span> <span class="s">"c"</span><span class="p">},</span> <span class="s">"b"</span><span class="p">))</span> +<span class="nx">assert</span><span class="p">.</span><span class="nf">Check</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">cmp</span><span class="p">.</span><span class="nf">Contains</span><span class="p">(</span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">int</span><span class="p">{</span><span class="s">"a"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="s">"b"</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="s">"c"</span><span class="p">:</span> <span class="mi">4</span><span class="p">},</span> <span class="s">"b"</span><span class="p">))</span> + +<span class="c1">// Len also works on string, map, slice or arrays +</span><span class="c1"></span><span class="nx">assert</span><span class="p">.</span><span class="nf">Check</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">cmp</span><span class="p">.</span><span class="nf">Len</span><span class="p">(</span><span class="s">"foobar"</span><span class="p">,</span> <span class="mi">6</span><span class="p">))</span> +<span class="nx">assert</span><span class="p">.</span><span class="nf">Check</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">cmp</span><span class="p">.</span><span class="nf">Len</span><span class="p">([]</span><span class="kt">string</span><span class="p">{</span><span class="s">"a"</span><span class="p">,</span> <span class="s">"b"</span><span class="p">,</span> <span class="s">"c"</span><span class="p">},</span> <span class="mi">3</span><span class="p">))</span> +<span class="nx">assert</span><span class="p">.</span><span class="nf">Check</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">cmp</span><span class="p">.</span><span class="nf">Len</span><span class="p">(</span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">int</span><span class="p">{</span><span class="s">"a"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="s">"b"</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="s">"c"</span><span class="p">:</span> <span class="mi">4</span><span class="p">},</span> <span class="mi">3</span><span class="p">))</span> + +<span class="c1">// Nil +</span><span class="c1"></span><span class="kd">var</span> <span class="nx">foo</span> <span class="o">*</span><span class="nx">MyStruc</span> +<span class="nx">assert</span><span class="p">.</span><span class="nf">Check</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">cmp</span><span class="p">.</span><span class="nf">Nil</span><span class="p">(</span><span class="nx">foo</span><span class="p">))</span> +<span class="nx">assert</span><span class="p">.</span><span class="nf">Check</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">cmp</span><span class="p">.</span><span class="nf">Nil</span><span class="p">(</span><span class="nf">bar</span><span class="p">()))</span></code></pre></div> +<p>But let’s not waste more time and let’s see how to write our own <code>Comparison</code> !</p> + +<h3 id="write-your-own-comparison">Write your own <code>Comparison</code></h3> + +<p>One of the main aspect of <code>gotest.tools/assert</code> is to make it easy for developer to write as less boilerplate code as +possible while writing tests. Writing your own <code>Comparison</code> allows you to write a well named function that will be easy +to read and that can be re-used across your tests.</p> + +<p>Let’s look back at the <code>cmp.Comparison</code> and <code>cmp.Result</code> types.</p> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="kd">type</span> <span class="nx">Comparison</span> <span class="kd">func</span><span class="p">()</span> <span class="nx">Result</span> + +<span class="kd">type</span> <span class="nx">Result</span> <span class="kd">interface</span> <span class="p">{</span> + <span class="nf">Success</span><span class="p">()</span> <span class="kt">bool</span> +<span class="p">}</span></code></pre></div> +<p>A <code>Comparison</code> for <code>assert.Check</code> or <code>assert.Check</code> is a function that return a <code>Result</code>, it’s pretty straightforward to +implement, especially with <code>cmp.ResultSuccess</code> and <code>cmp.ResultFailure(…)</code> (as seen previously).</p> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="kd">func</span> <span class="nf">regexPattern</span><span class="p">(</span><span class="nx">value</span> <span class="kt">string</span><span class="p">,</span> <span class="nx">pattern</span> <span class="kt">string</span><span class="p">)</span> <span class="nx">cmp</span><span class="p">.</span><span class="nx">Comparison</span> <span class="p">{</span> + <span class="k">return</span> <span class="kd">func</span><span class="p">()</span> <span class="nx">cmp</span><span class="p">.</span><span class="nx">Result</span> <span class="p">{</span> + <span class="nx">re</span> <span class="o">:=</span> <span class="nx">regexp</span><span class="p">.</span><span class="nf">MustCompile</span><span class="p">(</span><span class="nx">pattern</span><span class="p">)</span> + <span class="k">if</span> <span class="nx">re</span><span class="p">.</span><span class="nf">MatchString</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span> + <span class="k">return</span> <span class="nx">cmp</span><span class="p">.</span><span class="nx">ResultSuccess</span> + <span class="p">}</span> + <span class="k">return</span> <span class="nx">cmp</span><span class="p">.</span><span class="nf">ResultFailure</span><span class="p">(</span> + <span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprintf</span><span class="p">(</span><span class="s">"%q did not match pattern %q"</span><span class="p">,</span> <span class="nx">value</span><span class="p">,</span> <span class="nx">pattern</span><span class="p">))</span> + <span class="p">}</span> +<span class="p">}</span> + +<span class="c1">// To use it +</span><span class="c1"></span><span class="nx">assert</span><span class="p">.</span><span class="nf">Check</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nf">regexPattern</span><span class="p">(</span><span class="s">"12345.34"</span><span class="p">,</span> <span class="s">`\d+.\d\d`</span><span class="p">))</span></code></pre></div> +<p>As you can see, it’s pretty easy to implement, and you can do quite a lot in there easily. If a function call returns an +error inside of your <code>Comparison</code> function, you can use <code>cmp.ResultFromError</code> for example. Having something like +<code>assert.Check(t, isMyServerUp(":8080"))</code> is way more readable than a 30-line of code to check it.</p> + +<h2 id="conclusion">Conclusion…</h2> + +<p>… and that’s a wrap. We only looked at the <code>assert</code> package of <a href="https://gotest.tools"><code>gotest.tools</code></a> so far, but it’s already quite a bit to process.</p> + +<p>We’ve seen :</p> + +<ul> +<li>the main functions provided by this package : <code>assert.Assert</code> and <code>assert.Check</code></li> +<li>some helper functions like <code>assert.NilError</code>, …</li> +<li>the <code>assert/cmp</code>, and <code>assert/opt</code> sub-package that allows you to write more custom <code>Comparison</code></li> +</ul> + +<p>Next time, we’ll look at the <code>skip</code> package, that is a really simple wrapper on top of <code>testing.Skip</code> function.</p> + + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2018-09-01-gotest-tools-skip/" title="Golang testing — gotest.tools skip">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2018-07-28-gotest-tools-intro/" title="Golang testing — gotest.tools introduction">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2018-09-01-gotest-tools-skip/index.html b/www/vincent.demeester.fr/legacy/posts/2018-09-01-gotest-tools-skip/index.html @@ -0,0 +1,169 @@ +<!DOCTYPE html> + +<html lang="en"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2018-09-01-gotest-tools-skip/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang=""/> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">Golang testing — gotest.tools skip</h1><a href='https://vincent.demeester.fr/posts/2018-09-01-gotest-tools-skip/'></a> + <address class="signature"> + <span class="date">Sat, 1 September, 2018</span> + <span class="words">(400 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#developement">developement</a></li> + + + + + + <li class="tag tag-testing"><a href="/tags/#testing">testing<span>11</span></a></li> + + + <li class="tag tag-golang"><a href="/tags/#golang">golang<span>12</span></a></li> + + + <li class="tag tag-skip"><a href="/tags/#skip">skip<span>1</span></a></li> + + <br/> + + </ul> + </header> + + + + <p>Let’s continue the <a href="https://gotest.tools"><code>gotest.tools</code></a> serie, this time with the <code>skip</code> package. This is a +really simple one so this should be quick.</p> + +<blockquote> +<p><code>skip</code> provides functions for skipping a test and printing the source code of the +condition used to skip the test.</p> +</blockquote> + +<p>The package consists of only one function : <code>If</code>. The idea comes mainly from +<a href="https://github.com/docker/docker"><code>docker/docker</code></a> integration test suite, where we wanted to skip some test (or test suites) +given different context. By context I mean things like the system we are running on +(<code>Windows</code>, <code>Linux</code>, …) or the capabilities of the running kernel or node (is <code>apparmor</code> +available or not on the current machine).</p> + +<p>This <code>If</code> method takes a <code>testing.T</code> pointer and either a boolean, a function that +returns a boolean, <strong>or</strong> an expression.</p> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="c1">// boolean +</span><span class="c1">// --- SKIP: TestName (0.00s) +</span><span class="c1">// skip.go:19: MissingFeature +</span><span class="c1"></span><span class="kd">var</span> <span class="nx">MissingFeature</span> <span class="kt">bool</span> +<span class="nx">skip</span><span class="p">.</span><span class="nf">If</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">MissingFeature</span><span class="p">)</span> + +<span class="c1">// function +</span><span class="c1">// --- SKIP: TestName (0.00s) +</span><span class="c1">// skip.go:19: !IsExperimentalDaemon(dockerClient): daemon is not experimental +</span><span class="c1"></span><span class="nx">skip</span><span class="p">.</span><span class="nf">If</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nf">IsExperimentalDaemon</span><span class="p">(</span><span class="nx">dockerClient</span><span class="p">),</span> <span class="s">"daemon is not experimental"</span><span class="p">)</span> + +<span class="c1">// expression +</span><span class="c1">// --- SKIP: TestName (0.00s) +</span><span class="c1">// skip.go:19: apiVersion < version("v1.24") +</span><span class="c1"></span><span class="nx">skip</span><span class="p">.</span><span class="nf">If</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">apiVersion</span> <span class="p"><</span> <span class="nf">version</span><span class="p">(</span><span class="s">"v1.24"</span><span class="p">))</span></code></pre></div> +<p>There is few elements to note though :</p> + +<ul> +<li>This package (as other parts of the <code>gotest.tools</code> packages), will try to look at source +files to display the expression used (same goes for <code>assert</code>). This is usually not a +problem because you run tests where the source code is. <strong>However</strong>, in the cases you +generate a test binary to be executed later (à-la <code>kubernetes</code> or other projects), this +can display a weird error message if the sources are not available… You shouldn’t be +worried too much about it, but it’s better if you know :)</li> +<li>The main reason to use <code>skip.If</code> is mainly for new contributors to get in quickly, +<strong>reducing potential friction of them running the tests on their environment</strong>. The more +the tests are written in a way they explicitely declare their requirements (and skipped +if the environment does not meet those), the easier it makes contributors run your +tests. <strong>But</strong>, this also means, you should try to measure the skipped tests on your +continuous integration system to make sure you run all of them eventually… otherwise +it’s dead code. <em>But more on that in later posts 😉</em>.</li> +</ul> + +<p>That’s all for today folks, told you that was going to be quick.</p> + + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2018-09-06-gotest-tools-golden/" title="Golang testing — gotest.tools golden">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2018-08-16-gotest-tools-assertions/" title="Golang testing — gotest.tools assertions">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2018-09-06-gotest-tools-golden/index.html b/www/vincent.demeester.fr/legacy/posts/2018-09-06-gotest-tools-golden/index.html @@ -0,0 +1,182 @@ +<!DOCTYPE html> + +<html lang="en"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2018-09-06-gotest-tools-golden/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang=""/> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">Golang testing — gotest.tools golden</h1><a href='https://vincent.demeester.fr/posts/2018-09-06-gotest-tools-golden/'></a> + <address class="signature"> + <span class="date">Thu, 6 September, 2018</span> + <span class="words">(500 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#developement">developement</a></li> + + + + + + <li class="tag tag-testing"><a href="/tags/#testing">testing<span>11</span></a></li> + + + <li class="tag tag-golang"><a href="/tags/#golang">golang<span>12</span></a></li> + + <br/> + + </ul> + </header> + + + + + +<p>Let’s continue the <a href="https://gotest.tools"><code>gotest.tools</code></a> serie, this time with the <code>golden</code> package. This is a +<a href="/posts/2017-04-22-golang-testing-golden-file/"><em>quick follow-up</em> on a previous <code>golden</code> post</a>, but focused on the <code>gotest.tools</code> +implementation. I’m gonna be quicker, please read that one if <code>golden</code> files is a new +concept for you.</p> + +<blockquote> +<p>Package <code>golden</code> provides tools for comparing large mutli-line strings.</p> + +<p>Golden files are files in the <code>./testdata/</code> sub-directory of the package under test.</p> +</blockquote> + +<p>In the previous article, we described the problem, and how to fix it by writing a small +helper. Well, that small helper is in <code>gotest.tools/golden</code> now, and it has a tiny bit +more features.</p> + +<p>One of the difference between the <code>gotest.tools</code> implementation and the previous post is +the flag name. In <code>gotest.tools/golden</code>, the flag is <code>-test.update-golden</code> (was just +<code>-test.update</code> before). Just as before, if the <code>-test.update-golden</code> flag is set then the +actual content is written to the golden file, before reading it and comparing.</p> + +<p>There is two ways to use the <code>golden</code> package:</p> + +<ul> +<li>on it’s own, using <code>golden.Assert</code> or <code>golden.AssertBytes</code></li> +<li>as a <code>cmp.Comparison</code>, with <code>golden.String</code> or <code>golden.Bytes</code></li> +</ul> + +<h2 id="assert-and-assertbytes"><code>Assert</code> and <code>AssertBytes</code></h2> + +<p>Using <code>Assert</code> functions should be straightforward. Both <code>Assert</code> function compares the +actual content to the expected content in the golden file and returns whether the +assertion was successful (true) or not (false).</p> + +<ul> +<li><code>Assert</code> uses string. Note that this one <strong>removes carriage return</strong> before comparing to +depend as less as possible of the system (<code>\n</code> vs <code>\r\n</code> 😅)</li> +<li><code>AssertBytes</code> uses raw data (in the form of <code>[]byte</code>)</li> +</ul> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="nx">golden</span><span class="p">.</span><span class="nf">Assert</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="s">"foo"</span><span class="p">,</span> <span class="s">"foo-content.golden"</span><span class="p">)</span> +<span class="c1">// Could also be used to check some binary format +</span><span class="c1"></span><span class="nx">golden</span><span class="p">.</span><span class="nf">AssertBytes</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="p">[]</span><span class="nb">byte</span><span class="p">(</span><span class="s">"foo"</span><span class="p">),</span> <span class="s">"foo-content.golden"</span><span class="p">)</span></code></pre></div> +<h2 id="bytes-and-string"><code>Bytes</code> and <code>String</code></h2> + +<p>As written in a <a href="/posts/2018-08-16-gotest-tools-assertions/">previous post (about the <code>assert</code> package)</a>, I prefer to use <code>cmp.Comparison</code>.</p> + +<blockquote> +<p>All those helper functions have a equivalent function in the <code>cmp</code> package that returns a +<code>Comparison</code>. I, personally, prefer to use <code>assert.Check</code> or <code>assert.Assert</code> in +combination with <code>cmp.Comparison</code> as it allows me to write all my assertions the same way, +with built-ins comparison or with my own — i.e. <code>assert.Assert(t, is.Equal(…), "message"</code> +or <code>assert.Assert(t, stackIsUp(c, time…), "another message")</code>.</p> +</blockquote> + +<p>The <code>golden</code> package gives us that too, in the form of <code>Bytes</code> and <code>String</code>. Using the +<code>assert.Check</code> or <code>assert.Assert</code> functions with those is equivalent to their <em>helper</em> +counter-part <code>golden.Assert</code> and <code>golden.AssertBytes</code>.</p> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="nx">assert</span><span class="p">.</span><span class="nf">Assert</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">golden</span><span class="p">.</span><span class="nf">String</span><span class="p">(</span><span class="s">"foo"</span><span class="p">,</span> <span class="s">"foo-content.golden"</span><span class="p">))</span> +<span class="c1">// Could also be used to check some binary format +</span><span class="c1"></span><span class="nx">assert</span><span class="p">.</span><span class="nf">Assert</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">golden</span><span class="p">.</span><span class="nf">Bytes</span><span class="p">([]</span><span class="nb">byte</span><span class="p">(</span><span class="s">"foo"</span><span class="p">),</span> <span class="s">"foo-content.golden"</span><span class="p">))</span></code></pre></div> +<h2 id="conclusion">Conclusion…</h2> + +<p>… that’s a wrap. As for <a href="/posts/2018-09-01-gotest-tools-skip/"><code>skip</code></a>, this is a small package, so the post was going to be +quick. <code>golden</code> package just solve a specific problem (read <a href="/posts/2017-04-22-golang-testing-golden-file/">Golang testing — golden file</a>) +in a simple way.</p> + + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2018-09-14-gotest-tools-fs/" title="Golang testing — gotest.tools fs">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2018-09-01-gotest-tools-skip/" title="Golang testing — gotest.tools skip">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2018-09-14-gotest-tools-fs/index.html b/www/vincent.demeester.fr/legacy/posts/2018-09-14-gotest-tools-fs/index.html @@ -0,0 +1,279 @@ +<!DOCTYPE html> + +<html lang="en"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2018-09-14-gotest-tools-fs/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang=""/> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">Golang testing — gotest.tools fs</h1><a href='https://vincent.demeester.fr/posts/2018-09-14-gotest-tools-fs/'></a> + <address class="signature"> + <span class="date">Fri, 14 September, 2018</span> + <span class="words">(1100 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#developement">developement</a></li> + + + + + + <li class="tag tag-testing"><a href="/tags/#testing">testing<span>11</span></a></li> + + + <li class="tag tag-golang"><a href="/tags/#golang">golang<span>12</span></a></li> + + + <li class="tag tag-filesystem"><a href="/tags/#filesystem">filesystem<span>1</span></a></li> + + <br/> + + </ul> + </header> + + + + + +<p>Let’s continue the <a href="https://gotest.tools"><code>gotest.tools</code></a> serie, this time with the <code>fs</code> package.</p> + +<blockquote> +<p>Package fs provides tools for creating temporary files, and testing the contents and structure of a directory.</p> +</blockquote> + +<p>This package is heavily using functional arguments (as we saw in <a href="/posts/2017-01-01-go-testing-functionnal-builders/">functional arguments for +wonderful builders</a>). Functional arguments is, in a nutshell, a combinaison of two Go +features : <em>variadic</em> functions (<code>...</code> operation in a function signature) and the fact +that <code>func</code> are <em>first class citizen</em>. This looks more or less like that.</p> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="kd">type</span> <span class="nx">Config</span> <span class="kd">struct</span> <span class="p">{}</span> + +<span class="kd">func</span> <span class="nf">MyFn</span><span class="p">(</span><span class="nx">ops</span> <span class="o">...</span><span class="kd">func</span><span class="p">(</span><span class="o">*</span><span class="nx">Config</span><span class="p">))</span> <span class="o">*</span><span class="nx">Config</span> <span class="p">{</span> + <span class="nx">c</span> <span class="o">:=</span> <span class="o">&</span><span class="nx">Config</span><span class="p">{}</span> <span class="c1">// with default values +</span><span class="c1"></span> <span class="k">for</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">op</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">ops</span> <span class="p">{</span> + <span class="nf">op</span><span class="p">(</span><span class="nx">c</span><span class="p">)</span> + <span class="p">}</span> + <span class="k">return</span> <span class="nx">c</span> +<span class="p">}</span> + +<span class="c1">// Calling it +</span><span class="c1"></span><span class="nx">conf</span> <span class="o">:=</span> <span class="nf">MyFn</span><span class="p">(</span><span class="nx">withFoo</span><span class="p">,</span> <span class="nf">withBar</span><span class="p">(</span><span class="s">"baz"</span><span class="p">))</span></code></pre></div> +<p>The <code>fs</code> package has too <strong>main</strong> purpose :</p> + +<ol> +<li>create folders and files required for testing in a simple manner</li> +<li>compare two folders structure (and content)</li> +</ol> + +<h2 id="create-folder-structures">Create folder structures</h2> + +<p>Sometimes, you need to create folder structures (and files) in tests. Doing <code>i/o</code> work +takes time so try to limit the number of tests that needs to do that, especially in unit +tests. Doing it in tests adds a bit of boilerplate that could be avoid. As stated <a href="/posts/2017-01-01-go-testing-functionnal-builders/">before</a> :</p> + +<blockquote> +<p>One of the most important characteristic of a unit test (and any type of test really) is +<strong>readability</strong>. This means it should be easy to read but most importantly it should <strong>clearly +show the intent</strong> of the test. The setup (and cleanup) of the tests should be as small as +possible to avoid the noise.</p> +</blockquote> + +<p>In a test you usually end up using <code>ioutil</code> function to create what you need. This looks +somewhat like the following.</p> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="nx">path</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">ioutil</span><span class="p">.</span><span class="nf">TempDir</span><span class="p">(</span><span class="s">""</span><span class="p">,</span> <span class="s">"bar"</span><span class="p">)</span> +<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span> <span class="c1">// or using `assert.Assert` +</span><span class="c1"></span> <span class="nx">t</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span> +<span class="p">}</span> +<span class="k">if</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">os</span><span class="p">.</span><span class="nf">Mkdir</span><span class="p">(</span><span class="nx">filepath</span><span class="p">.</span><span class="nf">Join</span><span class="p">(</span><span class="nx">path</span><span class="p">,</span> <span class="s">"foo"</span><span class="p">),</span> <span class="nx">os</span><span class="p">.</span><span class="nf">FileMode</span><span class="p">(</span><span class="mo">0755</span><span class="p">));</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span> + <span class="nx">t</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span> +<span class="p">}</span> +<span class="k">if</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">ioutil</span><span class="p">.</span><span class="nf">WriteFile</span><span class="p">(</span><span class="nx">filepath</span><span class="p">.</span><span class="nf">Join</span><span class="p">(</span><span class="nx">path</span><span class="p">,</span> <span class="s">"foo"</span><span class="p">,</span> <span class="s">"bar"</span><span class="p">),</span> <span class="p">[]</span><span class="nb">byte</span><span class="p">(</span><span class="s">"content"</span><span class="p">),</span> <span class="nx">os</span><span class="p">.</span><span class="nf">FileMode</span><span class="p">(</span><span class="mo">0777</span><span class="p">));</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span> + <span class="nx">t</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span> +<span class="p">}</span> +<span class="k">defer</span> <span class="nx">os</span><span class="p">.</span><span class="nf">RemoveAll</span><span class="p">(</span><span class="nx">path</span><span class="p">)</span> <span class="o">//</span> <span class="nx">to</span> <span class="nx">clean</span> <span class="nx">up</span> <span class="nx">at</span> <span class="nx">the</span> <span class="nx">end</span> <span class="nx">of</span> <span class="nx">the</span> <span class="nx">test</span></code></pre></div> +<p>The <code>fs</code> package intends to help reduce the noise and comes with a bunch function to create +folder structure :</p> + +<ul> +<li>two main function <code>NewFile</code> and <code>NewDir</code></li> +<li>a bunch of <em>operators</em> : <code>WithFile</code>, <code>WithDir</code>, …</li> +</ul> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="kd">func</span> <span class="nf">NewDir</span><span class="p">(</span><span class="nx">t</span> <span class="nx">assert</span><span class="p">.</span><span class="nx">TestingT</span><span class="p">,</span> <span class="nx">prefix</span> <span class="kt">string</span><span class="p">,</span> <span class="nx">ops</span> <span class="o">...</span><span class="nx">PathOp</span><span class="p">)</span> <span class="o">*</span><span class="nx">Dir</span> <span class="p">{</span> + <span class="c1">// … +</span><span class="c1"></span><span class="p">}</span> + +<span class="kd">func</span> <span class="nf">NewFile</span><span class="p">(</span><span class="nx">t</span> <span class="nx">assert</span><span class="p">.</span><span class="nx">TestingT</span><span class="p">,</span> <span class="nx">prefix</span> <span class="kt">string</span><span class="p">,</span> <span class="nx">ops</span> <span class="o">...</span><span class="nx">PathOp</span><span class="p">)</span> <span class="o">*</span><span class="nx">File</span> <span class="p">{</span> + <span class="c1">// … +</span><span class="c1"></span><span class="p">}</span></code></pre></div> +<p>The <code>With*</code> function are all satisfying the <code>PathOp</code> interface, making <code>NewFile</code> and +<code>NewDir</code> extremely composable. Let’s first see how our above example would look like using +the <code>fs</code> package, and then, we’ll look a bit more at the main <code>PathOp</code> function…</p> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="nx">dir</span> <span class="o">:=</span> <span class="nx">fs</span><span class="p">.</span><span class="nf">NewDir</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="s">"bar"</span><span class="p">,</span> <span class="nx">fs</span><span class="p">.</span><span class="nf">WithDir</span><span class="p">(</span><span class="s">"foo"</span><span class="p">,</span> + <span class="nx">fs</span><span class="p">.</span><span class="nf">WithFile</span><span class="p">(</span><span class="s">"bar"</span><span class="p">,</span> <span class="nx">fs</span><span class="p">.</span><span class="nf">WithContent</span><span class="p">(</span><span class="s">"content"</span><span class="p">),</span> <span class="nx">fs</span><span class="p">.</span><span class="nf">WithMode</span><span class="p">(</span><span class="nx">os</span><span class="p">.</span><span class="nf">FileMode</span><span class="p">(</span><span class="mo">0777</span><span class="p">))),</span> +<span class="p">))</span> +<span class="k">defer</span> <span class="nx">dir</span><span class="p">.</span><span class="nf">Remove</span><span class="p">()</span></code></pre></div> +<p>It’s clean and simple to read. The intent is well described and there is not that much of +noise. <code>fs</code> functions tends to have <em>sane</em> and <em>safe</em> defaults value (for <code>os.FileMode</code> +for example). Let’s list the main, useful, <code>PathOp</code> provided by <code>gotest.tools/fs</code>.</p> + +<ul> +<li><code>WithDir</code> creates a sub-directory in the directory at path.</li> +<li><code>WithFile</code> creates a file in the directory at path with content.</li> +<li><code>WithSymlink</code> creates a symlink in the directory which links to target. Target must be a +path relative to the directory.</li> +<li><code>WithHardlink</code> creates a link in the directory which links to target. Target must be a +path relative to the directory.</li> +<li><code>WithContent</code> and <code>WWithBytes</code> write content to a file at Path (from a <code>string</code> or a +<code>[]byte</code> slice).</li> +<li><code>WithMode</code> sets the file mode on the directory or file at path.</li> +<li><code>WithTimestamps</code> sets the access and modification times of the file system object at +path.</li> +<li><code>FromDir</code> copies the directory tree from the source path into the new Dir. This is +pretty useful when you have a huge folder structure already present in you <code>testdata</code> +folder or elsewhere.</li> +<li><code>AsUser</code> changes ownership of the file system object at Path.</li> +</ul> + +<p>Also, note that <code>PathOp</code> being an function type, you can provide your own implementation +for specific use-cases. Your function just has to satisfy <code>PathOp</code> signature.</p> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="kd">type</span> <span class="nx">PathOp</span> <span class="kd">func</span><span class="p">(</span><span class="nx">path</span> <span class="nx">Path</span><span class="p">)</span> <span class="kt">error</span></code></pre></div> +<h2 id="compare-folder-structures">Compare folder structures</h2> + +<p>Sometimes, the code you’re testing is creating a folder structure, and you would like to +be able to tests that, with the given arguments, it creates the specified structure. <code>fs</code> +allows you to do that too.</p> + +<p>The package provides a <code>Equal</code> function, which returns a <code>Comparison</code>, that the <a href="/posts/2018-08-16-gotest-tools-assertions/"><code>assert</code></a> +package understand. It works by comparing a <code>Manifest</code> type provided by the test and a +<code>Manifest</code> representation of the specified folder.</p> + +<blockquote> +<p>Equal compares a directory to the expected structured described by a manifest and returns +success if they match. If they do not match the failure message will contain all the +differences between the directory structure and the expected structure defined by the +Manifest.</p> +</blockquote> + +<p>A <code>Manifest</code> stores the expected structure and properties of files and directories in a +file-system. You can create a <code>Manifest</code> using either the functions <code>Expected</code> or +<code>ManifestFromDir</code>.</p> + +<p>We’re going to focus on the <code>Expected</code> function, as <code>ManifestFromDir</code> does pretty much +what you would expected : it takes the specified path, and returns a <code>Manifest</code> that +represent this folder.</p> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="kd">func</span> <span class="nf">Expected</span><span class="p">(</span><span class="nx">t</span> <span class="nx">assert</span><span class="p">.</span><span class="nx">TestingT</span><span class="p">,</span> <span class="nx">ops</span> <span class="o">...</span><span class="nx">PathOp</span><span class="p">)</span> <span class="nx">Manifest</span></code></pre></div> +<p><code>Expected</code> is close to <code>NewDir</code> function : it takes the same <code>PathOp</code> functional +arguments. This makes creating a <code>Manifest</code> straightforward, as it’s working the same. Any +function that satisfy <code>PathOp</code> can be used for <code>Manifest</code> the exact same way you’re using +them on <code>fs.NewDir</code>.</p> + +<p>There is a few additional functions that are only useful with <code>Manifest</code> :</p> + +<ul> +<li><code>MatchAnyFileContent</code> updates a Manifest so that the file at path may contain any content.</li> +<li><code>MatchAnyFileMode</code> updates a Manifest so that the resource at path will match any file mode.</li> +<li><code>MatchContentIgnoreCarriageReturn</code> ignores cariage return discrepancies.</li> +<li><code>MatchExtraFiles</code> updates a Manifest to allow a directory to contain unspecified files.</li> +</ul> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="nx">path</span> <span class="o">:=</span> <span class="nf">operationWhichCreatesFiles</span><span class="p">()</span> +<span class="nx">expected</span> <span class="o">:=</span> <span class="nx">fs</span><span class="p">.</span><span class="nf">Expected</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> + <span class="nx">fs</span><span class="p">.</span><span class="nf">WithFile</span><span class="p">(</span><span class="s">"one"</span><span class="p">,</span> <span class="s">""</span><span class="p">,</span> + <span class="nx">fs</span><span class="p">.</span><span class="nf">WithBytes</span><span class="p">(</span><span class="nx">golden</span><span class="p">.</span><span class="nf">Get</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="s">"one.golden"</span><span class="p">)),</span> + <span class="nx">fs</span><span class="p">.</span><span class="nf">WithMode</span><span class="p">(</span><span class="mo">0600</span><span class="p">)),</span> + <span class="nx">fs</span><span class="p">.</span><span class="nf">WithDir</span><span class="p">(</span><span class="s">"data"</span><span class="p">,</span> + <span class="nx">fs</span><span class="p">.</span><span class="nf">WithFile</span><span class="p">(</span><span class="s">"config"</span><span class="p">,</span> <span class="s">""</span><span class="p">,</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">MatchAnyFileContent</span><span class="p">)),</span> +<span class="p">)</span> + +<span class="nx">assert</span><span class="p">.</span><span class="nf">Assert</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">fs</span><span class="p">.</span><span class="nf">Equal</span><span class="p">(</span><span class="nx">path</span><span class="p">,</span> <span class="nx">expected</span><span class="p">))</span></code></pre></div> +<p>The following example compares the result of <code>operationWhichCreatesFiles</code> to the expected +<code>Manifest</code>. As you can see it also integrates well with other part of the <code>gotest.tools</code> +library, with the <a href="/posts/2018-09-06-gotest-tools-golden/"><code>golden</code> package</a> in this example.</p> + +<h2 id="conclusion">Conclusion…</h2> + +<p>… that’s a wrap. In my opinion, this is one the most useful package provided by +<code>gotest.tools</code> after <code>assert</code>. It allows to create simple or complex folder structure +without the noise that usually comes with it.</p> + + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2018-09-18-gotest-tools-icmd/" title="Golang testing — gotest.tools icmd">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2018-09-06-gotest-tools-golden/" title="Golang testing — gotest.tools golden">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2018-09-18-gotest-tools-icmd/index.html b/www/vincent.demeester.fr/legacy/posts/2018-09-18-gotest-tools-icmd/index.html @@ -0,0 +1,304 @@ +<!DOCTYPE html> + +<html lang="en"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2018-09-18-gotest-tools-icmd/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang=""/> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">Golang testing — gotest.tools icmd</h1><a href='https://vincent.demeester.fr/posts/2018-09-18-gotest-tools-icmd/'></a> + <address class="signature"> + <span class="date">Tue, 18 September, 2018</span> + <span class="words">(1100 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#developement">developement</a></li> + + + + + + <li class="tag tag-testing"><a href="/tags/#testing">testing<span>11</span></a></li> + + + <li class="tag tag-golang"><a href="/tags/#golang">golang<span>12</span></a></li> + + + <li class="tag tag-exec"><a href="/tags/#exec">exec<span>1</span></a></li> + + + <li class="tag tag-command"><a href="/tags/#command">command<span>1</span></a></li> + + <br/> + + </ul> + </header> + + + + + +<p>Let’s continue the <a href="https://gotest.tools"><code>gotest.tools</code></a> serie, this time with the <code>icmd</code> package.</p> + +<blockquote> +<p>Package icmd executes binaries and provides convenient assertions for testing the results.</p> +</blockquote> + +<p>After file-system operations (seen in <a href="/posts/2018-09-14-gotest-tools-fs/"><code>fs</code></a>), another common use-case in tests is to +<strong>execute a command</strong>. The reasons can be you’re testing the <code>cli</code> you’re currently writing +or you need to setup something using a command line. A classic execution in a test might +lookup like the following.</p> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="nx">cmd</span> <span class="o">:=</span> <span class="nx">exec</span><span class="p">.</span><span class="nf">Command</span><span class="p">(</span><span class="s">"echo"</span><span class="p">,</span> <span class="s">"foo"</span><span class="p">)</span> +<span class="nx">cmd</span><span class="p">.</span><span class="nx">Stout</span> <span class="p">=</span> <span class="o">&</span><span class="nx">stdout</span> +<span class="nx">cmd</span><span class="p">.</span><span class="nx">Env</span> <span class="p">=</span> <span class="nx">env</span> +<span class="k">if</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">cmd</span><span class="p">.</span><span class="nf">Run</span><span class="p">();</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span> + <span class="nx">t</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span> +<span class="p">}</span> +<span class="k">if</span> <span class="nb">string</span><span class="p">(</span><span class="nx">stdout</span><span class="p">)</span> <span class="o">!=</span> <span class="s">"foo"</span> <span class="p">{</span> + <span class="nx">t</span><span class="p">.</span><span class="nf">Fatalf</span><span class="p">(</span><span class="s">"expected: foo, got %s"</span><span class="p">,</span> <span class="nb">string</span><span class="p">(</span><span class="nx">stdout</span><span class="p">))</span> +<span class="p">}</span></code></pre></div> +<p>The package <code>icmd</code> is there to ease your pain (as usual 😉) — we used <em>the name <code>icmd</code></em> +instead of <code>cmd</code> because it’s a pretty common identifier in Go source code, thus would be +really easy to <em>shadow</em> — and have some really weird problems going on.</p> + +<p>The usual <code>icmd</code> workflow is the following:</p> + +<ol> +<li>Describe the command you want to execute using : type <code>Cmd</code>, function <code>Command</code> and +<code>CmdOp</code> operators.</li> +<li>Run it using : function <code>RunCmd</code> or <code>RunCommand</code> (that does 1. for you). You can also +use <code>StartCmd</code> and <code>WaitOnCmd</code> if you want more control on the execution workflow.</li> +<li>Check the result using the <code>Assert</code>, <code>Equal</code> or <code>Compare</code> methods attached to the +<code>Result</code> struct that the command execution return.</li> +</ol> + +<h2 id="create-and-run-a-command">Create and run a command</h2> + +<p>Let’s first dig how to create commands. In this part, the assumption here is that the +command is successful, so we’ll have <code>.Assert(t, icmd.Success)</code> for now — we’ll learn more +about <code>Assert</code> in the next section 👼.</p> + +<p>The simplest way to create and run a command is using <code>RunCommand</code>, it has the same +signature as <code>os/exec.Command</code>. A simple command execution goes as below.</p> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="nx">icmd</span><span class="p">.</span><span class="nf">RunCommand</span><span class="p">(</span><span class="s">"echo"</span><span class="p">,</span> <span class="s">"foo"</span><span class="p">).</span><span class="nf">Assert</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">icmd</span><span class="p">.</span><span class="nx">Sucess</span><span class="p">)</span></code></pre></div> +<p>Sometimes, you need to customize the command a bit more, like adding some environment +variable. In those case, you are going to use <code>RunCmd</code>, it takes a <code>Cmd</code> and operators. +Let’s look at those functions.</p> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="kd">func</span> <span class="nf">RunCmd</span><span class="p">(</span><span class="nx">cmd</span> <span class="nx">Cmd</span><span class="p">,</span> <span class="nx">cmdOperators</span> <span class="o">...</span><span class="nx">CmdOp</span><span class="p">)</span> <span class="o">*</span><span class="nx">Result</span> + +<span class="kd">func</span> <span class="nf">Command</span><span class="p">(</span><span class="nx">command</span> <span class="kt">string</span><span class="p">,</span> <span class="nx">args</span> <span class="o">...</span><span class="kt">string</span><span class="p">)</span> <span class="nx">Cmd</span> + +<span class="kd">type</span> <span class="nx">Cmd</span> <span class="kd">struct</span> <span class="p">{</span> + <span class="nx">Command</span> <span class="p">[]</span><span class="kt">string</span> + <span class="nx">Timeout</span> <span class="nx">time</span><span class="p">.</span><span class="nx">Duration</span> + <span class="nx">Stdin</span> <span class="nx">io</span><span class="p">.</span><span class="nx">Reader</span> + <span class="nx">Stdout</span> <span class="nx">io</span><span class="p">.</span><span class="nx">Writer</span> + <span class="nx">Dir</span> <span class="kt">string</span> + <span class="nx">Env</span> <span class="p">[]</span><span class="kt">string</span> +<span class="p">}</span></code></pre></div> +<p>As we’ve seen <a href="/posts/2017-01-01-go-testing-functionnal-builders/">multiple</a> <a href="/posts/2018-08-16-gotest-tools-assertions/">times</a> <a href="/posts/2018-09-14-gotest-tools-fs/">before</a>, it uses the <em>powerful</em> functional arguments. At the +time I wrote this post, the <code>icmd</code> package doesn’t contains too much <code>CmdOp</code> <sup class="footnote-ref" id="fnref:fn-1"><a href="#fn:fn-1">1</a></sup>, so I’ll +propose two version for each example : one with <code>CmdOpt</code> present in <a href="https://github.com/gotestyourself/gotest.tools/pull/122">this PR</a> and one +without them.</p> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="c1">// With +</span><span class="c1"></span><span class="nx">icmd</span><span class="p">.</span><span class="nf">RunCmd</span><span class="p">(</span><span class="nx">icmd</span><span class="p">.</span><span class="nf">Command</span><span class="p">(</span><span class="s">"sh"</span><span class="p">,</span> <span class="s">"-c"</span><span class="p">,</span> <span class="s">"echo $FOO"</span><span class="p">),</span> + <span class="nx">icmd</span><span class="p">.</span><span class="nf">WithEnv</span><span class="p">(</span><span class="s">"FOO=bar"</span><span class="p">,</span> <span class="s">"BAR=baz"</span><span class="p">),</span> <span class="nx">icmd</span><span class="p">.</span><span class="nf">Dir</span><span class="p">(</span><span class="s">"/tmp"</span><span class="p">),</span> + <span class="nx">icmd</span><span class="p">.</span><span class="nf">WithTimeout</span><span class="p">(</span><span class="mi">10</span><span class="o">*</span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">),</span> +<span class="p">).</span><span class="nf">Assert</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">icmd</span><span class="p">.</span><span class="nx">Success</span><span class="p">)</span> + +<span class="c1">// Without +</span><span class="c1"></span><span class="nx">icmd</span><span class="p">.</span><span class="nf">RunCmd</span><span class="p">(</span><span class="nx">icmd</span><span class="p">.</span><span class="nx">Cmd</span><span class="p">{</span> + <span class="nx">Command</span><span class="p">:</span> <span class="p">[]</span><span class="kt">string</span><span class="p">{</span><span class="s">"sh"</span><span class="p">,</span> <span class="s">"-c"</span><span class="p">,</span> <span class="s">"echo $FOO"</span><span class="p">},</span> + <span class="nx">Env</span><span class="p">:</span> <span class="p">[]</span><span class="kt">string</span><span class="p">{</span><span class="s">"FOO=bar"</span><span class="p">,</span> <span class="s">"BAR=baz"</span><span class="p">},</span> + <span class="nx">Dir</span><span class="p">:</span> <span class="s">"/tmp"</span><span class="p">,</span> + <span class="nx">Timeout</span><span class="p">:</span> <span class="mi">10</span><span class="o">*</span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">,</span> +<span class="p">}).</span><span class="nf">Assert</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">icmd</span><span class="p">.</span><span class="nx">Success</span><span class="p">)</span></code></pre></div> +<p>As usual, the intent is clear, it’s simple to read and composable (with <code>CmdOp</code>’s).</p> + +<h2 id="assertions">Assertions</h2> + +<p>Let’s dig into the assertion part of <code>icmd</code>. Running a command returns a struct +<code>Result</code>. It has the following methods :</p> + +<ul> +<li><code>Assert</code> compares the Result against the Expected struct, and fails the test if any of +the expectations are not met.</li> +<li><code>Compare</code> compares the result to Expected and return an error if they do not match.</li> +<li><code>Equal</code> compares the result to Expected. If the result doesn’t match expected +returns a formatted failure message with the command, stdout, stderr, exit code, and any +failed expectations. It returns an <code>assert.Comparison</code> struct, that can be used by other +<code>gotest.tools</code>.</li> +<li><code>Combined</code> returns the stdout and stderr combined into a single string.</li> +<li><code>Stderr</code> returns the stderr of the process as a string.</li> +<li><code>Stdout</code> returns the stdout of the process as a string.</li> +</ul> + +<p>When you have a result, you, most likely want to do two things :</p> + +<ul> +<li><em>assert</em> that the command succeed or failed with some specific values (exit code, +stderr, stdout)</li> +<li>use the output — most likely <code>stdout</code> but maybe <code>stderr</code> — in the rest of the test.</li> +</ul> + +<p>As seen above, <em>asserting</em> the command result is using the <code>Expected</code> struct.</p> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="kd">type</span> <span class="nx">Expected</span> <span class="kd">struct</span> <span class="p">{</span> + <span class="nx">ExitCode</span> <span class="kt">int</span> <span class="c1">// the exit code the command returned +</span><span class="c1"></span> <span class="nx">Timeout</span> <span class="kt">bool</span> <span class="c1">// did it timeout ? +</span><span class="c1"></span> <span class="nx">Error</span> <span class="kt">string</span> <span class="c1">// error returned by the execution (os/exe) +</span><span class="c1"></span> <span class="nx">Out</span> <span class="kt">string</span> <span class="c1">// content of stdout +</span><span class="c1"></span> <span class="nx">Err</span> <span class="kt">string</span> <span class="c1">// content of stderr +</span><span class="c1"></span><span class="p">}</span></code></pre></div> +<p><code>Success</code> is a constant that defines a success — it’s an exit code of <code>0</code>, didn’t timeout, +no error. There is also the <code>None</code> constant, that should be used for <code>Out</code> or <code>Err</code>, to +specify that we don’t want any content for those standard outputs.</p> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="nx">icmd</span><span class="p">.</span><span class="nf">RunCmd</span><span class="p">(</span><span class="nx">icmd</span><span class="p">.</span><span class="nf">Command</span><span class="p">(</span><span class="s">"cat"</span><span class="p">,</span> <span class="s">"/does/not/exist"</span><span class="p">)).</span><span class="nf">Assert</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">icmd</span><span class="p">.</span><span class="nx">Expected</span><span class="p">{</span> + <span class="nx">ExitCode</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> + <span class="nx">Err</span><span class="p">:</span> <span class="s">"cat: /does/not/exist: No such file or directory"</span><span class="p">,</span> +<span class="p">})</span> + +<span class="c1">// In case of success, we may want to do something with the result +</span><span class="c1"></span><span class="nx">result</span> <span class="o">:=</span> <span class="nx">icmd</span><span class="p">.</span><span class="nf">RunCommand</span><span class="p">(</span><span class="s">"cat"</span><span class="p">,</span> <span class="s">"/does/exist"</span><span class="p">)</span> +<span class="nx">result</span><span class="p">.</span><span class="nf">Assert</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">icmd</span><span class="p">.</span><span class="nx">Success</span><span class="p">)</span> +<span class="c1">// Read the output line by line +</span><span class="c1"></span><span class="nx">scanner</span> <span class="o">:=</span> <span class="nx">bufio</span><span class="p">.</span><span class="nf">NewScanner</span><span class="p">(</span><span class="nx">strings</span><span class="p">.</span><span class="nf">NewReader</span><span class="p">(</span><span class="nx">result</span><span class="p">.</span><span class="nf">Stdout</span><span class="p">()))</span> +<span class="k">for</span> <span class="nx">scanner</span><span class="p">.</span><span class="nf">Scan</span><span class="p">()</span> <span class="p">{</span> + <span class="c1">// Do something with it +</span><span class="c1"></span><span class="p">}</span></code></pre></div> +<p>If the <code>Result</code> doesn’t map the <code>Expected</code>, a test failure will happen with a useful +message that will contains the executed command and what differs between the result and +the expectation.</p> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="nx">result</span> <span class="o">:=</span> <span class="nx">icmd</span><span class="p">.</span><span class="nf">RunCommand</span><span class="p">(</span><span class="err">…</span><span class="p">)</span> +<span class="nx">result</span><span class="p">.</span><span class="nf">Assert</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">icmd</span><span class="p">.</span><span class="nx">Expected</span><span class="p">{</span> + <span class="nx">ExitCode</span><span class="p">:</span> <span class="mi">101</span><span class="p">,</span> + <span class="nx">Out</span><span class="p">:</span> <span class="s">"Something else"</span><span class="p">,</span> + <span class="nx">Err</span><span class="p">:</span> <span class="nx">None</span><span class="p">,</span> +<span class="p">})</span> +<span class="c1">// Command: binary arg1 +</span><span class="c1">// ExitCode: 99 (timeout) +</span><span class="c1">// Error: exit code 99 +</span><span class="c1">// Stdout: the output +</span><span class="c1">// Stderr: the stderr +</span><span class="c1">// +</span><span class="c1">// Failures: +</span><span class="c1">// ExitCode was 99 expected 101 +</span><span class="c1">// Expected command to finish, but it hit the timeout +</span><span class="c1">// Expected stdout to contain "Something else" +</span><span class="c1">// Expected stderr to contain "[NOTHING]" +</span><span class="c1"></span><span class="err">…</span></code></pre></div> +<p>Finally, we listed <code>Equal</code> above, that returns a <code>Comparison</code> struct. This means we can +use it easily with the <code>assert</code> package. As written in a <a href="/posts/2018-08-16-gotest-tools-assertions/">previous post (about the <code>assert</code> +package)</a>, I tend prefer to use <code>cmp.Comparison</code>. Let’s convert the above examples using +<code>assert</code>.</p> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="nx">result</span> <span class="o">:=</span> <span class="nx">icmd</span><span class="p">.</span><span class="nf">RunCmd</span><span class="p">(</span><span class="nx">icmd</span><span class="p">.</span><span class="nf">Command</span><span class="p">(</span><span class="s">"cat"</span><span class="p">,</span> <span class="s">"/does/not/exist"</span><span class="p">))</span> +<span class="nx">assert</span><span class="p">.</span><span class="nf">Check</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">result</span><span class="p">.</span><span class="nf">Equal</span><span class="p">(</span><span class="nx">icmd</span><span class="p">.</span><span class="nx">Expected</span><span class="p">{</span> + <span class="nx">ExitCode</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> + <span class="nx">Err</span><span class="p">:</span> <span class="s">"cat: /does/not/exist: No such file or directory"</span><span class="p">,</span> +<span class="p">}))</span> + +<span class="c1">// In case of success, we may want to do something with the result +</span><span class="c1"></span><span class="nx">result</span> <span class="o">:=</span> <span class="nx">icmd</span><span class="p">.</span><span class="nf">RunCommand</span><span class="p">(</span><span class="s">"cat"</span><span class="p">,</span> <span class="s">"/does/exist"</span><span class="p">)</span> +<span class="nx">assert</span><span class="p">.</span><span class="nf">Assert</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">result</span><span class="p">.</span><span class="nf">Equal</span><span class="p">(</span><span class="nx">icmd</span><span class="p">.</span><span class="nx">Success</span><span class="p">))</span> +<span class="c1">// Read the output line by line +</span><span class="c1"></span><span class="nx">scanner</span> <span class="o">:=</span> <span class="nx">bufio</span><span class="p">.</span><span class="nf">NewScanner</span><span class="p">(</span><span class="nx">strings</span><span class="p">.</span><span class="nf">NewReader</span><span class="p">(</span><span class="nx">result</span><span class="p">.</span><span class="nf">Stdout</span><span class="p">()))</span> +<span class="k">for</span> <span class="nx">scanner</span><span class="p">.</span><span class="nf">Scan</span><span class="p">()</span> <span class="p">{</span> + <span class="c1">// Do something with it +</span><span class="c1"></span><span class="p">}</span></code></pre></div> +<h2 id="conclusion">Conclusion…</h2> + +<p>… that’s a wrap. The <code>icmd</code> package allows to easily run command and describe what result +are expected of the execution, with the least noise possible. We <strong>use this package heavily</strong> +on several <code>docker/*</code> projects (the engine, the cli)…</p> +<div class="footnotes"> + +<hr /> + +<ol> +<li id="fn:fn-1">The <code>icmd</code> package is one of the oldest <code>gotest.tools</code> package, that comes from the <a href="https://github.com/docker/docker"><code>docker/docker</code></a> initially. We introduced these <code>CmdOp</code> but implementations were in <code>docker/docker</code> at first and we never really updated them. + <a class="footnote-return" href="#fnref:fn-1"><sup>[return]</sup></a></li> +</ol> +</div> + + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2018-12-16-link/" title="Go testing style guide">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2018-09-14-gotest-tools-fs/" title="Golang testing — gotest.tools fs">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2018-12-16-link/index.html b/www/vincent.demeester.fr/legacy/posts/2018-12-16-link/index.html @@ -0,0 +1,115 @@ +<!DOCTYPE html> + +<html lang="en"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2018-12-16-link/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang=""/> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">Go testing style guide</h1><a href='https://vincent.demeester.fr/posts/2018-12-16-link/'></a> + <address class="signature"> + <span class="date">Sun, 16 December, 2018</span> + <span class="words">(100 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#developement">developement</a></li> + + + + + + <li class="tag tag-golang"><a href="/tags/#golang">golang<span>12</span></a></li> + + + <li class="tag tag-testing"><a href="/tags/#testing">testing<span>11</span></a></li> + + <br/> + + </ul> + </header> + + <p>A small (opiniated) style guide for Writing Go tests.</p> + <p>🔗 <a href="https://arp242.net/weblog/go-testing-style.html" class="link">https://arp242.net/weblog/go-testing-style.html</a></p> + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2019-01-10-link/" title="log.Fatal vs log.Panic · Iskander Sharipov technical blog">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2018-09-18-gotest-tools-icmd/" title="Golang testing — gotest.tools icmd">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2019-01-10-link/index.html b/www/vincent.demeester.fr/legacy/posts/2019-01-10-link/index.html @@ -0,0 +1,121 @@ +<!DOCTYPE html> + +<html lang="en"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2019-01-10-link/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang=""/> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">log.Fatal vs log.Panic · Iskander Sharipov technical blog</h1><a href='https://vincent.demeester.fr/posts/2019-01-10-link/'></a> + <address class="signature"> + <span class="date">Thu, 10 January, 2019</span> + <span class="words">(100 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#developement">developement</a></li> + + + + + + <li class="tag tag-golang"><a href="/tags/#golang">golang<span>12</span></a></li> + + + <li class="tag tag-log"><a href="/tags/#log">log<span>1</span></a></li> + + + <li class="tag tag-panic"><a href="/tags/#panic">panic<span>1</span></a></li> + + + <li class="tag tag-fatal"><a href="/tags/#fatal">fatal<span>1</span></a></li> + + <br/> + + </ul> + </header> + + <p>Why you should prefer to use log.Panic instead of log.Fatal in go code</p> + <p>🔗 <a href="https://quasilyte.github.io/blog/post/log-fatal-vs-log-panic/" class="link">https://quasilyte.github.io/blog/post/log-fatal-vs-log-panic/</a></p> + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2019-01-20-2018-year-review/" title="2018 year review">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2018-12-16-link/" title="Go testing style guide">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2019-01-20-2018-year-review/index.html b/www/vincent.demeester.fr/legacy/posts/2019-01-20-2018-year-review/index.html @@ -0,0 +1,284 @@ +<!DOCTYPE html> + +<html lang="en"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2019-01-20-2018-year-review/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang=""/> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">2018 year review</h1><a href='https://vincent.demeester.fr/posts/2019-01-20-2018-year-review/'></a> + <address class="signature"> + <span class="date">Sun, 20 January, 2019</span> + <span class="words">(1500 Words)</span> + </address> + <ul class="tag_box inline"> + + + + + + <li class="tag tag-review"><a href="/tags/#review">review<span>1</span></a></li> + + <br/> + + </ul> + </header> + + + + + +<p>Here is my review of 2018, the first of its kind, hopefully not the last 👼. I saw +some<sup class="footnote-ref" id="fnref:fn-1"><a href="#fn:fn-1">1</a></sup> <em>2018<sup class="footnote-ref" id="fnref:fn-2"><a href="#fn:fn-2">2</a></sup> reviews</em><sup class="footnote-ref" id="fnref:fn-3"><a href="#fn:fn-3">3</a></sup> articles<sup class="footnote-ref" id="fnref:fn-4"><a href="#fn:fn-4">4</a></sup> in my Feedly feed and I thought it +would be a good idea to write my own too.</p> + +<p>I’ll try in the next year — maybe month if I ever want to do monthly reviews — to automate +some of it ; using the beloved <code>org-mode</code>.</p> + +<h2 id="work">Work</h2> + +<p>The big change this year is : I changed job 👼. I went from Docker Inc. to Red Hat. I +needed a change and 5 month in, I think it was the <strong>best choice I made in my life</strong> so far +💃. I’m doing open-source for a living and best part, I am working remotely (more on that +later).</p> + +<p>Before that, at Docker Inc., I continued the work I started years before, +a.k.a. maintaining the Moby project and the docker engine, among other Docker project +(both open-source and closed-source). I also helped the work on the compose side, from the +root of <code>docker/compose-on-kubernetes</code> (before it got open-sourced), to the <code>docker/app</code> +experiments.</p> + +<p>At Red Hat, I started to work upstream in the Kubernetes community, mainly on the Knative +projects. I also work on the Openshift Cloud Function project (and thus team), and those +fellows are awesome ! Digging more into Openshift, and other part of the Red Hat portfolio +is a really good learning experience, and it’s just the start !</p> + +<p>As stated above, I am now working home, full-time. I could work from home from time to +time when I was at Docker inc, but working home full-time is another kind of beast. So far +it is really good, some adjustments were needed but it’s for the best. Here is a small +take on “working from home”:</p> + +<ul> +<li>It’s easy to have <strong>no distraction</strong>, thus having <strong>really productive</strong> piece of time</li> +<li>It’s also <strong>really easy to work long day or really long period of time</strong>. It’s especially +true if, like me, you work on a distributed team (across multiple timezones). + +<ul> +<li>I ended up using the Pomodoro technique to make sure I move at least few times a day</li> +<li>I try to make sure I don’t make an habits of checking out work code, email and other +material after a certain hour in the evening. It’s ok to do it sometimes, but for your +sanity, you need some rest time.</li> +</ul></li> +<li>It’s easy to adapt your day to circumstance. If you got to run errands in the middle of +the day, it’s no big deal. You can take the time back later on.</li> +<li>It’s so good to have <strong>no</strong> commmute time. That said I end up <em>walking or taking the bike</em> +early morning to clear my head before work 😝.</li> +</ul> + +<h2 id="personal">Personal</h2> + +<p>Health wise, it’s a mix of good and bad year. The first half was really good, the second +way less. End of august, I felt something weird in the right knee, and well, turns out my +internal meniscus is in a real bad shape. Just as before joining Docker, I’m gonna need a +surgery, on the right knee that time. It’s gonna affect 2019 (the first half, I’m not +gonna be able to move around much but.. meh, it’s life).</p> + +<p>Now that I work from home, I’m really glad I got a standing desk at the end of 2017. I +tend to work standing most of the time – except when my knee hurts (and most likely for +few months after the surgery 😅). I invested on a ultrawide screen, to get the same +experience I had at Docker. And oh boy those screens are good !</p> + +<p>I also try to clean my desk and it’s “neighboorhood”. As I get older, I want less messy +stuuf (desk, flat, …). I’m leaning towards having less stuff, being commputer related or +not. It’s not minimalism, but it feel good to have less stuff, but stuff that you actually +use. I still have trouble throwing old computer away, mainly because I fell they can be +useful in some way.</p> + +<figure> + <img src="/images/2019/01/desk1.jpg"/> +</figure> + + +<p>This year I migrate all of my “infrastructure” computer to NixOS. I learned a lot of Nix, +reworked my configuration multiple time to end up with a <a href="https://github.com/vdemeester/nixos-configuration.git">system configuration repository</a> +that uses modules, and a <a href="https://github.com/vdemeester/home.git"><em>home configuration repository</em></a> (for user configuration). The +<a href="https://github.com/vdemeester/home.git">home</a> repository uses <a href="https://github.com/rycee/home-manager.git"><code>home-manager</code></a> and thus doesn’t make any assumption of running on top +of NixOS. This allows me to have an <em>easy to get</em> setup on any system that <code>nixpkgs</code> +supports (any Linux distribution, Mac OSX, Windows Subsystem Linux). The current +configuration is not yet optimal but I’m pretty happy about what I got :</p> + +<ul> +<li>Custom DNS server @home to make it easier to target local hosts.</li> +<li>Local proxies and mirrors for docker images, nixpkgs binary package and go modules to +eat less bandwidth.</li> +<li>Easy to setup VPN using <a href="https://www.wireguard.com/">WireGuard</a>.</li> +<li>File replication using <code>syncthing</code> and automatic backup on my local NAS.</li> +<li>Automatic system upgrade, thanks to NixOS. I’ll probably write an article about that +later on this year.</li> +</ul> + +<p>I started to use <code>todoist</code> in 2017, and boy, oh boy, it helped me quite a lot ! I’m using +it daily to organize my work and quickly get idea, and <em>todos</em> out of my head. The main +problem with it is it’s not integrated with another tool I’m using daily : Emacs and +<code>org-mode</code>. <code>org-mode</code> is a fantastic piece of software and is, on its own, the main +reason for me to invest time in Emacs. I’m taking note in <code>org-mode</code>, I write my daily +standup notes in there too. I end up going back and forth between <code>org-mode</code> and <code>todoist</code> +for those daily standup. I am lazy, I want to automate that. And the best way to do it, is +to also use <code>org-mode</code> for task management. I’m in a <em>transition</em> mode right now, but my +goal for 2019 is to use todoist to take quick note/todo(s) on the move (aka on the phone) +and use <code>org-mode</code> for the rest.</p> + +<h2 id="reading-and-writing">Reading & Writing</h2> + +<p>I used to like reading, but the past years, I didn’t really read that much, except some +technical books. 2018 in, that respect, is not an exception, I didn’t read too much. Worse +than that, I started some book and stopped at some point, for no apparent reason ; and +now, I need to start back from the beginning, which, well, is not helping me want to read +them again.</p> + +<p>I’m trying two thing to counter that and consume more books for the years to come.</p> + +<ol> +<li>I now have a reading list on my <code>org-mode</code> files, where I track which one I read and +when I read them ; and maybe notes too. I have a lot of book on my kindle, that only +wait for one thing, being read..</li> +<li>I subscribed to <a href="https://www.audible.fr">Audible</a> 👼. Working from home, I tend to take a long break after lunch, +where I’m going for a walk, for around an hour. I can’t read while walking but I +definitely can listen - that make audio books perfect for these moments. I also +alternate between audio books and non-musical podcasts.</li> +</ol> + +<p>On the writing side, 2017 was a slow year in terms of writing (only 2 posts), 2018 was a +bit better, 6 posts – it’s a bit cheating, as it was mainly between changing jobs, and +on a series I still need to finish. I’m hoping to write more this year, hence the goals +I’ve set to myself below.</p> + +<h2 id="2019-goals">2019 Goals</h2> + +<ul> +<li><strong>Get back on my feet after knee surgery (exercices, …)</strong> 🏃</li> +<li><strong>Read at least one book per month (be audible, ebook or paper)</strong> 📖</li> + +<li><p><strong>Giving at least a talk (on Knative, containers, nixos, ..)</strong> 🙊</p> + +<p>I didn’t give too much talk in 2018 (at least less than 2017). I’m gonna try to get back +at it this year. The surgery won’t help but it’s just few months.</p></li> + +<li><p><strong>At least 1 video per month</strong> 📹</p> + +<p>I want to start recording some video, as I feel it’s an easier medium than writing and, +well, I wanna try !</p></li> + +<li><p><strong>At least 1 post per month</strong> ✍️</p></li> + +<li><p><strong>Enhance my emacs skills (aka don’t be afraid of the lisp)</strong> ⌨️</p> + +<p>I’m using Emacs for almost anything that doesn’t happen in a web browser. But I still +feel like a newbie. I want to learn more, to write more lisp that help me being even +more lazier (aka achieve more doing less 😝)</p></li> + +<li><p><strong>Enhance my Nix(OS) skills</strong> 🐧</p></li> + +<li><p><strong>Learn / master a new language</strong> 🎽</p> + +<p>I’m working with Go 90% of my time. I want to master and learn more language. On my list +are Emacs Lisp, Rust, Typescript and Haskell.</p></li> +</ul> +<div class="footnotes"> + +<hr /> + +<ol> +<li id="fn:fn-1"><a href="https://punchagan.muse-amuse.in/blog/2018-in-review/">2018 in Review - Noetic Nought</a> + <a class="footnote-return" href="#fnref:fn-1"><sup>[return]</sup></a></li> +<li id="fn:fn-2"><a href="https://medium.com/@buster/42-dig-deeper-e2278d1fe015">42 — Dig deeper – Buster Benson – Medium</a> + <a class="footnote-return" href="#fnref:fn-2"><sup>[return]</sup></a></li> +<li id="fn:fn-3"><a href="https://jvns.ca/blog/2018/12/23/2018--year-in-review/">2018: Year in review - Julia Evans</a> + <a class="footnote-return" href="#fnref:fn-3"><sup>[return]</sup></a></li> +<li id="fn:fn-4"><a href="https://writing.natwelch.com/post/685">Nat? Nat. Nat! | #685 2018 Year in Review</a> + <a class="footnote-return" href="#fnref:fn-4"><sup>[return]</sup></a></li> +</ol> +</div> + + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2019-01-26-nix-run-alias/" title="nix run aliases">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2019-01-10-link/" title="log.Fatal vs log.Panic · Iskander Sharipov technical blog">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2019-01-26-nix-run-alias/index.html b/www/vincent.demeester.fr/legacy/posts/2019-01-26-nix-run-alias/index.html @@ -0,0 +1,308 @@ +<!DOCTYPE html> + +<html lang="en"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2019-01-26-nix-run-alias/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang=""/> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">nix run aliases</h1><a href='https://vincent.demeester.fr/posts/2019-01-26-nix-run-alias/'></a> + <address class="signature"> + <span class="date">Sat, 26 January, 2019</span> + <span class="words">(1300 Words)</span> + </address> + <ul class="tag_box inline"> + + + + + + <li class="tag tag-nixos"><a href="/tags/#nixos">nixos<span>1</span></a></li> + + + <li class="tag tag-fish"><a href="/tags/#fish">fish<span>1</span></a></li> + + + <li class="tag tag-alias"><a href="/tags/#alias">alias<span>1</span></a></li> + + + <li class="tag tag-nix"><a href="/tags/#nix">nix<span>3</span></a></li> + + + <li class="tag tag-shell"><a href="/tags/#shell">shell<span>2</span></a></li> + + + <li class="tag tag-home"><a href="/tags/#home">home<span>1</span></a></li> + + + <li class="tag tag-manager"><a href="/tags/#manager">manager<span>1</span></a></li> + + <br/> + + </ul> + </header> + + + + + +<p>I use <a href="https://nixos.org/"><code>NixOS</code></a> each and every day, everywhere. One really cool feature of <code>nix</code> is +<code>nix-shell</code> and more recently (with <code>nix</code> >= <code>2.0.0</code>), <code>nix run</code>.</p> +<div class="highlight"><pre class="chroma"><code class="language-man" data-lang="man">Usage: nix run <FLAGS>... <INSTALLABLES>... + +Summary: run a shell in which the specified packages are available. + +Flags: + --arg <NAME> <EXPR> argument to be passed to Nix functions + --argstr <NAME> <STRING> string-valued argument to be passed to Nix functions + -c, --command <COMMAND> <ARGS> command and arguments to be executed; defaults to 'bash' + -f, --file <FILE> evaluate FILE rather than the default + -i, --ignore-environment clear the entire environment (except those specified with --keep) + -I, --include <PATH> add a path to the list of locations used to look up <...> file names + -k, --keep <NAME> keep specified environment variable + -u, --unset <NAME> unset specified environment variable + +Examples: + + To start a shell providing GNU Hello from NixOS 17.03: + $ nix run -f channel:nixos-17.03 hello + + To start a shell providing youtube-dl from your 'nixpkgs' channel: + $ nix run nixpkgs.youtube-dl + + To run GNU Hello: + $ nix run nixpkgs.hello -c hello --greeting 'Hi everybody!' + + To run GNU Hello in a chroot store: + $ nix run --store ~/my-nix nixpkgs.hello -c hello + +Note: this program is EXPERIMENTAL and subject to change.</code></pre></div> +<p>As you can see from the <code>-h</code> summary, it makes it really easy to run a shell or a command +with some packages that are not in your main configuration. It will download the +package(s) if there are not available in the Nix store (<code>/nix/store/</code>).</p> + +<p>A few month ago I decided it would be a perfect use-case for command I do not run +often. My idea was, let’s define <code>aliases</code> (in the shell) that would make a simple command +call, like <code>ncdu</code>, become <code>nix run nixpkgs.ncdu -c ndcu</code>. My <em>shell of choice</em> is <a href="https://fishshell.com/">fish</a>, so +I decided to dig into the <em>language</em> in order to implement that.</p> + +<p>The use case is the following :</p> + +<ul> +<li>When I type <code>foo</code>, I want the command <code>foo</code> in package <code>bar</code> to be executed.</li> +<li>I want to be able to pin a channel for the package — I’m using <a href="https://matthewbauer.us/">Matthew Bauer</a> <a href="https://matthewbauer.us/blog/channel-changing.html">Channel +Changing with Nix</a> setup for pin-pointing a given channel.</li> +</ul> + +<h2 id="fish-aliases-experimentation">Fish aliases experimentation</h2> + +<p>I had a feeling the built-in <code>alias</code> would not work so I ended up trying to define a +<em>dynamic</em> function that would be the name of the command. That’s the beauty of the shell, +everything is a command, even function appears as commands. If you define a function +<code>foo()</code>, you will be able to run <code>foo</code> in your shell, <strong>and</strong> it will take precedence over +the <code>foo</code> executable file that would be in your <code>PATH</code>.</p> + +<p>I ended up with two main helper function that would create those <em>alias</em> function.</p> +<div class="highlight"><pre class="chroma"><code class="language-fish" data-lang="fish"><span class="k">function</span> _nix_run_package + <span class="k">set</span> -l s <span class="nv">$argv</span><span class="o">[</span>1<span class="o">]</span> + <span class="k">set</span> -l package <span class="o">(</span>string split <span class="s2">":"</span> <span class="nv">$s</span><span class="o">)</span> + <span class="k">switch</span> <span class="o">(</span><span class="k">count</span> <span class="nv">$package</span><span class="o">)</span> + <span class="k">case</span> 1 + _nix_run <span class="nv">$s</span> <span class="nv">$s</span> <span class="nv">$argv</span><span class="o">[</span>2<span class="o">]</span> <span class="nv">$argv</span><span class="o">[</span>3<span class="o">]</span> + <span class="k">case</span> 2 + _nix_run <span class="nv">$package</span><span class="o">[</span>1<span class="o">]</span> <span class="nv">$package</span><span class="o">[</span>2<span class="o">]</span> <span class="nv">$argv</span><span class="o">[</span>2<span class="o">]</span> <span class="nv">$argv</span><span class="o">[</span>3<span class="o">]</span> + <span class="k">end</span> +<span class="k">end</span> + +<span class="k">function</span> _nix_run + <span class="k">set</span> -l c <span class="nv">$argv</span><span class="o">[</span>1<span class="o">]</span> + <span class="k">set</span> -l p <span class="nv">$argv</span><span class="o">[</span>2<span class="o">]</span> + <span class="k">set</span> -l channel <span class="nv">$argv</span><span class="o">[</span>3<span class="o">]</span> + <span class="k">set</span> -l channelsfile <span class="nv">$argv</span><span class="o">[</span>4<span class="o">]</span> + <span class="k">function</span> <span class="nv">$c</span> --inherit-variable c --inherit-variable p --inherit-variable channel --inherit-variable channelsfile + <span class="k">set</span> -l cmd nix run + <span class="k">if</span> <span class="k">test</span> -n <span class="s2">"</span><span class="nv">$channelsfile</span><span class="s2">"</span> + <span class="k">set</span> cmd <span class="nv">$cmd</span> -f <span class="nv">$channelsfile</span> + <span class="k">end</span> + <span class="nb">eval</span> <span class="nv">$cmd</span> <span class="nv">$channel</span>.<span class="nv">$p</span> -c <span class="nv">$c</span> <span class="nv">$argv</span> + <span class="k">end</span> +<span class="k">end</span></code></pre></div> +<p>In a nutshell, <code>_nix_run</code> is the function that create the alias function. There is so +condition in there depending on whether we gave it a channel or not. So, a call like +<code>_nix_run foo bar unstable channels.nix</code> would, in the end generate a function <code>foo</code> with +the following call : <code>nix run -f channels.nix unstable.bar -c foo</code>.</p> + +<p>The other function, <code>_nix_run_package</code> is there to make me write less when I define those +aliases — aka if the command and the package share the same name, I don’t want to write it +twice. So, a call like <code>_nix_run_package foo nixpkgs</code> would result in a <code>_nix_run foo foo +nixpkgs</code>, whereas a call like <code>_nix_run_package foo:bar unstable channels.nix</code> would +result in a <code>_nix_run foo bar unstable channels.nix</code>.</p> + +<p>An example is gonna be better than the above paragraphs. This is what I used to have in my +fish configuration.</p> +<div class="highlight"><pre class="chroma"><code class="language-fish" data-lang="fish"><span class="k">function</span> _def_nix_run_aliases + <span class="k">set</span> -l stable mr sshfs ncdu wakeonlan:python36Packages.wakeonlan lspci:pciutils lsusb:usbutils beet:beets gotop virt-manager:virtmanager pandoc nix-prefetch-git:nix-prefetch-scripts nix-prefetch-hg:nix-prefetch-scripts + <span class="k">set</span> -l unstable op:_1password update-desktop-database:desktop-file-utils lgogdownloader + <span class="k">for</span> s <span class="k">in</span> <span class="nv">$stable</span> + _nix_run_package <span class="nv">$s</span> nixpkgs + <span class="k">end</span> + <span class="k">for</span> s <span class="k">in</span> <span class="nv">$unstable</span> + _nix_run_package <span class="nv">$s</span> unstable ~/.config/nixpkgs/channels.nix + <span class="k">end</span> +<span class="k">end</span> +<span class="c"># Call the function to create the aliases +</span><span class="c"></span>_def_nix_run_aliases</code></pre></div> +<p>This works like a charm, and for a while, I was happy. But I soon realized something : I’m +not always on my shell — like, I tend to spend more and more time in <code>eshell</code>. This also +doesn’t work with graphic tools like <a href="https://github.com/DaveDavenport/rofi"><code>rofi</code></a>. I needed actual command, so that external +tools would benefit from that. I ended up writing a small tool, <a href="https://github.com/vdemeester/nr"><code>nr</code></a> that integrates +nicely with <code>nix</code> and <a href="https://github.com/rycee/home-manager"><code>home-manager</code></a>.</p> + +<h2 id="a-proper-tool-nr">A proper tool : <code>nr</code></h2> + +<p>The gist for this tool is simple :</p> + +<ul> +<li>create an executable script that will call <code>nix run ...</code> instead of the command</li> +<li>as for the above fish script, support different channels</li> +<li>make sure we don’t have conflicts — if the command already exists, then don’t create the +command</li> +</ul> + +<p>The <code>nr</code> tool would have to be able to manage multiple <em>profile</em>, which really stands for +multiple file. The main reason is really about how I manage my configuration ; To make it +simple, depending on the computer my configurations are setup, I may not have <code>go</code>, thus I +don’t want any <code>go</code>-related aliases for a computer that doesn’t have <code>go</code> (using <code>go</code> here +but you can replace with anything).</p> +<div class="highlight"><pre class="chroma"><code class="language-fish" data-lang="fish"><span class="nv">$ </span>nr default +<span class="o">></span> nr generate default +<span class="o">></span> virtmanager already exists +<span class="nv">$ </span>nr git +<span class="o">></span> nr generate git</code></pre></div> +<p><code>nr</code> generates a bash script that does the <code>nr run …</code> and mark it as executable. <code>nr</code> +needs to be able to clean files it has generated (in case we removed it from +aliases). Thus, I went for a really naive comment in the script. When generating a new set +of commands, <code>nr</code> will first remove previously generated script for this profile, and for +that, it uses the comment. Let’s look at what a generated script looks like, for the +default profile.</p> +<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="cp">#!/usr/bin/env bash +</span><span class="cp"></span><span class="c1"># Generated by nr default</span> +nix run nixpkgs.nix-prefetch-scripts -c nix-prefetch-git <span class="nv">$@</span></code></pre></div> +<p>The format used in <code>nr</code> is <code>json</code>. I’m not a <em>huge fan</em> of <code>json</code> but it really was the +best format to use for this tool. The reason to use <code>json</code> are simple :</p> + +<ul> +<li><p>Go has <code>encoding/json</code> built-in, so it’s really easy to <code>Marshall</code> and <code>Unmarshall</code> +structure.</p> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="kd">type</span> <span class="nx">alias</span> <span class="kd">struct</span> <span class="p">{</span> + <span class="nx">Command</span> <span class="kt">string</span> <span class="s">`json:"cmd"`</span> + <span class="nx">Package</span> <span class="kt">string</span> <span class="s">`json:"pkg"`</span> + <span class="nx">Channel</span> <span class="kt">string</span> <span class="s">`json:"chan"`</span> +<span class="p">}</span></code></pre></div></li> + +<li><p>Nix also has built-in support for <code>json</code> : <code>builtins.toJSON</code> will marshall a <em>struct</em> +into a json file.</p></li> +</ul> + +<p>Finally, to avoid conflicts at <em>build time</em> (<code>home-manager switch</code>) I couldn’t use/define +a nix package, but to execute command(s) at the end of the build. One way to achieve it is +to use <code>file.?.onChange</code> script, which is executed after <a href="https://github.com/rycee/home-manager"><code>home-manager</code></a> has updated the +environment, <strong>if</strong> the file has changed. That means it’s possible to check for executable +files in <code>~/.nix-profile/bin/</code> for defined aliases and create those that are not there, +with <code>nr</code>. My configuration then looks like the following.</p> +<div class="highlight"><pre class="chroma"><code class="language-nix" data-lang="nix"><span class="n">xdg</span><span class="o">.</span><span class="n">configFile</span><span class="o">.</span><span class="s2">"nr/default"</span> <span class="err">=</span> <span class="p">{</span> + <span class="n">text</span> <span class="o">=</span> <span class="nb">builtins</span><span class="o">.</span><span class="n">toJSON</span> <span class="p">[</span> + <span class="p">{</span><span class="n">cmd</span> <span class="o">=</span> <span class="s2">"ncdu"</span><span class="p">;}</span> <span class="p">{</span><span class="n">cmd</span> <span class="o">=</span> <span class="s2">"sshfs"</span><span class="p">;}</span> <span class="p">{</span><span class="n">cmd</span> <span class="o">=</span> <span class="s2">"gotop"</span><span class="p">;}</span> <span class="p">{</span><span class="n">cmd</span> <span class="o">=</span> <span class="s2">"pandoc"</span><span class="p">;}</span> + <span class="p">{</span><span class="n">cmd</span> <span class="o">=</span> <span class="s2">"wakeonlan"</span><span class="p">;</span> <span class="n">pkg</span> <span class="o">=</span> <span class="s2">"python36Packages.wakeonlan"</span><span class="p">;}</span> + <span class="p">{</span><span class="n">cmd</span> <span class="o">=</span> <span class="s2">"beet"</span><span class="p">;</span> <span class="n">pkg</span> <span class="o">=</span> <span class="s2">"beets"</span><span class="p">;}</span> + <span class="p">{</span><span class="n">cmd</span> <span class="o">=</span> <span class="s2">"virt-manager"</span><span class="p">;</span> <span class="n">pkg</span> <span class="o">=</span> <span class="s2">"virtmanager"</span><span class="p">;}</span> + <span class="p">{</span><span class="n">cmd</span> <span class="o">=</span> <span class="s2">"nix-prefetch-git"</span><span class="p">;</span> <span class="n">pkg</span> <span class="o">=</span> <span class="s2">"nix-prefetch-scripts"</span><span class="p">;}</span> + <span class="p">{</span><span class="n">cmd</span> <span class="o">=</span> <span class="s2">"nix-prefetch-hg"</span><span class="p">;</span> <span class="n">pkg</span> <span class="o">=</span> <span class="s2">"nix-prefetch-scripts"</span><span class="p">;}</span> + <span class="p">];</span> + <span class="n">onChange</span> <span class="o">=</span> <span class="s2">"</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">nur</span><span class="o">.</span><span class="n">repos</span><span class="o">.</span><span class="n">vdemeester</span><span class="o">.</span><span class="n">nr</span><span class="si">}</span><span class="s2">/bin/nr default"</span><span class="p">;</span> +<span class="p">};</span></code></pre></div> +<p>And there you are, now, each time I update my environment (<code>home-manager switch</code>), <code>nr</code> +will regenerate my <code>nix run</code> aliases.</p> + + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2019-01-27-link/" title="Self-care tips for new developers">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2019-01-20-2018-year-review/" title="2018 year review">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2019-01-27-link/index.html b/www/vincent.demeester.fr/legacy/posts/2019-01-27-link/index.html @@ -0,0 +1,121 @@ +<!DOCTYPE html> + +<html lang="en"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2019-01-27-link/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang=""/> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">Self-care tips for new developers</h1><a href='https://vincent.demeester.fr/posts/2019-01-27-link/'></a> + <address class="signature"> + <span class="date">Sun, 27 January, 2019</span> + <span class="words">(100 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#development">development</a></li> + + + + + + <li class="tag tag-development"><a href="/tags/#development">development<span>13</span></a></li> + + + <li class="tag tag-software"><a href="/tags/#software">software<span>13</span></a></li> + + + <li class="tag tag-productivity"><a href="/tags/#productivity">productivity<span>1</span></a></li> + + + <li class="tag tag-health"><a href="/tags/#health">health<span>1</span></a></li> + + <br/> + + </ul> + </header> + + <p>Tips for developers to take care of themselves to work better (but less too 👼)</p> + <p>🔗 <a href="https://punchagan.muse-amuse.in/blog/self-care-tips-for-new-developers/" class="link">https://punchagan.muse-amuse.in/blog/self-care-tips-for-new-developers/</a></p> + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2019-02-15-link/" title="$ docker build -f Mockerfile.yaml .">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2019-01-26-nix-run-alias/" title="nix run aliases">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2019-02-15-link/index.html b/www/vincent.demeester.fr/legacy/posts/2019-02-15-link/index.html @@ -0,0 +1,121 @@ +<!DOCTYPE html> + +<html lang="en"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2019-02-15-link/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang=""/> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">$ docker build -f Mockerfile.yaml .</h1><a href='https://vincent.demeester.fr/posts/2019-02-15-link/'></a> + <address class="signature"> + <span class="date">Fri, 15 February, 2019</span> + <span class="words">(100 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#development">development</a></li> + + + + + + <li class="tag tag-development"><a href="/tags/#development">development<span>13</span></a></li> + + + <li class="tag tag-software"><a href="/tags/#software">software<span>13</span></a></li> + + + <li class="tag tag-docker"><a href="/tags/#docker">docker<span>3</span></a></li> + + + <li class="tag tag-buildkit"><a href="/tags/#buildkit">buildkit<span>1</span></a></li> + + <br/> + + </ul> + </header> + + <p>how to write your own Dockerfile syntax that works out of the box with any existing Docker installation. </p> + <p>🔗 <a href="https://matt-rickard.com/building-a-new-dockerfile-frontend/" class="link">https://matt-rickard.com/building-a-new-dockerfile-frontend/</a></p> + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2019-02-25-link/" title="Go Interfaces - fREW Schmidt's Foolish Manifesto">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2019-01-27-link/" title="Self-care tips for new developers">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2019-02-25-link/index.html b/www/vincent.demeester.fr/legacy/posts/2019-02-25-link/index.html @@ -0,0 +1,121 @@ +<!DOCTYPE html> + +<html lang="en"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2019-02-25-link/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang=""/> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">Go Interfaces - fREW Schmidt's Foolish Manifesto</h1><a href='https://vincent.demeester.fr/posts/2019-02-25-link/'></a> + <address class="signature"> + <span class="date">Mon, 25 February, 2019</span> + <span class="words">(100 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#development">development</a></li> + + + + + + <li class="tag tag-development"><a href="/tags/#development">development<span>13</span></a></li> + + + <li class="tag tag-software"><a href="/tags/#software">software<span>13</span></a></li> + + + <li class="tag tag-go"><a href="/tags/#go">go<span>5</span></a></li> + + + <li class="tag tag-interfaces"><a href="/tags/#interfaces">interfaces<span>1</span></a></li> + + <br/> + + </ul> + </header> + + <p>I did some work recently that depended on Go interfaces and I found it both straightforward and elegant.</p> + <p>🔗 <a href="https://blog.afoolishmanifesto.com/posts/go-interfaces/" class="link">https://blog.afoolishmanifesto.com/posts/go-interfaces/</a></p> + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2019-03-12-link/" title="Go2 Contracts Go Too Far · npf.io">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2019-02-15-link/" title="$ docker build -f Mockerfile.yaml .">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2019-03-12-link/index.html b/www/vincent.demeester.fr/legacy/posts/2019-03-12-link/index.html @@ -0,0 +1,124 @@ +<!DOCTYPE html> + +<html lang="en"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2019-03-12-link/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang=""/> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">Go2 Contracts Go Too Far · npf.io</h1><a href='https://vincent.demeester.fr/posts/2019-03-12-link/'></a> + <address class="signature"> + <span class="date">Tue, 12 March, 2019</span> + <span class="words">(100 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#development">development</a></li> + + + + + + <li class="tag tag-development"><a href="/tags/#development">development<span>13</span></a></li> + + + <li class="tag tag-software"><a href="/tags/#software">software<span>13</span></a></li> + + + <li class="tag tag-go"><a href="/tags/#go">go<span>5</span></a></li> + + + <li class="tag tag-contracts"><a href="/tags/#contracts">contracts<span>1</span></a></li> + + + <li class="tag tag-go2"><a href="/tags/#go2">go2<span>1</span></a></li> + + <br/> + + </ul> + </header> + + <p>A take at the Go 2 contracts proposal and why it might not really do good</p> + <p>🔗 <a href="https://npf.io/2018/09/go2-contracts-go-too-far/" class="link">https://npf.io/2018/09/go2-contracts-go-too-far/</a></p> + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2019-03-23-gotest-tools-poll/" title="Golang testing — gotest.tools poll">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2019-02-25-link/" title="Go Interfaces - fREW Schmidt's Foolish Manifesto">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2019-03-23-gotest-tools-poll/index.html b/www/vincent.demeester.fr/legacy/posts/2019-03-23-gotest-tools-poll/index.html @@ -0,0 +1,236 @@ +<!DOCTYPE html> + +<html lang="en"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2019-03-23-gotest-tools-poll/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang=""/> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">Golang testing — gotest.tools poll</h1><a href='https://vincent.demeester.fr/posts/2019-03-23-gotest-tools-poll/'></a> + <address class="signature"> + <span class="date">Sat, 23 March, 2019</span> + <span class="words">(700 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#developement">developement</a></li> + + + + + + <li class="tag tag-testing"><a href="/tags/#testing">testing<span>11</span></a></li> + + + <li class="tag tag-golang"><a href="/tags/#golang">golang<span>12</span></a></li> + + + <li class="tag tag-poll"><a href="/tags/#poll">poll<span>1</span></a></li> + + <br/> + + </ul> + </header> + + + + + +<p>Let’s continue the <a href="https://gotest.tools"><code>gotest.tools</code></a> serie, this time with the <code>poll</code> package.</p> + +<blockquote> +<p>Package poll provides tools for testing asynchronous code.</p> +</blockquote> + +<p>When you write test, you may test a piece of code that work asynchronously, where the +state you’re expecting is gonna take a bit of time to be achieved. This is especially true +when you work on networking or file-system code. And this happens a lot when you write +integration (or end-to-end) test, less for unit-tests.</p> + +<p>The package <code>poll</code> is trying to tackle those use cases. We’ll first take a look at the +main function, <code>WaitOn</code>, then how to write a <code>Check</code>, using the <code>Result</code> type.</p> + +<h2 id="waiton"><code>WaitOn</code></h2> + +<p>Let’s look into the main <code>poll</code> function : `WaitOn`.</p> + +<blockquote> +<p>WaitOn a condition or until a timeout. Poll by calling check and exit when check returns +a done Result. To fail a test and exit polling with an error return a error result.</p> +</blockquote> + +<p>In a gist, <code>WaitOn</code> will run a <em>condition</em> function until it either times out or +succeed. It wait for a given time/delay between each run.</p> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="kd">func</span> <span class="nf">WaitOn</span><span class="p">(</span><span class="nx">t</span> <span class="nx">TestingT</span><span class="p">,</span> <span class="nx">check</span> <span class="nx">Check</span><span class="p">,</span> <span class="nx">pollOps</span> <span class="o">...</span><span class="nx">SettingOp</span><span class="p">)</span> <span class="p">{</span> + <span class="c1">// […] +</span><span class="c1"></span><span class="p">}</span></code></pre></div> +<p>As any <em>testing helper</em> function, the first argument is <code>*testing.T</code> (or, in this case, +any thing that look like it, thanks to the <code>TestingT</code> interace). The two other arguments +are way more interesting :</p> + +<ul> +<li>The <code>Check</code> is the condition that will run multiple times until it either timeout, or succeed.</li> +<li>The <code>SettingOp(s)</code> which are options to configure the function, things like the timeout, +or the <em>delay</em> between each run.</li> +</ul> + +<p>The settings are pretty straightforward :</p> + +<ul> +<li><code>WithDelay</code> : sets the delay to wait between polls. The default delay is 100ms.</li> +<li><code>WithTimeout</code> : sets the timeout. The default timeout is 10s.</li> +</ul> + +<p>There is existing <code>Check</code> for common case:</p> + +<ul> +<li><p><code>Connection</code> : try to open a connection to the address on the named network.</p> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="nx">poll</span><span class="p">.</span><span class="nf">WaitOn</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">poll</span><span class="p">.</span><span class="nf">Connection</span><span class="p">(</span><span class="s">"tcp"</span><span class="p">,</span> <span class="s">"foo.bar:55555"</span><span class="p">),</span> <span class="nx">poll</span><span class="p">.</span><span class="nf">WithTimeout</span><span class="p">(</span><span class="s">"5s"</span><span class="p">))</span></code></pre></div></li> + +<li><p><code>FileExists</code> : looks on filesystem and check that path exists.</p> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="nx">poll</span><span class="p">.</span><span class="nf">WaitOn</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">poll</span><span class="p">.</span><span class="nf">FileExists</span><span class="p">(</span><span class="s">"/should/be/created"</span><span class="p">),</span> <span class="nx">poll</span><span class="p">.</span><span class="nf">WithDelay</span><span class="p">(</span><span class="s">"1s"</span><span class="p">))</span></code></pre></div></li> +</ul> + +<h2 id="check-and-result"><code>Check</code> and <code>Result</code></h2> + +<p><code>Connection</code> and <code>FileExists</code> are the only two <em>built-in</em> <code>Check</code> provided by +<code>gotest.tools</code>. They are useful, but as usual, where <code>gotest.tools</code> shines is +extensiblity. It is really easy to define your own <code>Check</code>.</p> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="kd">type</span> <span class="nx">Check</span> <span class="kd">func</span><span class="p">(</span><span class="nx">t</span> <span class="nx">LogT</span><span class="p">)</span> <span class="nx">Result</span></code></pre></div> +<p>A <code>Check</code> is, thus, only a function that takes <code>LogT</code> — which is anything that can log +something, like <code>*testing.T</code> — and return a <code>Result</code>. Let’s look at this intersting +<code>Result</code> type.</p> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="kd">type</span> <span class="nx">Result</span> <span class="kd">interface</span> <span class="p">{</span> + <span class="c1">// Error indicates that the check failed and polling should stop, and the +</span><span class="c1"></span> <span class="c1">// the has failed +</span><span class="c1"></span> <span class="nf">Error</span><span class="p">()</span> <span class="kt">error</span> + <span class="c1">// Done indicates that polling should stop, and the test should proceed +</span><span class="c1"></span> <span class="nf">Done</span><span class="p">()</span> <span class="kt">bool</span> + <span class="c1">// Message provides the most recent state when polling has not completed +</span><span class="c1"></span> <span class="nf">Message</span><span class="p">()</span> <span class="kt">string</span> +<span class="p">}</span></code></pre></div> +<p>Although it’s an interface, the <code>poll</code> package defines built-in <code>Result</code> so that it’s easy +to write <code>Check</code> without having to define you <code>Result</code> type.</p> + +<ul> +<li><code>Continue</code> returns a Result that indicates to WaitOn that it should continue +polling. The message text will be used as the failure message if the timeout is reached.</li> +<li><code>Success</code> returns a Result where Done() returns true, which indicates to WaitOn that it +should stop polling and exit without an error.</li> +<li><code>Error</code> returns a Result that indicates to WaitOn that it should fail the test and stop +polling.</li> +</ul> + +<p>The basic just to write a <code>Check</code> is then :</p> + +<ul> +<li>if the state is not there yet, return <code>Continue</code>,</li> +<li>if there is an error, unrelated to validating the state, return an <code>Error</code>,</li> +<li>if the state is there, return <code>Success</code>.</li> +</ul> + +<p>Let’s look at an example taken from the <code>moby/moby</code> source code.</p> +<div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="nx">poll</span><span class="p">.</span><span class="nf">WaitOn</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">container</span><span class="p">.</span><span class="nf">IsInState</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="nx">client</span><span class="p">,</span> <span class="nx">cID</span><span class="p">,</span> <span class="s">"running"</span><span class="p">),</span> <span class="nx">poll</span><span class="p">.</span><span class="nf">WithDelay</span><span class="p">(</span><span class="mi">100</span><span class="o">*</span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">))</span> + +<span class="kd">func</span> <span class="nf">IsInState</span><span class="p">(</span><span class="nx">ctx</span> <span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span> <span class="nx">client</span> <span class="nx">client</span><span class="p">.</span><span class="nx">APIClient</span><span class="p">,</span> <span class="nx">containerID</span> <span class="kt">string</span><span class="p">,</span> <span class="nx">state</span> <span class="o">...</span><span class="kt">string</span><span class="p">)</span> <span class="kd">func</span><span class="p">(</span><span class="nx">log</span> <span class="nx">poll</span><span class="p">.</span><span class="nx">LogT</span><span class="p">)</span> <span class="nx">poll</span><span class="p">.</span><span class="nx">Result</span> <span class="p">{</span> + <span class="k">return</span> <span class="kd">func</span><span class="p">(</span><span class="nx">log</span> <span class="nx">poll</span><span class="p">.</span><span class="nx">LogT</span><span class="p">)</span> <span class="nx">poll</span><span class="p">.</span><span class="nx">Result</span> <span class="p">{</span> + <span class="nx">inspect</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">client</span><span class="p">.</span><span class="nf">ContainerInspect</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="nx">containerID</span><span class="p">)</span> + <span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span> + <span class="k">return</span> <span class="nx">poll</span><span class="p">.</span><span class="nf">Error</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span> + <span class="p">}</span> + <span class="k">for</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">v</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">state</span> <span class="p">{</span> + <span class="k">if</span> <span class="nx">inspect</span><span class="p">.</span><span class="nx">State</span><span class="p">.</span><span class="nx">Status</span> <span class="o">==</span> <span class="nx">v</span> <span class="p">{</span> + <span class="k">return</span> <span class="nx">poll</span><span class="p">.</span><span class="nf">Success</span><span class="p">()</span> + <span class="p">}</span> + <span class="p">}</span> + <span class="k">return</span> <span class="nx">poll</span><span class="p">.</span><span class="nf">Continue</span><span class="p">(</span><span class="s">"waiting for container to be one of (%s), currently %s"</span><span class="p">,</span> <span class="nx">strings</span><span class="p">.</span><span class="nf">Join</span><span class="p">(</span><span class="nx">state</span><span class="p">,</span> <span class="s">", "</span><span class="p">),</span> <span class="nx">inspect</span><span class="p">.</span><span class="nx">State</span><span class="p">.</span><span class="nx">Status</span><span class="p">)</span> + <span class="p">}</span> +<span class="p">}</span></code></pre></div> +<h2 id="conclusion">Conclusion</h2> + +<p>… that’s a wrap. The <code>poll</code> package allows to easily wait for a condition to happen in a +given time-frame — with sane defaults. As for most of the <code>gotest.tools</code> package, we use +this package heavily in <code>docker/*</code> projects too…</p> + + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2019-03-24-link/" title="Building Blocks – I.A.">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2019-03-12-link/" title="Go2 Contracts Go Too Far · npf.io">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2019-03-24-link/index.html b/www/vincent.demeester.fr/legacy/posts/2019-03-24-link/index.html @@ -0,0 +1,121 @@ +<!DOCTYPE html> + +<html lang="en"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2019-03-24-link/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang=""/> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">Building Blocks – I.A.</h1><a href='https://vincent.demeester.fr/posts/2019-03-24-link/'></a> + <address class="signature"> + <span class="date">Sun, 24 March, 2019</span> + <span class="words">(100 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#development">development</a></li> + + + + + + <li class="tag tag-development"><a href="/tags/#development">development<span>13</span></a></li> + + + <li class="tag tag-software"><a href="/tags/#software">software<span>13</span></a></li> + + + <li class="tag tag-functional"><a href="/tags/#functional">functional<span>1</span></a></li> + + + <li class="tag tag-programming"><a href="/tags/#programming">programming<span>1</span></a></li> + + <br/> + + </ul> + </header> + + <p>A visual approach to the topic of purely-typed functional programming.</p> + <p>🔗 <a href="https://icidasset.com/writings/building-blocks/" class="link">https://icidasset.com/writings/building-blocks/</a></p> + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2019-03-26-link/" title="Handle and Check - Let's Not · npf.io">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2019-03-23-gotest-tools-poll/" title="Golang testing — gotest.tools poll">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2019-03-26-link/index.html b/www/vincent.demeester.fr/legacy/posts/2019-03-26-link/index.html @@ -0,0 +1,127 @@ +<!DOCTYPE html> + +<html lang="en"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2019-03-26-link/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang=""/> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">Handle and Check - Let's Not · npf.io</h1><a href='https://vincent.demeester.fr/posts/2019-03-26-link/'></a> + <address class="signature"> + <span class="date">Tue, 26 March, 2019</span> + <span class="words">(100 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#development">development</a></li> + + + + + + <li class="tag tag-development"><a href="/tags/#development">development<span>13</span></a></li> + + + <li class="tag tag-software"><a href="/tags/#software">software<span>13</span></a></li> + + + <li class="tag tag-go"><a href="/tags/#go">go<span>5</span></a></li> + + + <li class="tag tag-proposal"><a href="/tags/#proposal">proposal<span>1</span></a></li> + + + <li class="tag tag-handle"><a href="/tags/#handle">handle<span>1</span></a></li> + + + <li class="tag tag-check"><a href="/tags/#check">check<span>1</span></a></li> + + <br/> + + </ul> + </header> + + <p>Opinion (that I share) on the check/handle error proposal for Go 2</p> + <p>🔗 <a href="https://npf.io/2018/09/check-and-handle/" class="link">https://npf.io/2018/09/check-and-handle/</a></p> + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2019-03-27-link/" title="Nicolas Mattia - The 5 Raisons d'Être of Testing">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2019-03-24-link/" title="Building Blocks – I.A.">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2019-03-27-link/index.html b/www/vincent.demeester.fr/legacy/posts/2019-03-27-link/index.html @@ -0,0 +1,121 @@ +<!DOCTYPE html> + +<html lang="en"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2019-03-27-link/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang=""/> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">Nicolas Mattia - The 5 Raisons d'Être of Testing</h1><a href='https://vincent.demeester.fr/posts/2019-03-27-link/'></a> + <address class="signature"> + <span class="date">Wed, 27 March, 2019</span> + <span class="words">(100 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#development">development</a></li> + + + + + + <li class="tag tag-development"><a href="/tags/#development">development<span>13</span></a></li> + + + <li class="tag tag-software"><a href="/tags/#software">software<span>13</span></a></li> + + + <li class="tag tag-testing"><a href="/tags/#testing">testing<span>11</span></a></li> + + + <li class="tag tag-craftmanship"><a href="/tags/#craftmanship">craftmanship<span>1</span></a></li> + + <br/> + + </ul> + </header> + + <p>The author describes five of the reason why he thinks tests are important — and I do agree !</p> + <p>🔗 <a href="https://nmattia.com/posts/2019-01-22-the-5-raisons-detre-of-testing.html" class="link">https://nmattia.com/posts/2019-01-22-the-5-raisons-detre-of-testing.html</a></p> + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2019-03-28-link/" title="Nix: A Reproducible Setup for Linux and macOS">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2019-03-26-link/" title="Handle and Check - Let's Not · npf.io">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2019-03-28-link/index.html b/www/vincent.demeester.fr/legacy/posts/2019-03-28-link/index.html @@ -0,0 +1,130 @@ +<!DOCTYPE html> + +<html lang="en"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2019-03-28-link/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang=""/> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">Nix: A Reproducible Setup for Linux and macOS</h1><a href='https://vincent.demeester.fr/posts/2019-03-28-link/'></a> + <address class="signature"> + <span class="date">Thu, 28 March, 2019</span> + <span class="words">(100 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#development">development</a></li> + + + + + + <li class="tag tag-development"><a href="/tags/#development">development<span>13</span></a></li> + + + <li class="tag tag-software"><a href="/tags/#software">software<span>13</span></a></li> + + + <li class="tag tag-linux"><a href="/tags/#linux">linux<span>4</span></a></li> + + + <li class="tag tag-macos"><a href="/tags/#macos">macos<span>1</span></a></li> + + + <li class="tag tag-nix"><a href="/tags/#nix">nix<span>3</span></a></li> + + + <li class="tag tag-reproductible"><a href="/tags/#reproductible">reproductible<span>2</span></a></li> + + + <li class="tag tag-nixpkgs"><a href="/tags/#nixpkgs">nixpkgs<span>2</span></a></li> + + <br/> + + </ul> + </header> + + <p>This post describes how I set up a reproducible development environment in a few seconds on any Linux distribution (and potentially macOS as well).</p> + <p>🔗 <a href="https://nmattia.com/posts/2018-03-21-nix-reproducible-setup-linux-macos.html" class="link">https://nmattia.com/posts/2018-03-21-nix-reproducible-setup-linux-macos.html</a></p> + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2019-03-31-link/" title="Six Years With a Distraction-Free iPhone">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2019-03-27-link/" title="Nicolas Mattia - The 5 Raisons d'Être of Testing">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2019-03-31-link/index.html b/www/vincent.demeester.fr/legacy/posts/2019-03-31-link/index.html @@ -0,0 +1,121 @@ +<!DOCTYPE html> + +<html lang="en"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2019-03-31-link/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang=""/> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">Six Years With a Distraction-Free iPhone</h1><a href='https://vincent.demeester.fr/posts/2019-03-31-link/'></a> + <address class="signature"> + <span class="date">Sun, 31 March, 2019</span> + <span class="words">(100 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#productivity">productivity</a></li> + + + + + + <li class="tag tag-iphone"><a href="/tags/#iphone">iphone<span>1</span></a></li> + + + <li class="tag tag-distration"><a href="/tags/#distration">distration<span>1</span></a></li> + + + <li class="tag tag-mobile"><a href="/tags/#mobile">mobile<span>1</span></a></li> + + + <li class="tag tag-free"><a href="/tags/#free">free<span>1</span></a></li> + + <br/> + + </ul> + </header> + + <p>A very interesting medium story on why and how to try your own low-stress experiment. I went and did a few of what the author did, and it feels good ! Really !</p> + <p>🔗 <a href="https://medium.com/s/story/six-years-with-a-distraction-free-iphone-8cf5eb4f97e3" class="link">https://medium.com/s/story/six-years-with-a-distraction-free-iphone-8cf5eb4f97e3</a></p> + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2019-04-18-link/" title="Easy Peasy Nix Versions">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2019-03-28-link/" title="Nix: A Reproducible Setup for Linux and macOS">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2019-04-18-link/index.html b/www/vincent.demeester.fr/legacy/posts/2019-04-18-link/index.html @@ -0,0 +1,127 @@ +<!DOCTYPE html> + +<html lang="en"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2019-04-18-link/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang=""/> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">Easy Peasy Nix Versions</h1><a href='https://vincent.demeester.fr/posts/2019-04-18-link/'></a> + <address class="signature"> + <span class="date">Thu, 18 April, 2019</span> + <span class="words">(100 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#development">development</a></li> + + + + + + <li class="tag tag-development"><a href="/tags/#development">development<span>13</span></a></li> + + + <li class="tag tag-software"><a href="/tags/#software">software<span>13</span></a></li> + + + <li class="tag tag-linux"><a href="/tags/#linux">linux<span>4</span></a></li> + + + <li class="tag tag-nix"><a href="/tags/#nix">nix<span>3</span></a></li> + + + <li class="tag tag-reproductible"><a href="/tags/#reproductible">reproductible<span>2</span></a></li> + + + <li class="tag tag-nixpkgs"><a href="/tags/#nixpkgs">nixpkgs<span>2</span></a></li> + + <br/> + + </ul> + </header> + + <p>This is a convention for using third-party packages in Nix. It has a simple directory structure, makes using the packages straightforward and automatizes updates.</p> + <p>🔗 <a href="https://www.nmattia.com/posts/2019-01-15-easy-peasy-nix-versions.html" class="link">https://www.nmattia.com/posts/2019-01-15-easy-peasy-nix-versions.html</a></p> + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2019-04-19-link/" title="Stop writing broken Go libraries">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2019-03-31-link/" title="Six Years With a Distraction-Free iPhone">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2019-04-19-link/index.html b/www/vincent.demeester.fr/legacy/posts/2019-04-19-link/index.html @@ -0,0 +1,127 @@ +<!DOCTYPE html> + +<html lang="en"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2019-04-19-link/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang=""/> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">Stop writing broken Go libraries</h1><a href='https://vincent.demeester.fr/posts/2019-04-19-link/'></a> + <address class="signature"> + <span class="date">Fri, 19 April, 2019</span> + <span class="words">(100 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#development">development</a></li> + + + + + + <li class="tag tag-development"><a href="/tags/#development">development<span>13</span></a></li> + + + <li class="tag tag-software"><a href="/tags/#software">software<span>13</span></a></li> + + + <li class="tag tag-go"><a href="/tags/#go">go<span>5</span></a></li> + + + <li class="tag tag-libraries"><a href="/tags/#libraries">libraries<span>1</span></a></li> + + + <li class="tag tag-broken"><a href="/tags/#broken">broken<span>1</span></a></li> + + + <li class="tag tag-tips"><a href="/tags/#tips">tips<span>2</span></a></li> + + <br/> + + </ul> + </header> + + <p>Every single one of those libraries had some fundamental problems that made it unusable in any real world applications. Furthermore every single one of those libraries was written in a way which made fixing the issues impossible without altering the existing library APIs in a non backwards compatibile way. As the same problems unfortunately plague many other Go libraries I attempted to list most of the sins commited by their authors below.</p> + <p>🔗 <a href="https://0x46.net/thoughts/2018/12/29/go-libraries/" class="link">https://0x46.net/thoughts/2018/12/29/go-libraries/</a></p> + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2019-04-23-link/" title="Trimming the fat from a Golang binary">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2019-04-18-link/" title="Easy Peasy Nix Versions">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2019-04-23-link/index.html b/www/vincent.demeester.fr/legacy/posts/2019-04-23-link/index.html @@ -0,0 +1,124 @@ +<!DOCTYPE html> + +<html lang="en"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2019-04-23-link/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang=""/> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">Trimming the fat from a Golang binary</h1><a href='https://vincent.demeester.fr/posts/2019-04-23-link/'></a> + <address class="signature"> + <span class="date">Tue, 23 April, 2019</span> + <span class="words">(100 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#development">development</a></li> + + + + + + <li class="tag tag-development"><a href="/tags/#development">development<span>13</span></a></li> + + + <li class="tag tag-software"><a href="/tags/#software">software<span>13</span></a></li> + + + <li class="tag tag-go"><a href="/tags/#go">go<span>5</span></a></li> + + + <li class="tag tag-binary"><a href="/tags/#binary">binary<span>1</span></a></li> + + + <li class="tag tag-size"><a href="/tags/#size">size<span>1</span></a></li> + + <br/> + + </ul> + </header> + + <p>Some tips and tricks to reduce the size of a golang binary</p> + <p>🔗 <a href="https://boyter.org/posts/trimming-golang-binary-fat/" class="link">https://boyter.org/posts/trimming-golang-binary-fat/</a></p> + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2019-05-11-link/" title="As We May Kube*">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2019-04-19-link/" title="Stop writing broken Go libraries">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2019-05-11-link/index.html b/www/vincent.demeester.fr/legacy/posts/2019-05-11-link/index.html @@ -0,0 +1,127 @@ +<!DOCTYPE html> + +<html lang="en"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2019-05-11-link/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang=""/> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">As We May Kube*</h1><a href='https://vincent.demeester.fr/posts/2019-05-11-link/'></a> + <address class="signature"> + <span class="date">Sat, 11 May, 2019</span> + <span class="words">(100 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#development">development</a></li> + + + + + + <li class="tag tag-development"><a href="/tags/#development">development<span>13</span></a></li> + + + <li class="tag tag-software"><a href="/tags/#software">software<span>13</span></a></li> + + + <li class="tag tag-orchestration"><a href="/tags/#orchestration">orchestration<span>2</span></a></li> + + + <li class="tag tag-kubernetes"><a href="/tags/#kubernetes">kubernetes<span>2</span></a></li> + + + <li class="tag tag-kube"><a href="/tags/#kube">kube<span>2</span></a></li> + + + <li class="tag tag-k8s"><a href="/tags/#k8s">k8s<span>2</span></a></li> + + <br/> + + </ul> + </header> + + <p>We use different programming languages and development environments to write apps. Each language comes with a different flow and also we typically go through different phases, from prototyping to integration-level activities to incrementally adding features or fixing bugs once the app is in production. Now, the expectation of a developer coming from a “traditional” environment to Kubernetes is in general that their natural and well-known workflow changes as little as possible. This article reviews where we stand in Kubernetes-land concerning developing apps and where we may be heading.</p> + <p>🔗 <a href="https://itnext.io/as-we-may-kube-293b30c0a365" class="link">https://itnext.io/as-we-may-kube-293b30c0a365</a></p> + + </article> + <hr /> + <div class="prev-next"> + + <a class="paging-link prev" href="/posts/2019-06-28-link/" title="boring is cool | advocacy site for boring technology">← Previous post</a> + + + + <a class="paging-link next" href="/posts/2019-04-23-link/" title="Trimming the fat from a Golang binary">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/legacy/posts/2019-06-28-link/index.html b/www/vincent.demeester.fr/legacy/posts/2019-06-28-link/index.html @@ -0,0 +1,125 @@ +<!DOCTYPE html> + +<html lang="en"> + + <head> + <meta charset="utf-8"> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + + <link rel="start" href="https://vincent.demeester.fr" /> + + <title>Vincent Demeester</title> + <link rel="canonical" href="https://vincent.demeester.fr/posts/2019-06-28-link/"> + <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" /> + + <link rel="openid.server" href="https://indieauth.com/openid" /> + <link rel="openid.delegate" href="http://vincent.demeester.fr/" /> + <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"> + + <link rel="stylesheet" href="/css/screen.css" type="text/css" /> + <link rel="stylesheet" href="/css/sbrain.css" type="text/css" /> + <link rel="stylesheet" href="/css/syntax.css" type="text/css" /> + + </head> + + <body lang=""/> + + + + + + +<div id="main-container"> + <div id="page"> + <article class="post"> + <header> + <h1 class="emphnext">boring is cool | advocacy site for boring technology</h1><a href='https://vincent.demeester.fr/posts/2019-06-28-link/'></a> + <address class="signature"> + <span class="date">Fri, 28 June, 2019</span> + <span class="words">(100 Words)</span> + </address> + <ul class="tag_box inline"> + + <li class="category"><a href="/categories/#development">development</a></li> + + + + + + <li class="tag tag-development"><a href="/tags/#development">development<span>13</span></a></li> + + + <li class="tag tag-software"><a href="/tags/#software">software<span>13</span></a></li> + + + <li class="tag tag-orchestration"><a href="/tags/#orchestration">orchestration<span>2</span></a></li> + + + <li class="tag tag-kubernetes"><a href="/tags/#kubernetes">kubernetes<span>2</span></a></li> + + + <li class="tag tag-kube"><a href="/tags/#kube">kube<span>2</span></a></li> + + + <li class="tag tag-k8s"><a href="/tags/#k8s">k8s<span>2</span></a></li> + + <br/> + + </ul> + </header> + + <p>« Here, I share some thoughts and considerations around using cloud native technologies, including Kubernetes, observability tools such as Prometheus, service meshes, and serverless offerings. »</p> + <p>🔗 <a href="http://boringis.cool/" class="link">http://boringis.cool/</a></p> + + </article> + <hr /> + <div class="prev-next"> + + + + <a class="paging-link next" href="/posts/2019-05-11-link/" title="As We May Kube*">Next post →</a> + + </div> + + </div> +</div> + +<footer> + <nav> + + <a href="/">home</a> + <span class="text-muted"> | </span> + + <a href="/about">about</a> + <span class="text-muted"> | </span> + + <a href="/archive">archive</a> + <span class="text-muted"> | </span> + + <a href="/categories">categories</a> + <span class="text-muted"> | </span> + + <a href="/tags">tags</a> + <span class="text-muted"> | </span> + + <a href="https://twitter.com/vdemeest">twitter</a> + <span class="text-muted"> | </span> + + <a href="https://github.com/vdemeester">github</a> + <span class="text-muted"> | </span> + + <a href="https://vincent.demeester.fr/index.xml">rss</a> + </nav> + <br/> + <address> + <span class="copyright"> + Content and design by Vincent Demeester + (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>) + </span><br /> + <span class="engine"> + Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a> + </span> + </address> +</footer> +</body> + diff --git a/www/vincent.demeester.fr/posts/2012-05-07-reinit-and-jekyll.org b/www/vincent.demeester.fr/posts/2012-05-07-reinit-and-jekyll.org @@ -0,0 +1,61 @@ +#+title: Reinit and Jekyll +#+date: <2012-05-07 Mon> +#+filetags: website jekyll +#+setupfile: ../templates/post.org + +* Introduction + +Two weeks ago, my /online/ personal server has been attacked and, +somehow, died. I'm in the process of re-installation of it but I'm going +to hardened a bit the security on it. Anyway, this crash meant that +every piece of site I maintain has been down. That's why I moved this +/identity site/ on the github pages, using a CNAME ; That way I can +crash as much as I want my server(s), this page should still be up for a +while. + +And I'm switching on Jekyll for this website as It is supported by +Github page, easy to use and easy to deploy elsewhere (if one day I want +to move from Github). + +The rest of the post is going to be used as a /sandbox/ post to test the +site styles. + +#+BEGIN_QUOTE + This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, + consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. + Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. + + Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse + id sem consectetuer libero luctus adipiscing. +#+END_QUOTE + +** Highlight + :PROPERTIES: + :CUSTOM_ID: highlight + :ID: 6e1687a1-6898-4fe1-ac56-53b6e1e1f310 + :END: + +#+begin_src ruby +def foo puts 'foo' end +#+end_src + +Some bash script... + +#+begin_src bash +#!/bin/bash +update_gems() { + echo "Update gems for all versions ? (y/N)" + read UPDATE_GEMS + test -z "${UPDATE_GEMS}" && UPDATE_GEMS="n" + + if test "${UPDATE_GEMS}" = "y"; then + for version in =ls --color=never $HOME/.rbenv/versions=; do + echo "Updating ${version%/}" + RBENV_VERSION="${version%/}" rbenv exec gem update + RBENV_VERSION="${version%/}" rbenv exec gem install bundler + done + fi +} + +update_gems +#+end_src diff --git a/www/vincent.demeester.fr/posts/2012-05-08-gitolite-quick-and-dirty-mirror.org b/www/vincent.demeester.fr/posts/2012-05-08-gitolite-quick-and-dirty-mirror.org @@ -0,0 +1,126 @@ +#+title: Gitolite quick and dirty mirror +#+date: <2012-05-08 Tue> +#+filetags: gitolite git linux mirror github +#+setupfile: ../templates/post.org + +* Introduction + +I'm running a gitolite _instance_ on my personal server to manage my repositories +(personnal, private or public) ; and I am quickly going to share with you how I setup a +_quick and dirty_ mirror feature. + +First, I am using **gitolite 3**. The mirroring we are going to setup is not the +_supported_ [[http://sitaramc.github.com/gitolite/mirroring.html][mirroring *built-in*]]. We are going to implement a simplier way to set mirror +thing : + +1. Write a custom gitolite command ; the idea is to be able to write ~git-config~ stuff. +2. Write a hook that take a specific ~git-config~ (let say ~mirror.url~) and do a simple + mirroring. + +* Gitolite commands + +Gitolite 3 has been rewritten to be more flexible : [[http://sitaramc.github.com/gitolite/g3why.html][Why a completely new version]]. The +rewrite made it really easy to extend gitolite. +I've fork [[https://github.com/vdemeester/gitolite][gitolite]] on github+ I've +created a [[http://github.com/vdemeester/vdemeester-gitolite-local-code][repository git]] to easily add commands to my gitolite instance via _local +code_. The gitolite command I wrote is a quick and dirty script in shell to add ~git +config~. The source should speek for itself ; It _should_ include some way to check if the +given config is not already present in the ~gitolite-admin~ configuration file — and so +might be rewritten in ~Perl~. + +The command is ~write-git-config~ because a ~git-config~ command already exists +in the built-in commands. + +#+begin_src bash +#!/bin/sh + +# Usage: ssh git@host write-git-config <repo> <key> <value> +# +# Set git-config value for user-created ("wild") repo. + +die() { echo "$@" >&2; exit 1; } +usage() { perl -lne 'print substr($_, 2) if /^# Usage/../^$/' < $0; exit 1; } +[ -z "$1" ] && [ -z "$2" ] && [ -z "$3" ] && usage +[ "$1" = "-h" ] && usage +[ -z "$GL_USER" ] && die GL_USER not set + +# ---------------------------------------------------------------------- +repo=$1; shift +key=$1; shift +value=$1; shift + +# this shell script takes arguments that are completely under the user's +# control, so make sure you quote those suckers! + +if gitolite query-rc -q WRITER_CAN_UPDATE_DESC +then + gitolite access -q "$repo" $GL_USER W any || die You are not authorised +else + gitolite creator "$repo" $GL_USER || die You are not authorised +fi + +# if it passes, $repo is a valid repo name so it is known to contain only sane +# characters. This is because 'gitolite creator' return true only if there +# *is* a repo of that name and it has a gl-creator file that contains the same +# text as $GL_USER. + +configfile=`gitolite query-rc GL_REPO_BASE`/"$repo".git/config + +git config --file "$configfile" "$key" "$value" +#+end_src + +* Gitolite hooks + +The next step is to write a quick ~post-receive~ hook that check if there is a +certain ~git-config~ entry and run ~git push --mirror~. The file is in +~$HOME/.gitolite/hooks/common/post-receive~ ; you could add a better system to +hooks (to be able to add "dynamic" hooks, …). + +#+begin_src bash + +#!/bin/sh + +# Simple gitolite mirroring + +# flush STDIN coming from git, because gitolite's own post-receive.mirrorpush +# script does the same thing +[ -t 0 ] || cat >/dev/null + +[ -z "$GL_REPO" ] && die GL_REPO not set + +target=`git config --get mirror.url` +[ -z "$target" ] && exit 0 + +# Support a REPO variable for wildcard mirrors +gl_repo_escaped=$(echo $GL_REPO | sed 's/\//\\\//g') +target=$(echo $target | sed -e "s/REPO/$gl_repo_escaped/g") + +# Do the mirror push +git push --mirror $target +#+end_src + +The next, and final step is to run `gitolite compile` to update links to hooks +for every repositories. + +* For real + +And finaly, this is the final step you'll do. + +#+begin_src bash +$ ssh git@host write-git-config vincent/vcsh-home mirror.url git@github.com:vdemeester/vcsh-home.git +$ git push +Counting objects: 5, done. +Delta compression using up to 2 threads. +Compressing objects: 100% (3/3), done. +Writing objects: 100% (3/3), 294 bytes, done. +Total 3 (delta 2), reused 0 (delta 0) +remote: To git@github.com:vdemeester/vcsh-home.git +remote: 65681a8..701c990 master -> master +To git@host:vincent/vcsh-home.git + 65681a8..701c990 master -> master +#+end_src + + +And that should be it ! + +_Update 2012/10/04_ : Moved from gitolite fork to _gitolite local code_ +repository. diff --git a/www/vincent.demeester.fr/posts/2012-05-13-jekyll-foreman-guard-bundler.org b/www/vincent.demeester.fr/posts/2012-05-13-jekyll-foreman-guard-bundler.org @@ -0,0 +1,90 @@ +#+title: Jekyll Forman Guard Bundler +#+date: <2012-05-13 Sun> +#+filetags: jekyll ruby bundler guard foreman +#+setupfile: ../templates/post.org + +* Introduction + +This post is a quick "How did I setup my Jekyll environnement ?". We are +going all the tools that are quite awesome in Ruby. + +* Goal + :PROPERTIES: + :CUSTOM_ID: goal + :END: + +The goal is simple : + +1. I want to be able to install any dependent + [[http://rubygems.org][Gem]] with a /on-liner/ command +2. I want to be able to run a /Jekyll server/ that auto updates. + +We are going to play with : [[http://gembundler.com/][Bundler]], +[[https://github.com/guard/guard][Guard]] and +[[https://github.com/ddollar/foreman][foreman]]. + +* Bundler + :PROPERTIES: + :CUSTOM_ID: bundler + :END: + +Bundler let us run =bundle install= to get all Ruby Gems we will need ; +It use a file name =Gemfile=. The gems we need are simple : =jekyll=, +=guard= and some Guard extensions. + +#+begin_src ruby +source "http://rubygems.org" + +gem 'jekyll' +gem 'guard' +gem 'guard-jekyll2' +gem 'guard-shell' +gem 'guard-bundler' +#+end_src + +* Guard + :PROPERTIES: + :CUSTOM_ID: guard + :END: + +#+BEGIN_QUOTE + Guard is a command line tool to easily handle events on file system + modifications. +#+END_QUOTE + +Guard will be watching file we told him and run action in consequence ; +The file is name =Guardfile=. + +#+begin_src ruby +guard 'jekyll2' do + watch %r{.*} +end + +guard :bundler do + watch('Gemfile') +end +# vim:filetype=ruby +#+end_src + +* Foreman + :PROPERTIES: + :CUSTOM_ID: foreman + :END: + +Finally, foreman will let us declare our processes and will handle the +start, forward the output and handle the shutdown. It can then export +its configuration into more /production-ready/ file (=init=, =upstard=, +...) ; It uses a file named =Procfile=. + +We will tell foreman to run : + +- The jekyll build-in server : =jekyll --server= +- Guard, to handle file changes /in background/. + +#+begin_src bash +web: bundle exec jekyll --server +guard: bundle exec guard +#+end_src + +And that's all folk. Now, you just need to run foreman in the +Jekyll-powered directory and edit your files. diff --git a/www/vincent.demeester.fr/posts/2012-07-23-maven-release-gitflow.org b/www/vincent.demeester.fr/posts/2012-07-23-maven-release-gitflow.org @@ -0,0 +1,99 @@ +#+title: Maven Release Gitflow +#+date: <2012-07-23 Mon> +#+filetags: maven java git gitflow release +#+setupfile: ../templates/post.org + +* Introduction + +I like a lot the [[http://nvie.com/posts/a-successful-git-branching-model/][gitflow]] way of managing project. When working on maven project, there is +few great plugins that helps to get the work done. One of them is [[http://maven.apache.org/plugins/maven-release-plugin][maven-release-plugin]]. + +Inspired on this [[https://gist.github.com/1043970][gist]], I've come +with a cool way of doing things (let say we want to release a 0.1 +version of an artifact) : + +* Prepare the pom.xml. + :PROPERTIES: + :CUSTOM_ID: prepare-the-pom.xml. + :END: + +It needs =<scm>= entries, =<distributionManagement>= entries (to know +where to deploy the release artifact) and few options for the +maven-release-plugin : + +{{< highlight xml >}} +#+begin_src xml +<project> + + <!-- […] --> + <build> + <plugins> + <!-- […] --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-release-plugin</artifactId> + <version>2.3.2</version> + <configuration> + <tagNameFormat>v@{project.version}</tagNameFormat> + <pushChanges>false</pushChanges> + <localCheckout>true</localCheckout> + </configuration> + </plugin> + <!-- […] --> + </plugins> + </build> + <!-- […] --> + +</project> +#+end_src + +Few explanation here : + +- =tagNameFormat= is here to change the default tag name (which is + =${project.artifactId}-${project.version}=) to a better one. +- =pushChanges= set to =false= tells maven-release-plugin not to push + changes (this will become useful) +- =localCheckout= set to =true= tells maven-release-plugin to clone from + local repository (not distant). This is especially useful here because + we didn't push anything (so not setting this option would result in a + failure). + +* The real stuff + :PROPERTIES: + :CUSTOM_ID: the-real-stuff + :END: + +First create a release branch from develop. + +#+begin_src bash +$ git checkout -b release/v0.1 develop +#+end_src + +Then run the maven release stuff. + +#+begin_src bash +$ mvn release:prepare # change the pom, commit and tag version, and + # re-change pom (by incrementing SNAPSHOT version) +$ mvn release:perform # get the tagged version, compile and deploy +#+end_src + +And the real fun begins. + +#+begin_src bash +$ git checkout develop # get back to the develop branch +$ git merge --no-ff release/v0.1 # merge the version back into develop +$ git checkout master # go to the master branch +$ git merge --no-ff release/v0.1~1 # merge the version back into master but + # the tagged version instead of the release/v0.1 HEAD +$ git branch -D release/v0.1 # Removing the release branch +$ git push --all && git push --tags # Finally push everything +#+end_src + +The real magic here is the =git merge --no-ff release/v0.1~1= which will +merge into master the commit before the HEAD of the branch +=release/v0.1=. + +The next step would be to create a helper script that automates this and +verify that the =pom.xml= has the right configuration options. + +*Edit 17:58* : You can take a look [[https://github.com/vdemeester/java-config/blob/master/bin/mvn-release-flow][here]] diff --git a/www/vincent.demeester.fr/posts/2012-12-16-gollum-comme-wiki-personnel.org b/www/vincent.demeester.fr/posts/2012-12-16-gollum-comme-wiki-personnel.org @@ -0,0 +1,84 @@ +#+title: Gollum comme Wiki personnel +#+date: <2012-12-16 Sun> +#+filetags: wiki golum github personnel +#+setupfile: ../templates/post.org + +* Introduction + +Il y a environ 4 mois j'ai eu un accident de vélo ; un traumatisme +crânien, des brulures sur la face, quelques points de sutures, un doigt +cassé et une hernie discale m'ont cloué (et me clou encore) plus que +d'habitude sur ma chaise de bureau. Le bon côté des choses, c'est que +cela m'a permit de me poser et de réfléchir une bonne façon d'être +efficace et organiser, au travail et à la maison :-). + +Une des principales /action/ que j'ai pris est d'utiliser un wiki local +et synchronisé sur /tout/ mes PCs. Le /format/ wiki est assez adapté à +une prise de note et à la création de contenu plus complet (comme des +[[http://shortbrain.org][articles]] ou de la documentation pour des +projets en cours). Les conditions étaient les suivantes : + +- Facilité de mise en place. +- Pas de base de données. +- /Merging/ facile ([[http://git-scm.com][git]] /rules my world/). +- [[http://daringfireball.net/projects/markdown/][Markdown]] comme + syntaxe, car utilisé à peu près partout (blogs, articles, READMEs, + documentations). +- Éditable à partir d'une interface web ou de mon éditeur favoris. + +L'outil qui remplit presque toutes ces conditions s'appelle +[[https://github.com/github/gollum][gollum]]. C'est un moteur wiki, +écris en ruby, qui se base sur un repository +[[http://git-scm.com][git]]. Il est développer par l'équipe de +[[http://github.com][Github]] et c'est celui qui est utilisé par les +pages wiki là-bas. Il permet d'utiliser à peu près n'importe quel +syntaxe (dont +[[https://github.com/github/github-flavored-markdown][github-markdown]] +qui est assez proche de celle de +[[http://johnmacfarlane.net/pandoc][pandoc]]). Par ailleurs, comme il se +base sur [[http://git-scm.com][git]], les points /"pas de base de +données"/, /"merging facile"/ et /"éditable également à partir de mon +éditeur favoris"/ sont toutes remplies. + +Avec [[https://github.com/github/gollum][Gollum]] vous avez un wiki +markdown décentralisé, éditable via une interface web ou via votre +éditeur favoris. + +* Mise en place + :PROPERTIES: + :CUSTOM_ID: mise-en-place + :END: + +La mise en place est relativement simple ; après tout dépend du besoin +que vous avez. L'installation se fait par [[file:rubygems.org][RubyGem]] +ou en clonant le repository. + +{{< highlight bash >}} # Installation de gollum et du format markdown de +github $ gem install gollum gitub-markdown {{< /highlight >}} + +Si vous n'utilisez pas [[https://github.com/sstephenson/rbenv][rbenv]] +ou [[https://rvm.io/][rvm]] il est probable qu'il faille lancer la +commande en root ou utiliser sudo. + +Ensuite, il suffit de lancer +[[https://github.com/github/gollum][Gollum]] dans un dossier qui est un +repository git ; le tour est joué + +{{< highlight bash >}} # J'ulitise ~/desktop/wiki pour mon wiki $ cd +~/desktop/wiki && gollum {{< /highlight >}} + +L'idée finale est d'automatiser deux choses : + +1. Le démarrage de gollum +2. La synchronisation du repository avec les différents autres /remotes/ + +Suivant le système d'exploitation et/ou la distribution utilisées, il y +a énormément de possibilité d'effectuer cette automatisation. Dans mon +cas, j'ai une [[http://debian.org][Debian]] assez light, avec surtout +plein de scripts. Je démarre donc +[[https://github.com/github/gollum][Gollum]] au démarrage de ma session +grâce à une script qui est lancé dans la foulée du gestionnaire de +fenêtre. La synchronisation se fait grâce à une tâche planifiée /cron/ +qui est "distribué" sur chacune de mes machines. + +/C'est tout pour le moment/ ;-). diff --git a/www/vincent.demeester.fr/posts/2013-09-08-maven-tmpfs.org b/www/vincent.demeester.fr/posts/2013-09-08-maven-tmpfs.org @@ -0,0 +1,158 @@ +#+title: Maven Tmpfs +#+date: <2013-09-08 Sun> +#+filetags: maven tmpfs ssd +#+setupfile: ../templates/post.org + +* Introduction + +Je suis un utilisateur convaincu de [[http://maven.apache.org/][maven]], malgré ces défauts, le +moto *"Convention over configuration"* me va vraiment bien. Que ce soit +au boulot ou à la maison, j'ai plus d'ordinateurs équipés de ssd (ou de +mémoire flash) que de disque traditionnel (mécanique ?). Pour augmenter +un peu la durée de vie de ces disques SSD, j'ai cherché à savoir comment +/déporter/ le /build/ de maven (qui, pour rappel, se passe dans le +dossier =target/=) hors du SSD ; ici ce sera dans le dossier =/tmp/= qui +est monté en mémoire (merci =tmpfs=), mais on peut imaginer déporter ça +sur un autre disque, etc.. Après quelques recherches j'ai trouvés +quelques inspirations. + +#+BEGIN_QUOTE + *Limitations* + + Dans la solution présentée ci-dessous les principales limitations sont + les suivantes (que j'essaierais de diminuer au fil du temp ;P) : + + 1. Il est nécessaire de modifier le pom.xml du projet ; cela ne + s'appliquera donc pas à tous les projets maven sans modification du + pom.xml. + 2. Cela ne fonctionne que sur une plateforme qui support les liens + symboliques (Linux, Mac OS X, et autre UNIX). + 3. Cela ne fonctionne qu'avec Java 7 ou plus. + 4. Si vous utilisez m2e, il va gentillement gueuler et c'est moche ; pour résoudre le + problème, il faut faire un tour vers [[http://wiki.eclipse.org/M2E_plugin_execution_not_covered][M2E plugin execution not covered]]. +#+END_QUOTE + +Pour [[http://maven.apache.org/][maven]], le dossier =target/= vient de la propriété +=project.build.directory=. Dans la théorie, il suffirait de modifier +(dans =$HOME/.m2/settings.xml=) cette propriété et le tour serait jouer. +Malheuresement ce n'est pas possible, =project.build.directory= est une +propriété interne et n'est, à priori, pas modifiable. + +Notre souhait est le suivant : + +1. Le build doit se faire dans =/tmp/m2/=, ce qui pour un projet se + traduit par =/tmp/m2/${groupId}:${artifactId}=. +2. Le dossier =target/= dans les sources est un lien symbolique vers le + dossier dans =/tmp/m2/= +3. On passe par un *profile* qui n'est *pas actif* par défaut (pour ne + pas faire chier le monde) mais *activable via une propriété* (maven + nous permet de le faire et c'est cool =^_^=). La propriété utilisée + sera =external.build.root=. + +Le code ci-dessous est repris directement de mon inspiration[fn:1]. Il +s'occupe de créer le dossier =${groupId}:${artifactId}= dans +=external.build.root= et de faire le lien dans le dossier courant. + +#+begin_src xml +<project> + <!-- […] --> + <profiles> + <profile> + <id>external-build-dir</id> + <activation> + <activeByDefault>false</activeByDefault> + <property> + <name>external.build.root</name> + </property> + </activation> + <build> + <plugins> + <plugin> + <groupId>com.alexecollins.maven.plugin</groupId> + <artifactId>script-maven-plugin</artifactId> + <version>1.0.0</version> + <executions> + <execution> + <id>prep-work-tree</id> + <goals> + <goal>execute</goal> + </goals> + <phase>initialize</phase> + <configuration> + <script> + import java.nio.file.* + def dir = + "${external.build.root}/${project.groupId}:${project.artifactId}" + println "using Maven dir ${dir}" + def dirPath = Paths.get(dir) + if (!Files.exists(dirPath)) { + Files.createDirectories(dirPath) + } + def target = Paths.get("${project.build.directory}") + if (!Files.exists(target)) { + Files.createSymbolicLink(target, dirPath) + }</script> + </configuration> + </execution> + <execution> + <id>drop-symlink</id> + <goals> + <goal>execute</goal> + </goals> + <phase>clean</phase> + <configuration> + <script> + import java.nio.file.* + def target = Paths.get("${project.build.directory}") + if (Files.isSymbolicLink(target)) { + Files.delete(target) + } + </script> + </configuration> + </execution> + </executions> + <dependencies> + <dependency> + <groupId>org.codehaus.groovy</groupId> + <artifactId>groovy</artifactId> + <version>1.8.6</version> + </dependency> + </dependencies> + <configuration> + <language>groovy</language> + </configuration> + </plugin> + </plugins> + </build> + </profile> + </profiles> + <!-- […] --> +</project> +#+end_src + +Ainsi, il suffit ensuite d'avoir quelques choses du genre dans son +=$HOME/.m2/settings.xml= pour que les builds qui ont ce profil se +/build/ dans =/tmp/m2/=. On peut aussi ne rien avoir dans +=$HOME/.m2/settings.xml= et utilise =-Dexternal.build.root=/tmp/m2/= +avec la commande =mvn=. + +#+begin_src xml +<settings> + <!-- […] --> + <profiles> + <profile> + <id>build-in-ramfs</id> + <properties> + <external.build.root>/tmp/m2/</external.build.root> + </properties> + </profile> + </profiles> + <activeProfiles> + <activeProfile>build-in-ramfs</activeProfile> + </activeProfiles> + <!-- […] --> +</settings> +#+end_src + + +[fn:1] [[http://elehack.net/writings/programming/maven-target-in-tmpfs][PuttingMaven build directories out-of-tree]] par [[http://elehack.net/][Michal Ekstrand]] diff --git a/www/vincent.demeester.fr/posts/2013-10-12-podcasts.org b/www/vincent.demeester.fr/posts/2013-10-12-podcasts.org @@ -0,0 +1,91 @@ +#+TITLE: Podcasts +#+date: <2013-10-12 Sat> +#+filetags: music podcast +#+setupfile: ../templates/post.org + +* Introduction + +#+BEGIN_QUOTE + Voici un petit billet présentant les différents podcast que j'écoute + plus ou moins régulièrement. +#+END_QUOTE + +J'écoute énormément de musique et de podcast ; je passe beaucoup de +temps avec des écouteurs sur la tête ou la chaine hifi en route. Les +podcasts ont une grande place. Voici une liste plus ou moins bien triés +de ceux auxquels je suis souscrit et/ou que j'écoute en ce moment. Je +tiendrais peut-être ce post à jour ou en créerait un nouveau sinon :-). + +* Geek & co + :PROPERTIES: + :CUSTOM_ID: geek-co + :END: + +Je suis un developpeur, un geek et convaincu des logiciels libres, les +podcasts qui suivent reflète assez cette partie là de mon identité. + +- [[http://www.captainweb.net/][L'apéro de Captain (fr)]] : dans le + genre geeky, déjanté et sauvage, en français, on ne fait pas mieux. + Pas vraiment safe for work, et pas tout à fait "libriste" comme + j'aime, les tranches de rires sont garanties ; même si parfois on est + un peu verreux d'avoir écouter jusqu'à la fin et le bien nommé + "wazzuf". +- [[http://www.agencetousgeeks.com/][Agence Tous Geeks (fr)]] : fils + caché de l'apéro du captain, on y retrouve des amis (et membres de ce + dernier), mais c'est un peu plus calme. +- [[http://bazingcast.com/about/][Bazingcast (fr)]] : podcast geek, plus + posé que les deux précédents mais avec des débats, des trolls et tout + ce que l'on peut attendre de geeks. +- [[http://www.captainposix.net/][Parole de Tux (fr-be)]] : podcast + venant de nos voisin belge, pas trop long et plutôt sympa ; et si + comme moi vous adorez l'accent belge, c'est le top. +- [[http://faif.us/][Free as in Freedom (en)]] : podcast parlant de + logiciel libre principalement côté license, truc légal, etc.. en + anglais, faut parfois s'accrocher. +- [[http://episodes.gitminutes.com/][Git Minutes (en)]] : podcast à + propos de Git et des outils de son écosystème (vcsh, etc..). + +* Radio + :PROPERTIES: + :CUSTOM_ID: radio + :END: + +J'aime bien la radio, bien plus que la télévision (que j'allume si peu +qu'à chaque fois la box se met à jour =;-p=). + +- [[http://www.franceinter.fr/emission-laura-leishman-project][France + Inter - LLP (Laura leishman Project)]] +- [[http://radiofrance-podcast.net/podcast09/rss_12265.xml][Le Mouv' - + Laura Leishman Project]] +- [[http://www.franceinter.fr/emission-interception][France Inter - + Interception]] +- [[http://www.la-bas.org/][France Inter - là-bas si j'y suis]] +- [[http://www.franceculture.fr/podcast/4689840][France Culture - + Pixel]] +- [[http://www.franceculture.fr/podcast/4685228][France Culture - Place + de la toile]] +- [[http://www.franceculture.fr/podcast/4689418][France Culture - + Philippe Meyer]] +- [[http://radiofrance-podcast.net/podcast09/rss_12582.xml][Le Mouv' - + Glitch (sur le Mouv')]] +- [[http://radiofrance-podcast.net/podcast09/rss_12691.xml][Le Mouv' - + Code Source]] +- [[http://radiofrance-podcast.net/podcast09/rss_12190.xml][Le Mouv' - + Suivez le geek]] +- [[http://www.divergence-fm.org/-http-www-divergence-fm-org-ecrire-exec-rubrique-id_rubrique-61-.html][Divergence + Numerique]] + +* Musique + :PROPERTIES: + :CUSTOM_ID: musique + :END: + +Les podcast suivant sont purement musique, tous musique techno ou trance +(que j'aime bien). + +- [[http://podcasts.flaix.fr/corstencountdown][Ferry Corsten - Corsten + Countdown]] +- [[http://www.galexmusic.com/podcast/gareth.xml][Gareth Emery Podcast]] +- [[http://feedproxy.feedburner.com/Tiestos_club_life][Tiesto Club + Life]] +- [[http://oakenfold.libsyn.com/rss][Oakenfold Perfecto Podcast]] diff --git a/www/vincent.demeester.fr/posts/2014-03-24-redesign-et-résolutions.org b/www/vincent.demeester.fr/posts/2014-03-24-redesign-et-résolutions.org @@ -0,0 +1,76 @@ +#+title: Redesign et Résolutions +#+date: <2014-03-24 Mon> +#+filetags: jekyll design images redesign +#+setupfile: ../templates/post.org + +* Introduction + +Un /tout petit/ post pour parler rapidement, entre autre, du redesign de +[[http://vincent.demeester.fr][vincent.demeester.fr]] et de mes +résolutions. + +* Redesign + :PROPERTIES: + :CUSTOM_ID: redesign + :END: + +Les raisons de ce redesign sont assez simple : je change d'employeur et +de ville (retour sur Paris =\o/=). Cela fait pas mal de changement, et +en voulant mettre à jour la page d'accueil (ce que je n'ai toujours pas +fait =;-P=), j'avais envie de /déménager/ le site en quelques sortes. + +Pour l'inspiration c'est assez facile à trouver, regarder +[[http://medium.com][medium]], le site de +[[http://www.viksit.com/][Viksit Gaur]] ou encore celui de +[[http://silent-strength.com/][Michael]] (coucou =:-P=). J'adore ce +genre de site, assez épuré mais avec une partie fixe (à gauche ou à +droite) et avec des images changeantes. + +Les images en fond de l'espace de gauche peuvent changer d'une page à +l'autre. Je suis en train de m'amuser un peu avec +[[http://jekyllrb.com][Jekyll]]. Il faut que je trouve un moyen +d'optimiser un peu les images que j'utilise parce que là je joue un peu +le bourrin. + +* Résolutions + :PROPERTIES: + :CUSTOM_ID: résolutions + :END: + +Nouvelle année, nouveau boulot, implique nouvelles résolutions. Il y a +deux aspects à ces /résolutions/ : sur un plan +informatique/geek/travail/organisation, et sur un plan purement +/physique/. + +Mon retour sur Paris devrait me permettre de participer à un peu plus +d'évènements, notamment du côté des Java User Group, mais aussi FSFE, et +j'en passe. Je compte également continué à jouer le /factotum/ en me +gardant un peu de temps au niveau personnel pour travailler sur des +aspects /geek/ que je n'aurais peut-être pas l'occasion de pratiquer au +travail. Au niveau de mes /points d'entrées/ sur le web, je compte +remettre un peu en route la partie blog de ce site et tourner +shortbrain.org (ou autre) en un site plus "documentation" en me basant +sur mes notes (powered by [[http://org-mode.org][org-mode]]). Je me suis +également remis à [[https://www.gnu.org/software/emacs/][Gnu Emacs]] et +/oh god/ qu'est-ce que c'est bon =:-D=. + +Au niveau phyisque, dans la continuité de la fin 2012, l'année 2013 a +probablement été la pire de ma vie. Les problèmes de dos c'est pas +facile tous les jours.. En février, j'avais peur de ne jamais remonter +sur un vélo ; en Mai je remontais pour la première fois sur le vélo, en +Septembre je faisais 40km par semaine et en décembre j'étais presque +capable de faire 30km en une journée. Mème si l'année 2014 a mal +commencé (je me suis felé un côte en Janvier), mes objectifs pour cette +année et les futurs sont nettement plus positif. Le +[[http://fitbit.com][fitbit]] que j'ai acquis en Février me permet +d'avoir un objectif de marche (certes assez modeste) de 10000 pas et +8,05 km par jour. Je compte bien me fixer d'autre objectifs : monter la +barre plus haut (15000 voir plus), faire 50km de vélo sur une journée. + +Les objectifs physiques à très long termes sont également assez simples +: retrouver ma forme physique et ne plus être géné à cause du dos. En +gros c'est : être capable de faire 120km de vélo et monter des cols (Col +du Sapenay, Mont Revard) et à long terme, faire des courses de footing +(i.e. 10km, Paris-Versaille, Semi-marathon, marathon :D). + +Sur ce, je vais retourner à mes cartons :-P. diff --git a/www/vincent.demeester.fr/posts/2018-07-28-gotest-tools-intro.org b/www/vincent.demeester.fr/posts/2018-07-28-gotest-tools-intro.org @@ -0,0 +1,45 @@ +#+TITLE: Golang testing — gotest.tools introduction +#+date: <2018-07-28 Sat> +#+filetags: feature go testing +#+setupfile: ../templates/post.org + +* Introduction + +I already wrote 2 previous posts about golang and testing. It's something I care deeply +about and I wanted to continue writing about it. It took me a bit more time than I +thought, but getting back to it. Since the [[http://vincent.demeester.fr/posts/2017-04-22-golang-testing-golden-file/][last post]], Daniel Nephin and I worked (but +mainly Daniel 🤗) on bootstrapping a testing helper library. + +Let me introduce it to you this library : [[https://gotest.tools][=gotest.tools=]]. As described in the [[https://godoc.org/gotest.tools][godoc]] package comment, =gotest.tools= is a +collection of packages to augment =testing= and support common patterns. It's an enhanced and growing version of the +initial helpers we (the docker/moby maintainers) wrote initially in [[https://github.com/docker/docker][=docker/docker=]] repository. We are using in quite some +project here at [[https://github.com][Docker]]. + +There is a bunch of packages that will all have their own post (linked here when available) : + +- [[file:2018-08-16-gotest-tools-assertions.org][=assert=]] (with =assert/cmp= and =assert/opt=) that provides assertions for comparing expected values to actual values. +- =env= that provides functions to test code that read environment variable or the current working directory. +- [[file:2018-09-14-gotest-tools-fs.org][=fs=]] that provides tools for creating temporary files, and testing the contents and structure of a directory. +- [[file:2018-09-06-gotest-tools-golden.org][=golden=]] that provides tools for comparing large multi-line strings. +- [[file:2018-09-18-gotest-tools-icmd.org][=icmd=]] that executes binaries and provides convenient assertions for testing the results. +- [[file:2019-03-23-gotest-tools-poll.org][=poll=]] that provides tools for testing asynchronous code. +- [[file:2018-09-01-gotest-tools-skip.org][=skip=]] that provides functions for skipping a test and printing the source code of the condition used to skip the test. + +There is also experimental package, using the =x= notation (as the golang team uses, for example with =golang.org/x/sync=) : + +- =x/subtest= that provides a =TestContext= to subtests which handles cleanup and provides a =testing.TB= and =context.Context=. + +There is already some good =testing= helpers in the Go ecosystem : [[https://github.com/stretchr/testify][=testify=]], [[http://labix.org/gocheck][=gocheck=]], [[https://github.com/onsi/ginkgo][=ginkgo=]] and a lot more — so +why create a new one ? There is multiple reason for it, most of them can be seen in the following [[https://github.com/gotestyourself/gotest.tools/issues/49#issuecomment-362436026][GitHub issue]]. + +[[https://github.com/dnephin/][Daniel]] also wrote a very useful converter if your code base is currently using =testify= : =gty-migrate-from-testify=. + +#+BEGIN_SRC sh +$ go get -u gotest.tools/assert/cmd/gty-migrate-from-testify +# […] +$ go list \ + -f '{{.ImportPath}} {{if .XTestGoFiles}}{{"\n"}}{{.ImportPath}}_test{{end}}' \ + ./... | xargs gty-migrate-from-testify +#+END_SRC + +In the next post, let's dig into the assertion part of the library, package =assert= 👼. diff --git a/www/vincent.demeester.fr/posts/2018-08-16-gotest-tools-assertions.org b/www/vincent.demeester.fr/posts/2018-08-16-gotest-tools-assertions.org @@ -0,0 +1,340 @@ +#+title: Golang testing — gotest.tools assertions +#+date: <2018-08-16 Thu> +#+filetags: go testing assert +#+setupfile: ../templates/post.org + +#+TOC: headlines 2 + +* Introduction + +Let's take a closer look at [[https://gotest.tools][=gotest.tools=]] assertions packages. This is mainly about =assert=, =assert/cmp= and +=assert/opt=. + +#+BEGIN_QUOTE +Package assert provides assertions for comparing expected values to actual values. When assertion fails a helpful error +message is printed. +#+END_QUOTE + +There is two main functions (=Assert= and =Check=) and some helpers (like =NilError=, …). They all take a =*testing.T= as +a first argument, pretty common across testing Go libraries. Let's dive into those ! + +* =Assert= and =Check= + +Both those functions accept a =Comparison= (we'll check what it is later on) and fail the test when that comparison +fails. The one difference is that =Assert= will end the test execution at immediately whereas =Check= will fail the test +and proceed with the rest of the test case. This is similar to =FailNow= and =Fail= from the standard library +=testing=. Both have their use cases. + +We'll Use =Assert= for the rest of the section but any example here would work with =Check= too. When we said +=Comparison= above, it's mainly the [[https://godoc.org/gotest.tools/assert#BoolOrComparison][BoolOrComparison]] interface — it can either be a boolean expression, or a +[[https://godoc.org/gotest.tools/assert/cmp#Comparison][cmp.Comparison]] type. =Assert= and =Check= code will be /smart/ enough to detect which one it is. + +#+BEGIN_SRC go + assert.Assert(t, ok) + assert.Assert(t, err != nil) + assert.Assert(t, foo.IsBar()) +#+END_SRC + +So far not anything extra-ordinary. Let's first look at some more /helper/ functions in the =assert= package and quickly +dive a bit deeper with =Comparison=. + +* More =assert= helpers + +The additional helper functions are the following + +- =Equal= uses the ==== operator to assert two values are equal. +- =DeepEqual= uses =google/go-cmp= to assert two values are equal (it's /close/ to =reflect.DeepEqual= but not + quite). We'll detail a bit more the /options/ part of this function with =cmp.DeepEqual=. +- =Error= fails if the error is =nil= *or* the error message is not the expected one. +- =ErrorContains= fails if the error is =nil= *or* the error message does not contain the expected substring. +- =ErrorType= fails if the error is =nil= *or* the error type is not the expected type. +- =NilError= fails if the error is not =nil=. + +All those helper functions have a equivalent function in the =cmp= package that returns a =Comparison=. I, personally, +prefer to use =assert.Check= or =assert.Assert= in combination with =cmp.Comparison= as it allows me to write all my +assertions the same way, with built-ins comparison or with my own — i.e. =assert.Assert(t, is.Equal(…), "message"= or +=assert.Assert(t, stackIsUp(c, time…), "another message")=. + +* =cmp.Comparison= + +This is where it get really interesting, =gotest.tools= tries to make it as easy as possible for you to create +appropriate comparison — making you test readable as much as possible. + +Let's look a bit at the =cmp.Comparison= type. + +#+BEGIN_SRC go +type Comparison func() Result +#+END_SRC + +It's just a function that returns a =cmp.Result=, so let's look at =cmp.Result= definition. + +#+BEGIN_SRC go +type Result interface { + Success() bool +} +#+END_SRC + +Result is an =interface=, thus any /struct/ that provide a function =Success= that returns a =bool= can be used as a +comparison result, making it really easy to use in your code. There is also existing type of result to make it even +quicker to write your own comparison. + +- =ResultSuccess= is a constant which is returned to indicate success. +- =ResultFailure= and =ResultFailureTemplate= return a failed Result with a failure message. +- =ResultFromError= returns =ResultSuccess= if =err= is nil. Otherwise =ResultFailure= is returned with the error + message as the failure message. It works a bit like the =errors.Wrap= function of the [[https://github.com/pkg/errors][=github.com/pkgs/errors=]] + package. + +The =cmp= package comes with a few defined comparison that, we think, should cover a high number of use-cases. Let's +look at them. + +** Equality with =Equal= and =DeepEqual= + +#+BEGIN_QUOTE +Equal uses the == operator to assert two values are equal and fails the test if they are not equal. + +If the comparison fails Equal will use the variable names for x and y as part of the failure message to identify the +actual and expected values. + +If either x or y are a multi-line string the failure message will include a unified diff of the two values. If the +values only differ by whitespace the unified diff will be augmented by replacing whitespace characters with visible +characters to identify the whitespace difference. +#+END_QUOTE + +On the other hand… + +#+BEGIN_QUOTE +DeepEqual uses google/go-cmp (http://bit.do/go-cmp) to assert two values are equal and fails the test if they are not +equal. + +Package https://godoc.org/gotest.tools/assert/opt provides some additional commonly used Options. +#+END_QUOTE + +Using one or the other is as simple as : if you wrote your =if= with ==== then use =Equal=, otherwise use =DeepEqual=. +=DeepEqual= (and usually =reflect.DeepEqual=) is used when you want to compare anything more complex than primitive +types. One advantage of using =cmp.DeepEqual= over =reflect.DeepEqual= (in an if), is that you get a well crafted +message that shows the diff between the expected and the actual structs compared – and you can pass options to it. + +#+BEGIN_SRC go +assert.Assert(t, cmp.DeepEqual([]string{"a", "b"}, []string{"b", "a"})) +// Will print something like +// --- result +// +++ exp +// {[]string}[0]: +// -: "a" +// +: "b" +// {[]string}[1]: +// -: "b" +// +: "a" +foo := &someType(a: "with", b: "value") +bar := &someType(a: "with", b: "value") +// the following will succeed as foo and bar are _DeepEqual_ +assert.Assert(t, cmp.DeepEqual(foo, bar)) +#+END_SRC + +When using =DeepEqual=, you may end up with really weird behavior(s). You may want to ignore some fields, or consider +=nil= slice or map the same as empty ones ; or more common, your /struct/ contains some unexported fields that you +cannot use when comparing (as they are not exported 😓). In those case, you can use =go-cmp= options. + +Some existing one are : +- [[https://godoc.org/github.com/google/go-cmp/cmp/cmpopts#EquateEmpty][=EquateEmpty=]] returns a Comparer option that determines all maps and slices with a length of zero to be equal, + regardless of whether they are nil. +- [[https://godoc.org/github.com/google/go-cmp/cmp/cmpopts#IgnoreFields][=IgnoreFields=]] returns an Option that ignores exported fields of the given names on a single struct type. The struct + type is specified by passing in a value of that type. +- [[https://godoc.org/github.com/google/go-cmp/cmp/cmpopts#IgnoreUnexported][=IgnoreUnexported=]] returns an Option that only ignores the immediate unexported fields of a struct, including anonymous + fields of unexported types. +- [[https://godoc.org/github.com/google/go-cmp/cmp/cmpopts#SortSlices][=SortSlices=]] returns a Transformer option that sorts all =[]V= +- … and [[https://godoc.org/github.com/google/go-cmp/cmp/cmpopts][more]] 👼 + +=gotest.tools= also defines some *and* you can define yours ! As an example, =gotest.tools= defines =TimeWithThreshold= +and =DurationWithThreshold= that allows to not fails if the time (or duration) is not exactly the same but in the +specified threshold we specified. Here is the code for =DurationWithThreshold= for inspiration. + +#+BEGIN_SRC go +// DurationWithThreshold returns a gocmp.Comparer for comparing time.Duration. The +// Comparer returns true if the difference between the two Duration values is +// within the threshold and neither value is zero. +func DurationWithThreshold(threshold time.Duration) gocmp.Option { + return gocmp.Comparer(cmpDuration(threshold)) +} + +func cmpDuration(threshold time.Duration) func(x, y time.Duration) bool { + return func(x, y time.Duration) bool { + if x == 0 || y == 0 { + return false + } + delta := x - y + return delta <= threshold && delta >= -threshold + } +} +#+END_SRC + +Another good example for those options is when you want to skip some field. In [[https://github.com/docker/docker][=docker/docker=]] we want to be able to +easily check for equality between two service specs, but those might have different =CreatedAt= and =UpdatedAt= values +that we usually don't care about – what we want is to make sure it happens in the past 20 seconds. You can easily define +an option for that. + +#+BEGIN_SRC go + func cmpServiceOpts() cmp.Option { + const threshold = 20 * time.Second + + // Apply withinThreshold only for the following fields + metaTimeFields := func(path cmp.Path)bool { + switch path.String() { + case "Meta.CreatedAt", "Meta.UpdatedAt": + return true + } + return false + } + // have a 20s threshold for the time value that will be passed + withinThreshold := cmp.Comparer(func(x, y time.Time) bool { + delta := x.Sub(y) + return delta < threshold && delta > -threshold + }) + + return cmp.FilterPath(metaTimeFields, withinThreshold) + } +#+END_SRC + +I recommend you look at the [[https://godoc.org/gotest.tools/assert/opt][gotest.tools/assert/opt]] documentation to see which one are defined and how to use them. + +** Errors with =Error=, =ErrorContains= and =ErrorType= + +Checking for errors is *very common* in Go, having =Comparison= function for it was a requirement. + +- =Error= fails if the error is =nil= *or* the error message is not the expected one. +- =ErrorContains= fails if the error is =nil= *or* the error message does not contain the expected substring. +- =ErrorType= fails if the error is =nil= *or* the error type is not the expected type. + +Let's first look at the most used : =Error= and =ErrorContains=. + +#+BEGIN_SRC go + var err error + // will fail with : expected an error, got nil + assert.Check(t, cmp.Error(err, "message in a bottle")) + err = errors.Wrap(errors.New("other"), "wrapped") + // will fail with : expected error "other", got "wrapped: other" + assert.Check(t, cmp.Error(err, "other")) + // will succeed + assert.Check(t, cmp.ErrorContains(err, "other")) +#+END_SRC + +As you can see =ErrorContains= is especially useful when working with /wrapped/ errors. +Now let's look at =ErrorType=. + +#+BEGIN_SRC go + var err error + // will fail with : error is nil, not StubError + assert.Check(t, cmp.ErrorType(err, StubError{})) + + err := StubError{"foo"} + // will succeed + assert.Check(t, cmp.ErrorType(err, StubError{})) + + // Note that it also work with a function returning an error + func foo() error {} + assert.Check(t, cmp.ErrorType(foo, StubError{})) +#+END_SRC + +** Bonus with =Panics= + +Sometimes, a code is supposed to /panic/, see [[https://golang.org/doc/effective_go.html#panic][Effective Go (#Panic)]] for more information. And thus, you may want to make +sure you're code panics in such cases. It's always a bit tricky to test a code that panic as you have to use a deferred +function to recover the panic — but then if the panic doesn't happen how do you fail the test ? + +This is where =Panics= comes handy. + +#+BEGIN_SRC go + func foo(shouldPanic bool) { + if shouldPanic { + panic("booooooooooh") + } + // don't worry, be happy + } + // will fail with : did not panic + assert.Check(t, cmp.Panics(foo(false))) + // will succeed + assert.Check(t, cmp.Panics(foo(true))) +#+END_SRC + +** Miscellaneous with =Contains=, =Len= and =Nil= + +Those last three /built-in/ =Comparison= are pretty straightforward. + +- =Contains= succeeds if item is in collection. Collection may be a string, map, slice, or array. + + If collection is a string, item must also be a string, and is compared using =strings.Contains()=. If collection is a + Map, contains will succeed if item is a key in the map. If collection is a slice or array, item is compared to each + item in the sequence using ==reflect.DeepEqual()==. +- =Len= succeeds if the sequence has the expected length. +- =Nil= succeeds if obj is a nil interface, pointer, or function. + +#+BEGIN_SRC go + // Contains works on string, map, slice or arrays + assert.Check(t, cmp.Contains("foobar", "foo")) + assert.Check(t, cmp.Contains([]string{"a", "b", "c"}, "b")) + assert.Check(t, cmp.Contains(map[string]int{"a": 1, "b": 2, "c": 4}, "b")) + + // Len also works on string, map, slice or arrays + assert.Check(t, cmp.Len("foobar", 6)) + assert.Check(t, cmp.Len([]string{"a", "b", "c"}, 3)) + assert.Check(t, cmp.Len(map[string]int{"a": 1, "b": 2, "c": 4}, 3)) + + // Nil + var foo *MyStruc + assert.Check(t, cmp.Nil(foo)) + assert.Check(t, cmp.Nil(bar())) +#+END_SRC + +But let's not waste more time and let's see how to write our own =Comparison= ! + +** Write your own =Comparison= + +One of the main aspect of =gotest.tools/assert= is to make it easy for developer to write as less boilerplate code as +possible while writing tests. Writing your own =Comparison= allows you to write a well named function that will be easy +to read and that can be re-used across your tests. + +Let's look back at the =cmp.Comparison= and =cmp.Result= types. + +#+BEGIN_SRC go +type Comparison func() Result + +type Result interface { + Success() bool +} +#+END_SRC + +A =Comparison= for =assert.Check= or =assert.Check= is a function that return a =Result=, it's pretty straightforward to +implement, especially with =cmp.ResultSuccess= and =cmp.ResultFailure(…)= (as seen previously). + +#+BEGIN_SRC go + func regexPattern(value string, pattern string) cmp.Comparison { + return func() cmp.Result { + re := regexp.MustCompile(pattern) + if re.MatchString(value) { + return cmp.ResultSuccess + } + return cmp.ResultFailure( + fmt.Sprintf("%q did not match pattern %q", value, pattern)) + } + } + + // To use it + assert.Check(t, regexPattern("12345.34", `\d+.\d\d`)) +#+END_SRC + +As you can see, it's pretty easy to implement, and you can do quite a lot in there easily. If a function call returns an +error inside of your =Comparison= function, you can use =cmp.ResultFromError= for example. Having something like +=assert.Check(t, isMyServerUp(":8080"))= is way more readable than a 30-line of code to check it. + +* Conclusion… + +… and that's a wrap. We only looked at the =assert= package of [[https://gotest.tools][=gotest.tools=]] so far, but it's already quite a bit to process. + +We've seen : +- the main functions provided by this package : =assert.Assert= and =assert.Check= +- some helper functions like =assert.NilError=, … +- the =assert/cmp=, and =assert/opt= sub-package that allows you to write more custom =Comparison= + +Next time, we'll look at the =skip= package, that is a really simple wrapper on top of =testing.Skip= function. + +** diff --git a/www/vincent.demeester.fr/posts/2018-09-01-gotest-tools-skip.org b/www/vincent.demeester.fr/posts/2018-09-01-gotest-tools-skip.org @@ -0,0 +1,59 @@ +#+title: Golang testing — gotest.tools skip +#+date: <2018-09-01 Sat> +#+filetags: go testing skip +#+setupfile: ../templates/post.org + +* Introduction + +Let's continue the [[https://gotest.tools][=gotest.tools=]] serie, this time with the =skip= package. This is a +really simple one so this should be quick. + +#+BEGIN_QUOTE +=skip= provides functions for skipping a test and printing the source code of the +condition used to skip the test. +#+END_QUOTE + +The package consists of only one function : =If=. The idea comes mainly from +[[https://github.com/docker/docker][=docker/docker=]] integration test suite, where we wanted to skip some test (or test suites) +given different context. By context I mean things like the system we are running on +(=Windows=, =Linux=, …) or the capabilities of the running kernel or node (is =apparmor= +available or not on the current machine). + +This =If= method takes a =testing.T= pointer and either a boolean, a function that +returns a boolean, *or* an expression. + +#+BEGIN_SRC go + // boolean + // --- SKIP: TestName (0.00s) + // skip.go:19: MissingFeature + var MissingFeature bool + skip.If(t, MissingFeature) + + // function + // --- SKIP: TestName (0.00s) + // skip.go:19: !IsExperimentalDaemon(dockerClient): daemon is not experimental + skip.If(t, IsExperimentalDaemon(dockerClient), "daemon is not experimental") + + // expression + // --- SKIP: TestName (0.00s) + // skip.go:19: apiVersion < version("v1.24") + skip.If(t, apiVersion < version("v1.24")) +#+END_SRC + +There is few elements to note though : + +- This package (as other parts of the =gotest.tools= packages), will try to look at source + files to display the expression used (same goes for =assert=). This is usually not a + problem because you run tests where the source code is. *However*, in the cases you + generate a test binary to be executed later (à-la =kubernetes= or other projects), this + can display a weird error message if the sources are not available… You shouldn't be + worried too much about it, but it's better if you know :) +- The main reason to use =skip.If= is mainly for new contributors to get in quickly, + *reducing potential friction of them running the tests on their environment*. The more + the tests are written in a way they explicitely declare their requirements (and skipped + if the environment does not meet those), the easier it makes contributors run your + tests. *But*, this also means, you should try to measure the skipped tests on your + continuous integration system to make sure you run all of them eventually… otherwise + it's dead code. /But more on that in later posts 😉/. + +That's all for today folks, told you that was going to be quick. diff --git a/www/vincent.demeester.fr/posts/2018-09-06-gotest-tools-golden.org b/www/vincent.demeester.fr/posts/2018-09-06-gotest-tools-golden.org @@ -0,0 +1,75 @@ +#+TITLE: Golang testing — gotest.tools golden +#+date: <2018-09-06 Thu> +#+filetags: go testing golden +#+setupfile: ../templates/post.org + +#+TOC: headlines 2 + +* Introduction +Let's continue the [[https://gotest.tools][=gotest.tools=]] serie, this time with the =golden= package. This is a +[[/posts/2017-04-22-golang-testing-golden-file/][/quick follow-up/ on a previous =golden= post]], but focused on the =gotest.tools= +implementation. I'm gonna be quicker, please read that one if =golden= files is a new +concept for you. + +#+BEGIN_QUOTE +Package =golden= provides tools for comparing large mutli-line strings. + +Golden files are files in the =./testdata/= sub-directory of the package under test. +#+END_QUOTE + +In the previous article, we described the problem, and how to fix it by writing a small +helper. Well, that small helper is in =gotest.tools/golden= now, and it has a tiny bit +more features. + +One of the difference between the =gotest.tools= implementation and the previous post is +the flag name. In =gotest.tools/golden=, the flag is =-test.update-golden= (was just +=-test.update= before). Just as before, if the =-test.update-golden= flag is set then the +actual content is written to the golden file, before reading it and comparing. + +There is two ways to use the =golden= package: +- on it's own, using =golden.Assert= or =golden.AssertBytes= +- as a =cmp.Comparison=, with =golden.String= or =golden.Bytes= + +* =Assert= and =AssertBytes= + +Using =Assert= functions should be straightforward. Both =Assert= function compares the +actual content to the expected content in the golden file and returns whether the +assertion was successful (true) or not (false). + +- =Assert= uses string. Note that this one *removes carriage return* before comparing to + depend as less as possible of the system (=\n= vs =\r\n= 😅) +- =AssertBytes= uses raw data (in the form of =[]byte=) + +#+BEGIN_SRC go + golden.Assert(t, "foo", "foo-content.golden") + // Could also be used to check some binary format + golden.AssertBytes(t, []byte("foo"), "foo-content.golden") +#+END_SRC + +* =Bytes= and =String= + +As written in a [[/posts/2018-08-16-gotest-tools-assertions/][previous post (about the =assert= package)]], I prefer to use =cmp.Comparison=. + +#+BEGIN_QUOTE +All those helper functions have a equivalent function in the =cmp= package that returns a +=Comparison=. I, personally, prefer to use =assert.Check= or =assert.Assert= in +combination with =cmp.Comparison= as it allows me to write all my assertions the same way, +with built-ins comparison or with my own — i.e. =assert.Assert(t, is.Equal(…), "message"= +or =assert.Assert(t, stackIsUp(c, time…), "another message")=. +#+END_QUOTE + +The =golden= package gives us that too, in the form of =Bytes= and =String=. Using the +=assert.Check= or =assert.Assert= functions with those is equivalent to their /helper/ +counter-part =golden.Assert= and =golden.AssertBytes=. + +#+BEGIN_SRC go + assert.Assert(t, golden.String("foo", "foo-content.golden")) + // Could also be used to check some binary format + assert.Assert(t, golden.Bytes([]byte("foo"), "foo-content.golden")) +#+END_SRC + +* Conclusion… + +… that's a wrap. As for [[/posts/2018-09-01-gotest-tools-skip/][=skip=]], this is a small package, so the post was going to be +quick. =golden= package just solve a specific problem (read [[/posts/2017--04-22-golang-testing-golden-file/][Golang testing — golden file]]) +in a simple way. diff --git a/www/vincent.demeester.fr/posts/2018-09-14-gotest-tools-fs.org b/www/vincent.demeester.fr/posts/2018-09-14-gotest-tools-fs.org @@ -0,0 +1,187 @@ +#+TITLE: Golang testing — gotest.tools fs +#+date: <2018-09-14 Fri> +#+filetags: go testing fs +#+setupfile: ../templates/post.org + +#+TOC: headlines 2 + +* Introduction + +Let's continue the [[https://gotest.tools][=gotest.tools=]] serie, this time with the =fs= package. + +#+BEGIN_QUOTE +Package fs provides tools for creating temporary files, and testing the contents and structure of a directory. +#+END_QUOTE + +This package is heavily using functional arguments (as we saw in [[/posts/2017-01-01-go-testing-functionnal-builders/][functional arguments for +wonderful builders]]). Functional arguments is, in a nutshell, a combinaison of two Go +features : /variadic/ functions (=...= operation in a function signature) and the fact +that =func= are /first class citizen/. This looks more or less like that. + +#+BEGIN_SRC go + type Config struct {} + + func MyFn(ops ...func(*Config)) *Config { + c := &Config{} // with default values + for _, op := range ops { + op(c) + } + return c + } + + // Calling it + conf := MyFn(withFoo, withBar("baz")) +#+END_SRC + +The =fs= package has too *main* purpose : + +1. create folders and files required for testing in a simple manner +2. compare two folders structure (and content) + +* Create folder structures + +Sometimes, you need to create folder structures (and files) in tests. Doing =i/o= work +takes time so try to limit the number of tests that needs to do that, especially in unit +tests. Doing it in tests adds a bit of boilerplate that could be avoid. As stated [[/posts/2017-01-01-go-testing-functionnal-builders/][before]] : + +#+BEGIN_QUOTE +One of the most important characteristic of a unit test (and any type of test really) is +*readability*. This means it should be easy to read but most importantly it should *clearly +show the intent* of the test. The setup (and cleanup) of the tests should be as small as +possible to avoid the noise. +#+END_QUOTE + +In a test you usually end up using =ioutil= function to create what you need. This looks +somewhat like the following. + +#+BEGIN_SRC go + path, err := ioutil.TempDir("", "bar") + if err != nil { // or using `assert.Assert` + t.Fatal(err) + } + if err := os.Mkdir(filepath.Join(path, "foo"), os.FileMode(0755)); err != nil { + t.Fatal(err) + } + if err := ioutil.WriteFile(filepath.Join(path, "foo", "bar"), []byte("content"), os.FileMode(0777)); err != nil { + t.Fatal(err) + } + defer os.RemoveAll(path) // to clean up at the end of the test +#+END_SRC + +The =fs= package intends to help reduce the noise and comes with a bunch function to create +folder structure : + +- two main function =NewFile= and =NewDir= +- a bunch of /operators/ : =WithFile=, =WithDir=, … + +#+BEGIN_SRC go + func NewDir(t assert.TestingT, prefix string, ops ...PathOp) *Dir { + // … + } + + func NewFile(t assert.TestingT, prefix string, ops ...PathOp) *File { + // … + } +#+END_SRC + +The =With*= function are all satisfying the =PathOp= interface, making =NewFile= and +=NewDir= extremely composable. Let's first see how our above example would look like using +the =fs= package, and then, we'll look a bit more at the main =PathOp= function… + +#+BEGIN_SRC go + dir := fs.NewDir(t, "bar", fs.WithDir("foo", + fs.WithFile("bar", fs.WithContent("content"), fs.WithMode(os.FileMode(0777))), + )) + defer dir.Remove() +#+END_SRC + +It's clean and simple to read. The intent is well described and there is not that much of +noise. =fs= functions tends to have /sane/ and /safe/ defaults value (for =os.FileMode= +for example). Let's list the main, useful, =PathOp= provided by =gotest.tools/fs=. + +- =WithDir= creates a sub-directory in the directory at path. +- =WithFile= creates a file in the directory at path with content. +- =WithSymlink= creates a symlink in the directory which links to target. Target must be a + path relative to the directory. +- =WithHardlink= creates a link in the directory which links to target. Target must be a + path relative to the directory. +- =WithContent= and =WWithBytes= write content to a file at Path (from a =string= or a + =[]byte= slice). +- =WithMode= sets the file mode on the directory or file at path. +- =WithTimestamps= sets the access and modification times of the file system object at + path. +- =FromDir= copies the directory tree from the source path into the new Dir. This is + pretty useful when you have a huge folder structure already present in you =testdata= + folder or elsewhere. +- =AsUser= changes ownership of the file system object at Path. + +Also, note that =PathOp= being an function type, you can provide your own implementation +for specific use-cases. Your function just has to satisfy =PathOp= signature. + +#+BEGIN_SRC go + type PathOp func(path Path) error +#+END_SRC + +* Compare folder structures + +Sometimes, the code you're testing is creating a folder structure, and you would like to +be able to tests that, with the given arguments, it creates the specified structure. =fs= +allows you to do that too. + +The package provides a =Equal= function, which returns a =Comparison=, that the [[/posts/2018-08-16-gotest-tools-assertions/][=assert=]] +package understand. It works by comparing a =Manifest= type provided by the test and a +=Manifest= representation of the specified folder. + +#+BEGIN_QUOTE + Equal compares a directory to the expected structured described by a manifest and returns + success if they match. If they do not match the failure message will contain all the + differences between the directory structure and the expected structure defined by the + Manifest. +#+END_QUOTE + +A =Manifest= stores the expected structure and properties of files and directories in a +file-system. You can create a =Manifest= using either the functions =Expected= or +=ManifestFromDir=. + +We're going to focus on the =Expected= function, as =ManifestFromDir= does pretty much +what you would expected : it takes the specified path, and returns a =Manifest= that +represent this folder. + +#+BEGIN_SRC go + func Expected(t assert.TestingT, ops ...PathOp) Manifest +#+END_SRC + +=Expected= is close to =NewDir= function : it takes the same =PathOp= functional +arguments. This makes creating a =Manifest= straightforward, as it's working the same. Any +function that satisfy =PathOp= can be used for =Manifest= the exact same way you're using +them on =fs.NewDir=. + +There is a few additional functions that are only useful with =Manifest= : + +- =MatchAnyFileContent= updates a Manifest so that the file at path may contain any content. +- =MatchAnyFileMode= updates a Manifest so that the resource at path will match any file mode. +- =MatchContentIgnoreCarriageReturn= ignores cariage return discrepancies. +- =MatchExtraFiles= updates a Manifest to allow a directory to contain unspecified files. + +#+BEGIN_SRC go + path := operationWhichCreatesFiles() + expected := fs.Expected(t, + fs.WithFile("one", "", + fs.WithBytes(golden.Get(t, "one.golden")), + fs.WithMode(0600)), + fs.WithDir("data", + fs.WithFile("config", "", fs.MatchAnyFileContent)), + ) + + assert.Assert(t, fs.Equal(path, expected)) +#+END_SRC + +The following example compares the result of =operationWhichCreatesFiles= to the expected +=Manifest=. As you can see it also integrates well with other part of the =gotest.tools= +library, with the [[/posts/2018-09-06-gotest-tools-golden/][=golden= package]] in this example. + +* Conclusion… + +… that's a wrap. In my opinion, this is one the most useful package provided by +=gotest.tools= after =assert=. It allows to create simple or complex folder structure +without the noise that usually comes with it. diff --git a/www/vincent.demeester.fr/posts/2018-09-18-gotest-tools-icmd.org b/www/vincent.demeester.fr/posts/2018-09-18-gotest-tools-icmd.org @@ -0,0 +1,209 @@ +#+TITLE: Golang testing — gotest.tools icmd +#+date: <2018-09-18 Tue> +#+filetags: go testing cmd +#+setupfile: ../templates/post.org + +#+TOC: headlines 2 + +* Introduction +Let's continue the [[https://gotest.tools][=gotest.tools=]] serie, this time with the =icmd= package. + +#+BEGIN_QUOTE +Package icmd executes binaries and provides convenient assertions for testing the results. +#+END_QUOTE + +After file-system operations (seen in [[/posts/2018-09-14-gotest-tools-fs/][=fs=]]), another common use-case in tests is to +*execute a command*. The reasons can be you're testing the =cli= you're currently writing +or you need to setup something using a command line. A classic execution in a test might +lookup like the following. + +#+BEGIN_SRC go + cmd := exec.Command("echo", "foo") + cmd.Stout = &stdout + cmd.Env = env + if err := cmd.Run(); err != nil { + t.Fatal(err) + } + if string(stdout) != "foo" { + t.Fatalf("expected: foo, got %s", string(stdout)) + } +#+END_SRC + +The package =icmd= is there to ease your pain (as usual 😉) — we used /the name =icmd=/ +instead of =cmd= because it's a pretty common identifier in Go source code, thus would be +really easy to /shadow/ — and have some really weird problems going on. + +The usual =icmd= workflow is the following: + +1. Describe the command you want to execute using : type =Cmd=, function =Command= and + =CmdOp= operators. +2. Run it using : function =RunCmd= or =RunCommand= (that does 1. for you). You can also + use =StartCmd= and =WaitOnCmd= if you want more control on the execution workflow. +3. Check the result using the =Assert=, =Equal= or =Compare= methods attached to the + =Result= struct that the command execution return. + +* Create and run a command + +Let's first dig how to create commands. In this part, the assumption here is that the +command is successful, so we'll have =.Assert(t, icmd.Success)= for now — we'll learn more +about =Assert= in the next section 👼. + +The simplest way to create and run a command is using =RunCommand=, it has the same +signature as =os/exec.Command=. A simple command execution goes as below. + +#+BEGIN_SRC go + icmd.RunCommand("echo", "foo").Assert(t, icmd.Sucess) +#+END_SRC + +Sometimes, you need to customize the command a bit more, like adding some environment +variable. In those case, you are going to use =RunCmd=, it takes a =Cmd= and operators. +Let's look at those functions. + +#+BEGIN_SRC go + func RunCmd(cmd Cmd, cmdOperators ...CmdOp) *Result + + func Command(command string, args ...string) Cmd + + type Cmd struct { + Command []string + Timeout time.Duration + Stdin io.Reader + Stdout io.Writer + Dir string + Env []string + } +#+END_SRC + +As we've seen [[/posts/2017-01-01-go-testing-functionnal-builders/][multiple]] [[/posts/2018-08-16-gotest-tools-assertions/][times]] [[/posts/2018-09-14-gotest-tools-fs/][before]], it uses the /powerful/ functional arguments. At the +time I wrote this post, the =icmd= package doesn't contains too much =CmdOp= [fn:1], so I'll +propose two version for each example : one with =CmdOpt= present in [[https://github.com/gotestyourself/gotest.tools/pull/122][this PR]] and one +without them. + +#+BEGIN_SRC go + // With + icmd.RunCmd(icmd.Command("sh", "-c", "echo $FOO"), + icmd.WithEnv("FOO=bar", "BAR=baz"), icmd.Dir("/tmp"), + icmd.WithTimeout(10*time.Second), + ).Assert(t, icmd.Success) + + // Without + icmd.RunCmd(icmd.Cmd{ + Command: []string{"sh", "-c", "echo $FOO"}, + Env: []string{"FOO=bar", "BAR=baz"}, + Dir: "/tmp", + Timeout: 10*time.Second, + }).Assert(t, icmd.Success) +#+END_SRC + +As usual, the intent is clear, it's simple to read and composable (with =CmdOp='s). + +[fn:1] The =icmd= package is one of the oldest =gotest.tools= package, that comes from the +[[https://github.com/docker/docker][=docker/docker=]] initially. We introduced these =CmdOp= but implementations were in +=docker/docker= at first and we never really updated them. + +* Assertions + +Let's dig into the assertion part of =icmd=. Running a command returns a struct +=Result=. It has the following methods : + +- =Assert= compares the Result against the Expected struct, and fails the test if any of + the expectations are not met. +- =Compare= compares the result to Expected and return an error if they do not match. +- =Equal= compares the result to Expected. If the result doesn't match expected + returns a formatted failure message with the command, stdout, stderr, exit code, and any + failed expectations. It returns an =assert.Comparison= struct, that can be used by other + =gotest.tools=. +- =Combined= returns the stdout and stderr combined into a single string. +- =Stderr= returns the stderr of the process as a string. +- =Stdout= returns the stdout of the process as a string. + +When you have a result, you, most likely want to do two things : + +- /assert/ that the command succeed or failed with some specific values (exit code, + stderr, stdout) +- use the output — most likely =stdout= but maybe =stderr= — in the rest of the test. + +As seen above, /asserting/ the command result is using the =Expected= struct. + +#+BEGIN_SRC go + type Expected struct { + ExitCode int // the exit code the command returned + Timeout bool // did it timeout ? + Error string // error returned by the execution (os/exe) + Out string // content of stdout + Err string // content of stderr + } +#+END_SRC + +=Success= is a constant that defines a success — it's an exit code of =0=, didn't timeout, +no error. There is also the =None= constant, that should be used for =Out= or =Err=, to +specify that we don't want any content for those standard outputs. + +#+BEGIN_SRC go + icmd.RunCmd(icmd.Command("cat", "/does/not/exist")).Assert(t, icmd.Expected{ + ExitCode: 1, + Err: "cat: /does/not/exist: No such file or directory", + }) + + // In case of success, we may want to do something with the result + result := icmd.RunCommand("cat", "/does/exist") + result.Assert(t, icmd.Success) + // Read the output line by line + scanner := bufio.NewScanner(strings.NewReader(result.Stdout())) + for scanner.Scan() { + // Do something with it + } +#+END_SRC + +If the =Result= doesn't map the =Expected=, a test failure will happen with a useful +message that will contains the executed command and what differs between the result and +the expectation. + +#+BEGIN_SRC go + result := icmd.RunCommand(…) + result.Assert(t, icmd.Expected{ + ExitCode: 101, + Out: "Something else", + Err: None, + }) + // Command: binary arg1 + // ExitCode: 99 (timeout) + // Error: exit code 99 + // Stdout: the output + // Stderr: the stderr + // + // Failures: + // ExitCode was 99 expected 101 + // Expected command to finish, but it hit the timeout + // Expected stdout to contain "Something else" + // Expected stderr to contain "[NOTHING]" + … +#+END_SRC + +Finally, we listed =Equal= above, that returns a =Comparison= struct. This means we can +use it easily with the =assert= package. As written in a [[/posts/2018-08-16-gotest-tools-assertions/][previous post (about the =assert= +package)]], I tend prefer to use =cmp.Comparison=. Let's convert the above examples using +=assert=. + +#+BEGIN_SRC go + result := icmd.RunCmd(icmd.Command("cat", "/does/not/exist")) + assert.Check(t, result.Equal(icmd.Expected{ + ExitCode: 1, + Err: "cat: /does/not/exist: No such file or directory", + })) + + // In case of success, we may want to do something with the result + result := icmd.RunCommand("cat", "/does/exist") + assert.Assert(t, result.Equal(icmd.Success)) + // Read the output line by line + scanner := bufio.NewScanner(strings.NewReader(result.Stdout())) + for scanner.Scan() { + // Do something with it + } +#+END_SRC + +* Conclusion… + +… that's a wrap. The =icmd= package allows to easily run command and describe what result +are expected of the execution, with the least noise possible. We *use this package heavily* +on several =docker/*= projects (the engine, the cli)… diff --git a/www/vincent.demeester.fr/posts/2019-01-20-2018-year-review.org b/www/vincent.demeester.fr/posts/2019-01-20-2018-year-review.org @@ -0,0 +1,151 @@ +#+title: 2018 year review +#+date: <2019-01-20 Sun> +#+filetags: review + +* Introduction + +Here is my review of 2018, the first of its kind, hopefully not the last 👼. I saw +some[fn:1] /2018[fn:2] reviews/[fn:3] articles[fn:4] in my Feedly feed and I thought it +would be a good idea to write my own too. + +I'll try in the next year — maybe month if I ever want to do monthly reviews — to automate +some of it ; using the beloved =org-mode=. + +[fn:1] [[https://punchagan.muse-amuse.in/blog/2018-in-review/][2018 in Review - Noetic Nought]] +[fn:2] [[https://medium.com/@buster/42-dig-deeper-e2278d1fe015][42 — Dig deeper – Buster Benson – Medium]] +[fn:3] [[https://jvns.ca/blog/2018/12/23/2018--year-in-review/][2018: Year in review - Julia Evans]] +[fn:4] [[https://writing.natwelch.com/post/685][Nat? Nat. Nat! | #685 2018 Year in Review]] + +* Work + +The big change this year is : I changed job 👼. I went from Docker Inc. to Red Hat. I +needed a change and 5 month in, I think it was the *best choice I made in my life* so far +💃. I'm doing open-source for a living and best part, I am working remotely (more on that +later). + +Before that, at Docker Inc., I continued the work I started years before, +a.k.a. maintaining the Moby project and the docker engine, among other Docker project +(both open-source and closed-source). I also helped the work on the compose side, from the +root of =docker/compose-on-kubernetes= (before it got open-sourced), to the =docker/app= +experiments. + +At Red Hat, I started to work upstream in the Kubernetes community, mainly on the Knative +projects. I also work on the Openshift Cloud Function project (and thus team), and those +fellows are awesome ! Digging more into Openshift, and other part of the Red Hat portfolio +is a really good learning experience, and it's just the start ! + +As stated above, I am now working home, full-time. I could work from home from time to +time when I was at Docker inc, but working home full-time is another kind of beast. So far +it is really good, some adjustments were needed but it's for the best. Here is a small +take on "working from home": + +- It's easy to have *no distraction*, thus having *really productive* piece of time +- It's also *really easy to work long day or really long period of time*. It's especially + true if, like me, you work on a distributed team (across multiple timezones). + - I ended up using the Pomodoro technique to make sure I move at least few times a day + - I try to make sure I don't make an habits of checking out work code, email and other + material after a certain hour in the evening. It's ok to do it sometimes, but for your + sanity, you need some rest time. +- It's easy to adapt your day to circumstance. If you got to run errands in the middle of + the day, it's no big deal. You can take the time back later on. +- It's so good to have *no* commmute time. That said I end up /walking or taking the bike/ + early morning to clear my head before work 😝. + +* Personal + +Health wise, it's a mix of good and bad year. The first half was really good, the second +way less. End of august, I felt something weird in the right knee, and well, turns out my +internal meniscus is in a real bad shape. Just as before joining Docker, I'm gonna need a +surgery, on the right knee that time. It's gonna affect 2019 (the first half, I'm not +gonna be able to move around much but.. meh, it's life). + +Now that I work from home, I'm really glad I got a standing desk at the end of 2017. I +tend to work standing most of the time -- except when my knee hurts (and most likely for +few months after the surgery 😅). I invested on a ultrawide screen, to get the same +experience I had at Docker. And oh boy those screens are good ! + +I also try to clean my desk and it's "neighboorhood". As I get older, I want less messy +stuuf (desk, flat, ...). I'm leaning towards having less stuff, being commputer related or +not. It's not minimalism, but it feel good to have less stuff, but stuff that you actually +use. I still have trouble throwing old computer away, mainly because I fell they can be +useful in some way. + +[[/images/2019/01/desk1.jpg]] + +This year I migrate all of my "infrastructure" computer to NixOS. I learned a lot of Nix, +reworked my configuration multiple time to end up with a [[https://github.com/vdemeester/nixos-configuration.git][system configuration repository]] +that uses modules, and a [[https://github.com/vdemeester/home.git][/home configuration repository/]] (for user configuration). The +[[https://github.com/vdemeester/home.git][home]] repository uses [[https://github.com/rycee/home-manager.git][=home-manager=]] and thus doesn't make any assumption of running on top +of NixOS. This allows me to have an /easy to get/ setup on any system that =nixpkgs= +supports (any Linux distribution, Mac OSX, Windows Subsystem Linux). The current +configuration is not yet optimal but I'm pretty happy about what I got : + +- Custom DNS server @home to make it easier to target local hosts. +- Local proxies and mirrors for docker images, nixpkgs binary package and go modules to + eat less bandwidth. +- Easy to setup VPN using [[https://www.wireguard.com/][WireGuard]]. +- File replication using =syncthing= and automatic backup on my local NAS. +- Automatic system upgrade, thanks to NixOS. I'll probably write an article about that + later on this year. + +I started to use =todoist= in 2017, and boy, oh boy, it helped me quite a lot ! I'm using +it daily to organize my work and quickly get idea, and /todos/ out of my head. The main +problem with it is it's not integrated with another tool I'm using daily : Emacs and +=org-mode=. =org-mode= is a fantastic piece of software and is, on its own, the main +reason for me to invest time in Emacs. I'm taking note in =org-mode=, I write my daily +standup notes in there too. I end up going back and forth between =org-mode= and =todoist= +for those daily standup. I am lazy, I want to automate that. And the best way to do it, is +to also use =org-mode= for task management. I'm in a /transition/ mode right now, but my +goal for 2019 is to use todoist to take quick note/todo(s) on the move (aka on the phone) +and use =org-mode= for the rest. + + +* Reading & Writing + +I used to like reading, but the past years, I didn't really read that much, except some +technical books. 2018 in, that respect, is not an exception, I didn't read too much. Worse +than that, I started some book and stopped at some point, for no apparent reason ; and +now, I need to start back from the beginning, which, well, is not helping me want to read +them again. + +I'm trying two thing to counter that and consume more books for the years to come. + +1. I now have a reading list on my =org-mode= files, where I track which one I read and + when I read them ; and maybe notes too. I have a lot of book on my kindle, that only + wait for one thing, being read.. +2. I subscribed to [[https://www.audible.fr][Audible]] 👼. Working from home, I tend to take a long break after lunch, + where I'm going for a walk, for around an hour. I can't read while walking but I + definitely can listen - that make audio books perfect for these moments. I also + alternate between audio books and non-musical podcasts. + +On the writing side, 2017 was a slow year in terms of writing (only 2 posts), 2018 was a +bit better, 6 posts -- it's a bit cheating, as it was mainly between changing jobs, and +on a series I still need to finish. I'm hoping to write more this year, hence the goals +I've set to myself below. + +* 2019 Goals + +- *Get back on my feet after knee surgery (exercices, …)* 🏃 +- *Read at least one book per month (be audible, ebook or paper)* 📖 +- *Giving at least a talk (on Knative, containers, nixos, ..)* 🙊 + + I didn't give too much talk in 2018 (at least less than 2017). I'm gonna try to get back + at it this year. The surgery won't help but it's just few months. + +- *At least 1 video per month* 📹 + + I want to start recording some video, as I feel it's an easier medium than writing and, + well, I wanna try ! + +- *At least 1 post per month* ✍️ +- *Enhance my emacs skills (aka don't be afraid of the lisp)* ⌨️ + + I'm using Emacs for almost anything that doesn't happen in a web browser. But I still + feel like a newbie. I want to learn more, to write more lisp that help me being even + more lazier (aka achieve more doing less 😝) + +- *Enhance my Nix(OS) skills* 🐧 +- *Learn / master a new language* 🎽 + + I'm working with Go 90% of my time. I want to master and learn more language. On my list + are Emacs Lisp, Rust, Typescript and Haskell. diff --git a/www/vincent.demeester.fr/posts/2019-01-26-nix-run-alias.org b/www/vincent.demeester.fr/posts/2019-01-26-nix-run-alias.org @@ -0,0 +1,200 @@ +#+title: Nix run aliases +#+date: <2019-01-26 Sat> +#+filetags: nixos fish alias nix shell home manager + +* Introduction + +I use [[https://nixos.org/][=NixOS=]] each and every day, everywhere. One really cool feature of =nix= is +=nix-shell= and more recently (with =nix= >= =2.0.0=), =nix run=. + +#+begin_src man +Usage: nix run <FLAGS>... <INSTALLABLES>... + +Summary: run a shell in which the specified packages are available. + +Flags: + --arg <NAME> <EXPR> argument to be passed to Nix functions + --argstr <NAME> <STRING> string-valued argument to be passed to Nix functions + -c, --command <COMMAND> <ARGS> command and arguments to be executed; defaults to 'bash' + -f, --file <FILE> evaluate FILE rather than the default + -i, --ignore-environment clear the entire environment (except those specified with --keep) + -I, --include <PATH> add a path to the list of locations used to look up <...> file names + -k, --keep <NAME> keep specified environment variable + -u, --unset <NAME> unset specified environment variable + +Examples: + + To start a shell providing GNU Hello from NixOS 17.03: + $ nix run -f channel:nixos-17.03 hello + + To start a shell providing youtube-dl from your 'nixpkgs' channel: + $ nix run nixpkgs.youtube-dl + + To run GNU Hello: + $ nix run nixpkgs.hello -c hello --greeting 'Hi everybody!' + + To run GNU Hello in a chroot store: + $ nix run --store ~/my-nix nixpkgs.hello -c hello + +Note: this program is EXPERIMENTAL and subject to change. +#+end_src + +As you can see from the =-h= summary, it makes it really easy to run a shell or a command +with some packages that are not in your main configuration. It will download the +package(s) if there are not available in the Nix store (=/nix/store/=). + +A few month ago I decided it would be a perfect use-case for command I do not run +often. My idea was, let's define =aliases= (in the shell) that would make a simple command +call, like =ncdu=, become =nix run nixpkgs.ncdu -c ndcu=. My /shell of choice/ is [[https://fishshell.com/][fish]], so +I decided to dig into the /language/ in order to implement that. + +The use case is the following : +- When I type =foo=, I want the command =foo= in package =bar= to be executed. +- I want to be able to pin a channel for the package — I'm using [[https://matthewbauer.us/][Matthew Bauer]] [[https://matthewbauer.us/blog/channel-changing.html][Channel + Changing with Nix]] setup for pin-pointing a given channel. + +* Fish aliases experimentation + +I had a feeling the built-in =alias= would not work so I ended up trying to define a +/dynamic/ function that would be the name of the command. That's the beauty of the shell, +everything is a command, even function appears as commands. If you define a function +=foo()=, you will be able to run =foo= in your shell, *and* it will take precedence over +the =foo= executable file that would be in your =PATH=. + +I ended up with two main helper function that would create those /alias/ function. + +#+begin_src fish + function _nix_run_package + set -l s $argv[1] + set -l package (string split ":" $s) + switch (count $package) + case 1 + _nix_run $s $s $argv[2] $argv[3] + case 2 + _nix_run $package[1] $package[2] $argv[2] $argv[3] + end + end + + function _nix_run + set -l c $argv[1] + set -l p $argv[2] + set -l channel $argv[3] + set -l channelsfile $argv[4] + function $c --inherit-variable c --inherit-variable p --inherit-variable channel --inherit-variable channelsfile + set -l cmd nix run + if test -n "$channelsfile" + set cmd $cmd -f $channelsfile + end + eval $cmd $channel.$p -c $c $argv + end + end +#+end_src + +In a nutshell, =_nix_run= is the function that create the alias function. There is so +condition in there depending on whether we gave it a channel or not. So, a call like +=_nix_run foo bar unstable channels.nix= would, in the end generate a function =foo= with +the following call : =nix run -f channels.nix unstable.bar -c foo=. + +The other function, =_nix_run_package= is there to make me write less when I define those +aliases — aka if the command and the package share the same name, I don't want to write it +twice. So, a call like =_nix_run_package foo nixpkgs= would result in a =_nix_run foo foo +nixpkgs=, whereas a call like =_nix_run_package foo:bar unstable channels.nix= would +result in a =_nix_run foo bar unstable channels.nix=. + +An example is gonna be better than the above paragraphs. This is what I used to have in my +fish configuration. + +#+begin_src fish + function _def_nix_run_aliases + set -l stable mr sshfs ncdu wakeonlan:python36Packages.wakeonlan lspci:pciutils lsusb:usbutils beet:beets gotop virt-manager:virtmanager pandoc nix-prefetch-git:nix-prefetch-scripts nix-prefetch-hg:nix-prefetch-scripts + set -l unstable op:_1password update-desktop-database:desktop-file-utils lgogdownloader + for s in $stable + _nix_run_package $s nixpkgs + end + for s in $unstable + _nix_run_package $s unstable ~/.config/nixpkgs/channels.nix + end + end + # Call the function to create the aliases + _def_nix_run_aliases +#+end_src + +This works like a charm, and for a while, I was happy. But I soon realized something : I'm +not always on my shell — like, I tend to spend more and more time in =eshell=. This also +doesn't work with graphic tools like [[https://github.com/DaveDavenport/rofi][=rofi=]]. I needed actual command, so that external +tools would benefit from that. I ended up writing a small tool, [[https://github.com/vdemeester/nr][=nr=]] that integrates +nicely with =nix= and [[https://github.com/rycee/home-manager][=home-manager=]]. + +* A proper tool : =nr= + +The gist for this tool is simple : +- create an executable script that will call =nix run ...= instead of the command +- as for the above fish script, support different channels +- make sure we don't have conflicts — if the command already exists, then don't create the + command + +The =nr= tool would have to be able to manage multiple /profile/, which really stands for +multiple file. The main reason is really about how I manage my configuration ; To make it +simple, depending on the computer my configurations are setup, I may not have =go=, thus I +don't want any =go=-related aliases for a computer that doesn't have =go= (using =go= here +but you can replace with anything). + +#+begin_src fish +$ nr default +> nr generate default +> virtmanager already exists +$ nr git +> nr generate git +#+end_src + +=nr= generates a bash script that does the =nr run …= and mark it as executable. =nr= +needs to be able to clean files it has generated (in case we removed it from +aliases). Thus, I went for a really naive comment in the script. When generating a new set +of commands, =nr= will first remove previously generated script for this profile, and for +that, it uses the comment. Let's look at what a generated script looks like, for the +default profile. + +#+begin_src bash +#!/usr/bin/env bash +# Generated by nr default +nix run nixpkgs.nix-prefetch-scripts -c nix-prefetch-git $@ +#+end_src + +The format used in =nr= is =json=. I'm not a /huge fan/ of =json= but it really was the +best format to use for this tool. The reason to use =json= are simple : + +- Go has =encoding/json= built-in, so it's really easy to =Marshall= and =Unmarshall= + structure. + #+begin_src go + type alias struct { + Command string `json:"cmd"` + Package string `json:"pkg"` + Channel string `json:"chan"` + } + #+end_src +- Nix also has built-in support for =json= : =builtins.toJSON= will marshall a /struct/ + into a json file. + +Finally, to avoid conflicts at /build time/ (=home-manager switch=) I couldn't use/define +a nix package, but to execute command(s) at the end of the build. One way to achieve it is +to use =file.?.onChange= script, which is executed after [[https://github.com/rycee/home-manager][=home-manager=]] has updated the +environment, *if* the file has changed. That means it's possible to check for executable +files in =~/.nix-profile/bin/= for defined aliases and create those that are not there, +with =nr=. My configuration then looks like the following. + +#+BEGIN_SRC nix + xdg.configFile."nr/default" = { + text = builtins.toJSON [ + {cmd = "ncdu";} {cmd = "sshfs";} {cmd = "gotop";} {cmd = "pandoc";} + {cmd = "wakeonlan"; pkg = "python36Packages.wakeonlan";} + {cmd = "beet"; pkg = "beets";} + {cmd = "virt-manager"; pkg = "virtmanager";} + {cmd = "nix-prefetch-git"; pkg = "nix-prefetch-scripts";} + {cmd = "nix-prefetch-hg"; pkg = "nix-prefetch-scripts";} + ]; + onChange = "${pkgs.nur.repos.vdemeester.nr}/bin/nr default"; + }; +#+END_SRC + +And there you are, now, each time I update my environment (=home-manager switch=), =nr= +will regenerate my =nix run= aliases. diff --git a/www/vincent.demeester.fr/posts/2019-03-23-gotest-tools-poll.org b/www/vincent.demeester.fr/posts/2019-03-23-gotest-tools-poll.org @@ -0,0 +1,136 @@ +#+TITLE: Golang testing — gotest.tools poll +#+date: <2019-03-23 Sat> +#+filetags: go testing poll + +#+TOC: headlines 2 + +* Introduction +Let's continue the [[https://gotest.tools][=gotest.tools=]] serie, this time with the =poll= package. + +#+BEGIN_QUOTE +Package poll provides tools for testing asynchronous code. +#+END_QUOTE + +When you write test, you may test a piece of code that work asynchronously, where the +state you're expecting is gonna take a bit of time to be achieved. This is especially true +when you work on networking or file-system code. And this happens a lot when you write +integration (or end-to-end) test, less for unit-tests. + +The package =poll= is trying to tackle those use cases. We'll first take a look at the +main function, =WaitOn=, then how to write a ~Check~, using the ~Result~ type. + +* ~WaitOn~ + +Let's look into the main ~poll~ function : `WaitOn`. + +#+begin_quote + WaitOn a condition or until a timeout. Poll by calling check and exit when check returns + a done Result. To fail a test and exit polling with an error return a error result. +#+end_quote + +In a gist, ~WaitOn~ will run a /condition/ function until it either times out or +succeed. It wait for a given time/delay between each run. + +#+begin_src go + func WaitOn(t TestingT, check Check, pollOps ...SettingOp) { + // […] + } +#+end_src + +As any /testing helper/ function, the first argument is ~*testing.T~ (or, in this case, +any thing that look like it, thanks to the ~TestingT~ interace). The two other arguments +are way more interesting : + +- The ~Check~ is the condition that will run multiple times until it either timeout, or succeed. +- The ~SettingOp(s)~ which are options to configure the function, things like the timeout, + or the /delay/ between each run. + +The settings are pretty straightforward : + +- ~WithDelay~ : sets the delay to wait between polls. The default delay is 100ms. +- ~WithTimeout~ : sets the timeout. The default timeout is 10s. + +There is existing ~Check~ for common case: + +- ~Connection~ : try to open a connection to the address on the named network. + + #+begin_src go + poll.WaitOn(t, poll.Connection("tcp", "foo.bar:55555"), poll.WithTimeout("5s")) + #+end_src + +- ~FileExists~ : looks on filesystem and check that path exists. + + #+begin_src go + poll.WaitOn(t, poll.FileExists("/should/be/created"), poll.WithDelay("1s")) + #+end_src + + +* ~Check~ and ~Result~ + +~Connection~ and ~FileExists~ are the only two /built-in/ ~Check~ provided by +~gotest.tools~. They are useful, but as usual, where ~gotest.tools~ shines is +extensiblity. It is really easy to define your own ~Check~. + +#+begin_src go + type Check func(t LogT) Result +#+end_src + +A ~Check~ is, thus, only a function that takes ~LogT~ — which is anything that can log +something, like ~*testing.T~ — and return a ~Result~. Let's look at this intersting +~Result~ type. + +#+begin_src go + type Result interface { + // Error indicates that the check failed and polling should stop, and the + // the has failed + Error() error + // Done indicates that polling should stop, and the test should proceed + Done() bool + // Message provides the most recent state when polling has not completed + Message() string + } +#+end_src + +Although it's an interface, the ~poll~ package defines built-in ~Result~ so that it's easy +to write ~Check~ without having to define you ~Result~ type. + +- ~Continue~ returns a Result that indicates to WaitOn that it should continue + polling. The message text will be used as the failure message if the timeout is reached. +- ~Success~ returns a Result where Done() returns true, which indicates to WaitOn that it + should stop polling and exit without an error. +- ~Error~ returns a Result that indicates to WaitOn that it should fail the test and stop + polling. + +The basic just to write a ~Check~ is then : + +- if the state is not there yet, return ~Continue~, +- if there is an error, unrelated to validating the state, return an ~Error~, +- if the state is there, return ~Success~. + +Let's look at an example taken from the ~moby/moby~ source code. + +#+begin_src go + poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond)) + + func IsInState(ctx context.Context, client client.APIClient, containerID string, state ...string) func(log poll.LogT) poll.Result { + return func(log poll.LogT) poll.Result { + inspect, err := client.ContainerInspect(ctx, containerID) + if err != nil { + return poll.Error(err) + } + for _, v := range state { + if inspect.State.Status == v { + return poll.Success() + } + } + return poll.Continue("waiting for container to be one of (%s), currently %s", strings.Join(state, ", "), inspect.State.Status) + } + } +#+end_src + + +* Conclusion + +… that's a wrap. The =poll= package allows to easily wait for a condition to happen in a +given time-frame — with sane defaults. As for most of the ~gotest.tools~ package, we use +this package heavily in =docker/*= projects too… diff --git a/www/vincent.demeester.fr/posts/2020-02-22-digital-minimalism.org b/www/vincent.demeester.fr/posts/2020-02-22-digital-minimalism.org @@ -0,0 +1,130 @@ +#+title: On digital minimalism, Linux, NixOS and Emacs +#+date: +#+filetags: minimalism digital emacs linux nixos +#+setupfile: ../templates/post.org/ + +* Introduction + +I've been reading and listening about Minimalism and Digital Minimalism for a little while +now. I've watch some [[https://www.youtube.com/channel/UCJ24N4O0bP7LGLBDvye7oCA][Matt D'Avella]] youtube video (and documentary), read [[https://www.goodreads.com/book/show/40672036-digital-minimalism][Digital +Minimalism]] from [[https://www.goodreads.com/author/show/147891.Cal_Newport][Cal Newport]], and read a bunch of [[Links][articles and blog posts]]. + +I wouldn't say I am a minimalist, neither am I a digital minimalist *but* it is something +that is a bit appealing and I feel I am slowly taking inspiration from those. I've started +reducing to a minimun what my /smart/-phone does, I've reduced the number of /gadgets/ +I've add over time. But in this posts, I am going to focus on my systems and tools — this +means Linux distributions, Emacs and other tools. + +* A little bit of history (or context) + +I've been using GNU/Linux for ages now. It has been around 22 years now that I discovered +Red Hat[fn:1] Linux, 5.2 at the time. I've use it as my primary operating system almost +ever sync. During that time, I've tried so many distribution: from [[https://en.wikipedia.org/wiki/Mandriva_Linux][Mandrake]] (later +Mandriva), to [[https://www.debian.org/][Debian]] (3.0 -> 9.0 on some servers) and [[https://www.ubuntu.com][Ubuntu]] (on the first ever public +release), with a long time on [[https://gentoo.org/][Gentoo]] and later [[https://www.archlinux.org/][Archlinux]]. I've used Gnome, KDE, wmii, +XMonad, awesome-vm. I've used bash, zsh, fish, switching from one to another multiple +times. I've used Emacs, vim, IntelliJ, Eclipse, VSCode… My gentoo/archlinux days made me +try and /use/ a lot of tools (screen, tmux, …) and customization. + +I had the habit to customize everything, down to the theme and icons I use. There was a +time where I would patch and re-package some GTK themes and icons set. *I had to +customize everything*, that was my learning experience. I would often break things and +have to re-install the system — and *it was fun* 😎. + +But the more I /grew/, the more I worked for different companies, the less time I had to +do this. I was also learning a lot about tests, reproducibility at that time. My /will/ to +customization and /instability/ slowly faded, I wanted to be able to focus on what +mattered, and not loose time on silly things that I would break soon after making it. + +* Minimalism and Digital Mininalism + +Let's try to define really quick what is minimalism and digital minimalism about.Digital +minimalism is Minimalism in the digital world, and [[https://www.theminimalists.com/minimalism/][minimalism]] can be seen as [[https://jamesstuber.com/minimalism-as-a-framework/][a Framework +for Decision Making]]. + +#+begin_quote +Every decision we make is constrained by limited resources. Money, materials, energy. Even +the richest man in the world is limited by time, and has to make decisions accordingly. + +Here are some factors that make minimalism well suited for decision making: + +- Practicing minimalism helps develop an improved ability to discern what’s important and + what’s not +- It becomes easier to let go of ‘sort of important’ things +- What you choose to keep is a reflection of your values +#+end_quote + +In a gist, Digital Minimalism is about making conscious choice of what you use and what +you do in the digital world, a world of abundance (information, software, …). + +Let's take two examples: our desktops and our phones. + +On desktop, digital minimalism can be something like the follow blog post : [[http://neugierig.org/software/blog/2014/07/anti-dashboard-manifesto.html][Tech Notes: +The Anti-Dashboard Manifesto]]. + +#+begin_quote +Now take that reasoning further: It's not useful to reserve a portion of my screen for +displaying which applications are running, as the things that are running are visible and +the things that aren't visible can be found when necessary. There's no need for an icon +displaying wifi status; if I'm connected it's uninteresting and if I'm not connected I'll +surely discover it if I attempt to use the internet. + +I took this reasoning to its conclusion. I run my computers with the screen blank except +for the apps I run, and the apps I run I configure to display a minimal amount of +information. I think of this as being anti-dashboard: against the cognitive clutter of +extraneous information. My computer is not a cockpit and I am at my best when I'm only +thinking about the single task at hand. +#+end_quote + +This is taking things a little bit too far for me *but* it does resonate with me, and as +you'll see in the following articles (and on my configuration), I share some of Evan's +belief. By default, my desktop do only show a background (be an image or a color) and +anything related to time, battery and other status is a keybinding away (the =Win= key). + +And on the phone: [[https://robertheaton.com/2020/03/18/yourself-happier-iphone-worse/][How to make yourself happier by making your iPhone worse | Robert Heaton]] +— which I followed almost word for word, but this isn't the subject of this post. + +* Emacs, Nix, NixOS, =home-manager= and other tools + +How a GNU/Linux distribution like NixOS comes into this. First, let's see what I want : + +- A system that is tailored to my needs, that makes me efficient. +- A system that is customizable, to answer the previous item. I want to be able to modify + the default behavior if it doesn't suit me. +- A system that is reproductible and easy to replicate. If I change my hardware, or if I + need to re-install my system for /any reason/. +- A way to share configuration on my infra. This is probably the developer speaking, but I + want to write something once and re-use it several times. +- A developer environment that is reproductible, on-demand and /safe/. +- Use defaults of the software I use as much as possible. This one is tricky to achiev for + several reasons: + + I am used to customize everything (/easy to fix/) + + I use a custom keyboard layout ([[https://bepo.fr][bepo]]), so if I go with default keybinding, I will have + the wrong /muscle memory/ (and they could be not optimized either) +- Hackable tools that I can manipulate as I want. +- Use the right tool at the right time, trying not to reinvent the wheel. + +As it turns out Nix and NixOS are giving all the components required for my /almost/ ideal +setup. + +If you complete that with a /not really minimalist/ editor, called [[https://www.gnu.org/software/emacs/][GNU Emacs]], you've got a +pretty composable system without leaving Emacs. I'll dig more into my Emacs configuration +and my NixOS setup in different posts — and on the [[https://vincent.demeester.fr/configurations/][=configurations=]] pages — but my take +lately is to try to do with what is available (as built-in, in Emacs for example). After +reading the docs, if it's not sufficient, I may look for an external module or tool. + +* Conclusion + +This was a weird post that mixes up tooling, minimalism and thoughts. It's definitely not +what I initially envisioned. But I'm hoping it introduce a bit my take on things. + +* Links + +- [[http://neugierig.org/software/blog/2014/07/anti-dashboard-manifesto.html][Tech Notes: The Anti-Dashboard Manifesto]] +- [[https://blog.zdsmith.com/posts/digital-minimalism-for-the-working-hacker.html][Subset Park: Digital Minimalism for the Working Hacker]] +- [[https://jamesstuber.com/minimalism-as-a-framework/][Minimalism as a Framework for Decision Making | JamesStuber.com]] +- [[https://robertheaton.com/2020/03/18/yourself-happier-iphone-worse/][How to make yourself happier by making your iPhone worse | Robert Heaton]] + +* Footnotes + +[fn:1] And I now work for Red Hat Inc., what a journey 😝 diff --git a/www/vincent.demeester.fr/posts/2020-03-22-org-mode-website.org b/www/vincent.demeester.fr/posts/2020-03-22-org-mode-website.org @@ -0,0 +1,562 @@ +#+title: Migrating to an org-mode website +#+date: <2020-03-22 Sun> +#+filetags: orgmode website emacs +#+setupfile: ../templates/post.org + +* Introduction +:PROPERTIES: +:ID: 24a765bd-a0ed-42cf-b96c-db667f7d37e2 +:END: + +This is a story… a story of me changing the way I code and publish my website. In the +past, I've switch from [[https://vincent.demeester.fr/posts/2012-05-07-reinit-and-jekyll/][Jekyll]] to [[https://vincent.demeester.fr/posts/2015-05-01-orgmode-et-jekyll/]["=orgmode= and Jekyll"]] to [[https://vincent.demeester.fr/posts/2015-05-09-migration-to-hugo/][Hugo]] (sorry those are written +in french). The past year, I've written and documented myself a little bit about +[[https://www.theminimalists.com/minimalism/][minimalism]] and [[https://www.goodreads.com/book/show/40672036-digital-minimalism][digital minimalism]]. Although I don't see myself as a minimalist, it helped +me realize some issues I had. + +I also realized if I want to write more, I need to lower the barrier between writing and +publishing my content ; /if I want it to be published, of course/. This /post/ is about +what I'm putting in place for this, with a premise : I spend my life in [[https://www.gnu.org/software/emacs/][Emacs]] and thus in +[[https://orgmode.org/][=orgmode=]]. And [[https://orgmode.org/][=orgmode=]] is feature-full and has this badass feature : =org-publish=. + +To build and publish this website, we will /try/ to rely on a reproducible setup, meaning +[[https://www.gnu.org/software/emacs/][Emacs]] and [[https://orgmode.org/][=orgmode=]] of course, [[https://www.gnu.org/software/make/][GNU Make]] of course *but* most importantly, [[https://nixos.org/nix/][Nix]] (in the near +future 👼). + +:update: +There is now an article about it, that uses literate programming: [[../articles/meta_publishing_this_website.org][publishing this +website]]. The content of the post might no be up-to-date at some point. +:end: + + +* Requirements +:PROPERTIES: +:ID: 09991f8e-4257-443c-a3a3-40f449df4597 +:END: + +Let's list the requirements I feel I have for my website: + +- Full control over the URL of the published posts. :: + This is a golden rule of the web: should I change the publishing system, I want to be + able to stick to the same URLs or else all external references would be broken. This is + a big no-no and in my opinion it makes most blogging systems unacceptable. +- Top-notch Org support. :: + I believe generators like Jekyll and Hugo only have partial Org support. You end up + requiring some conversion tooling to get the job done. +- Simple publishing pipeline. :: + I want the generation process to be as simple as possible. This is important for + maintenance. Should I someday switch host, I want to be sure that I can set up the same + pipeline. +- Full control over the publishing system. :: + I want maximum control over the generation process. I don’t want to be restricted by a + non-Turing-complete configuration file or a dumb programming language. +- Ease of use. :: + The process as a whole must be as immediate and friction-less as possible, or else I + take the risk of feeling too lazy to publish new posts and update the content. +- Hackability. :: + Last but not least, and this probably supersedes all other requirements: The system must + be hackable. Lisp-based systems are prime contenders in that area. + +* DONE Organizing +CLOSED: [2020-03-21 Sat 17:57] +:PROPERTIES: +:ID: 78251967-c48a-476d-bc39-c748fc412927 +:END: +:LOGBOOK: +- State "DONE" from "TODO" [2020-03-21 Sat 17:57] +:END: + +Let's describe what I do want to publish here: + +- Posts :: this is what we call /blog/ these days : short to medium article that are valid + at a point of time, as may contain /deprecated/ content, or content that do not reflect + my views at a later point in time. +- Articles :: medium to long article about a topic. Those should be up-to-date or + explicitly mark as deprecated or invalid. /In a ideal world this is my *ready for the + public* knowledge database, a bit like [[https://braindump.jethro.dev/][Jethro's Braindump]]/. +- Configurations :: medium to long article about my configurations. Those are base on my + ~home~ /configuration/ mono-repository, and usually follow literate programming + principles. +- About :: an about page about the author of the website (aka [[https://vincent.demeester.fr][me]]), linking external + contributions (GitHub/Gitlab/… profiles, Talks, …). + +* DONE Publishing +CLOSED: [2020-03-25 Wed 15:54] +:PROPERTIES: +:ID: da542763-78b9-46c0-bcf7-d8d2a2b18677 +:END: +:LOGBOOK: +- State "DONE" from "TODO" [2020-03-25 Wed 15:54] +:END: + +As said above, the goal is to publish everything using only [[https://www.gnu.org/software/emacs/][Emacs]] and [[https://orgmode.org/][=orgmode=]] (with the +help of some standard GNU tools). + +The [[file:~/src/sbr/publish.el][~publish.el~]] file is where all the magic happens: + +- I want to generate something that is ~html5~ (almost?). The /preamble/, /content/ and + /postamble/ ~div~ can be customized using ~org-html-div~. Same goes for the /container/ + element that is used for "wrapping top level sections", it is customized using + ~org-html-container-helement~ (we want ~<section>~). There is a few additional variable + that I might document one day 😛. + + #+begin_src emacs-lisp + (setq org-export-use-babel nil) + (setq org-link-abbrev-alist '(("att" . org-attach-expand-link))) + + ;; setting to nil, avoids "Author: x" at the bottom + (setq org-export-with-section-numbers nil + org-export-with-smart-quotes t + org-export-with-toc nil) + + (defvar sbr-date-format "%b %d, %Y") + + (setq org-html-divs '((preamble "header" "top") + (content "main" "content") + (postamble "footer" "postamble")) + org-html-container-element "section" + org-html-metadata-timestamp-format sbr-date-format + org-html-checkbox-type 'unicode + org-html-html5-fancy t + org-html-doctype "html5" + org-html-htmlize-output-type 'css + org-html-htmlize-font-prefix "org-" + org-src-fontify-natively t + org-html-coding-system 'utf-8-unix) + #+end_src + +- Part of the ~<head>~, preamble and postamble are customized for the website. + + ~head~ :: + #+begin_src emacs-lisp + (defvar sbr-website-html-head + "<link rel='icon' type='image/x-icon' href='/images/favicon.ico'/> + <meta name='viewport' content='width=device-width, initial-scale=1'> + <link rel='stylesheet' href='/css/new.css' type='text/css'/> + <link rel='stylesheet' href='/css/syntax.css' type='text/css'/> + <link href='/index.xml' rel='alternate' type='application/rss+xml' title='Vincent Demeester' />") + #+end_src + + premable :: + #+begin_src emacs-lisp + (defun sbr-website-html-preamble (plist) + "PLIST: An entry." + ;; Skip adding subtitle to the post if :KEYWORDS don't have 'post' has a + ;; keyword + (when (string-match-p "post" (format "%s" (plist-get plist :keywords))) + (plist-put plist + :subtitle (format "Published on %s by %s." + (org-export-get-date plist sbr-date-format) + (car (plist-get plist :author))))) + + ;; Below content will be added anyways + "<nav> + <img src=\"/images/favicon.ico\" id=\"sitelogo\"/> <a href='/'>home</a> / + <a href='/posts/'>posts</a> (<a href='/index.xml'>rss</a>) / + <a href='/articles/'>articles</a> / + <a href='/configurations/'>configurations</a> / + <a href='https://dl.sbr.pm/'>files</a> / + <a href='/about/'>about</a></li> + </nav>") + #+end_src + + postamble :: + #+begin_src emacs-lisp + (defvar sbr-website-html-postamble + "<footer> + <span class='questions'>Questions, comments ? Please use my <a href=\"https://lists.sr.ht/~vdemeester/public-inbox\">public inbox</a> by sending a plain-text email to <a href=\"mailto:~vdemeester/public-inbox@lists.sr.ht\">~vdemeester/public-inbox@lists.sr.ht</a>.</span> + <span class='opinions'>Opinions stated here are my own and do not express the views of my employer, spouse, children, pets, neighbors, secret crushes, favorite authors, or anyone else who is not me. And maybe not even me, depending on how old this is.</span> + <span class='copyright'> + Content and design by Vincent Demeester + (<a rel='licence' href='http://creativecommons.org/licenses/by-nc-sa/3.0/'>Some rights reserved</a>) + </span><br /> + <span class='engine'> + Powered by <a href='https://www.gnu.org/software/emacs/'>Gnu Emacs</a> and <a href='https://orgmode.org'>orgmode</a> + </span> + </footer>") + #+end_src +- =orgmode= is able to generate a site-map. This is what we are going to use to generate + the index files for ~posts/~ and ~articles~ mainly. + #+begin_src emacs-lisp + (defun sbr/org-sitemap-format-entry (entry style project) + "Format posts with author and published data in the index page. + + ENTRY: file-name + STYLE: + PROJECT: `posts in this case." + (cond ((not (directory-name-p entry)) + (format "%s — [[file:%s][%s]] + :PROPERTIES: + :PUBDATE: [%s] + :END:" + (format-time-string "%Y-%m-%d" + (org-publish-find-date entry project)) + entry + (org-publish-find-title entry project) + (format-time-string "%Y-%m-%d" + (org-publish-find-date entry project)))) + ((eq style 'tree) (file-name-nondirectory (directory-file-name entry))) + (t entry))) + + (defun sbr/org-publish-sitemap (title list) + "" + (concat "#+TITLE: " title "\n\n" + (org-list-to-subtree list))) + #+end_src +- =orgmode= is able to generate a rss, with =ox-rss=. This is what we are going to use to + generate the rss files for ~posts~ and ~articles~. This is _heavily_ customized. + #+begin_src emacs-lisp + (defun sbr/org-get-first-paragraph (file) + "Get string content of first paragraph of file." + (ignore-errors + (with-temp-buffer + (insert-file-contents file) + (goto-char (point-min)) + (show-all) + (let ((first-begin (progn + (org-forward-heading-same-level 1) + (next-line) + (point))) + (first-end (progn + (org-next-visible-heading 1) + (point)))) + (buffer-substring first-begin first-end))))) + + (defun sbr/org-rss-publish-to-rss (plist filename pub-dir) + "Prepare rss.org file before exporting." + (let* ((postsdir (plist-get plist :base-directory))) + (with-current-buffer (find-file filename) + (erase-buffer) + (insert "#+TITLE: Posts\n") + (insert "#+AUTHOR: Vincent Demeester\n") + (insert "#+OPTIONS: toc:nil\n") + (let* ((files-all + (reverse (directory-files "." nil + "[0-9-]+.*\\.org$"))) + (files (subseq files-all 0 (min (length files-all) 30)))) + (message (format "foo: %s" filename)) + (dolist (post files) + (let* ((post-file post) + (post-title (org-publish-find-title post-file plist)) + (preview-str (sbr/org-get-first-paragraph post-file)) + (date (replace-regexp-in-string + "\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\)-.*" + "\\1" post))) + (insert (concat "* [[file:" postsdir "/" post "][" post-title "]]\n\n")) + (org-set-property "ID" post) + (org-set-property "RSS_TITLE" post-title) + ;; ox-rss prepends html-link-home to permalink + (org-set-property "RSS_PERMALINK" + (concat postsdir "/" + (file-name-sans-extension post) + ".html")) + (org-set-property + "PUBDATE" + (format-time-string + "<%Y-%m-%d %a %H:%M>" + (org-time-string-to-time + (replace-regexp-in-string + "\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\)-.*" + "\\1" post)))) + (insert preview-str) + (newline 1) + (insert (concat "[[file:" postsdir "/" post "][(Read more)]]\n\n")))) + (save-buffer)))) + (let ((user-mail-address "t") + (org-export-with-broken-links t) + (org-rss-use-entry-url-as-guid nil)) + (org-rss-publish-to-rss plist filename pub-dir))) + #+end_src +- Finally let's set the ~org-publish-project-alist~ to publish our projects + #+begin_src emacs-lisp + (setq org-publish-project-alist + `(("posts" + :base-directory "posts" + :base-extension "org" + :recursive t + :publishing-function org-html-publish-to-html + :publishing-directory "./public/posts" + :exclude ,(regexp-opt '("README.org" "draft")) + :auto-sitemap t + :with-footnotes t + :with-toc nil + :with-drawers t + :sitemap-filename "index.org" + :sitemap-title "Posts" + :sitemap-format-entry sbr/org-sitemap-format-entry + :sitemap-style list + :sitemap-sort-files anti-chronologically + :sitemap-function sbr/org-publish-sitemap + :html-head-include-scripts nil + :html-head-include-default-style nil + :html-head ,sbr-website-html-head + :html-preamble sbr-website-html-preamble + :html-postamble ,sbr-website-html-postamble) + ("posts-rss" + :base-directory "posts" + :base-extension "org" + :recursive t + :html-link-home "https://vincent.demeester.fr/" + :rss-link-home "https://vincent.demeester.fr/posts/" + :html-link-use-abs-url t + :rss-extension "xml" + :publishing-directory "./public" + :publishing-function (sbr/org-rss-publish-to-rss) + :section-number nil + :exclude ".*" + :include ("index.org")) + ("articles" + :base-directory "articles" + :base-extension "org" + :recursive t + :publishing-function org-html-publish-to-html + :publishing-directory "./public/articles" + :exclude ,(regexp-opt '("README.org" "draft")) + :auto-sitemap t + :with-footnotes t + :with-toc nil + :with-drawers t + :sitemap-filename "sitemap.org" + :sitemap-title "Articles" + :sitemap-style tree + :sitemap-sort-files anti-chronologically + ;;:sitemap-format-entry sbr/org-sitemap-format-entry + ;;:sitemap-function sbr/org-publish-sitemap + :html-head-include-scripts nil + :html-head-include-default-style nil + :html-head ,sbr-website-html-head + :html-preamble sbr-website-html-preamble + :html-postamble ,sbr-website-html-postamble) + ("articles-assets" + :exclude ,(regexp-opt '("*.org")) + :base-directory "articles" + :base-extension ,site-attachments + :publishing-directory "./public/articles" + :publishing-function org-publish-attachment + :recursive t) + ("about" + :base-directory "about" + :base-extension "org" + :exclude ,(regexp-opt '("README.org" "draft")) + :index-filename "index.org" + :recursive nil + :with-footnotes t + :with-toc nil + :with-drawers t + :publishing-function org-html-publish-to-html + :publishing-directory "./public/about" + :html-head-include-scripts nil + :html-head-include-default-style nil + :html-head ,sbr-website-html-head + :html-preamble sbr-website-html-preamble + :html-postamble ,sbr-website-html-postamble) + ("index" + :base-directory "" + :base-extension "org" + :exclude ,(regexp-opt '("README.org" "draft")) + :index-filename "index.org" + :recursive nil + :with-footnotes t + :with-toc nil + :with-drawers t + :with-title nil + :publishing-function org-html-publish-to-html + :publishing-directory "./public" + :html-head-include-scripts nil + :html-head-include-default-style nil + :html-head ,sbr-website-html-head + :html-preamble sbr-website-html-preamble + :html-postamble ,sbr-website-html-postamble) + ("css" + :base-directory "./css" + :base-extension ,site-attachments + :recursive t + :publishing-directory "./public/css" + :publishing-function org-publish-attachment + :recursive t) + ("images" + :base-directory "./images" + :base-extension ,site-attachments + :publishing-directory "./public/images" + :publishing-function org-publish-attachment + :recursive t) + ("assets" + :base-directory "./assets" + :base-extension ,site-attachments + :publishing-directory "./public/assets" + :publishing-function org-publish-attachment + :recursive t) + ("legacy" + :base-directory "./legacy" + :base-extension ,site-attachments + :publishing-directory "./public/" + :publishing-function org-publish-attachment + :recursive t) + ("all" :components ("posts" "about" "index" "articles" "articles-assets" "css" "images" "assets" "legacy" "posts-rss")))) + #+end_src + +Here are some /inspiration/ I took for this publishing code: + +- [[https://thibaultmarin.github.io/blog/posts/2016-11-13-Personal_website_in_org.html][Personal website in org]] +- [[https://vicarie.in/posts/blogging-with-org.html][Blogging with Org publishing]] +- [[https://ambrevar.xyz/blog-architecture/][A blog in pure Org/Lisp]] +- [[https://zngguvnf.org/2017-07-13--blogging-with-org-static-blog.html][Blogging with org-static-blog]] +- [[https://bastibe.de/2013-11-13-blogging-with-emacs.html][Blogging with Emacs]] +- [[https://gjhenrique.com/meta.html][Blogging with org-mode and Gitlab Pages]] + + +* DONE Styling +CLOSED: [2020-03-23 Mon 19:02] +:PROPERTIES: +:ID: 052e297f-01b2-41d3-9689-cd2946b56cfb +:END: +:LOGBOOK: +- State "DONE" from "STARTED" [2020-03-23 Mon 19:02] +:END: + +The style of the website has be as simple as possible, and also really light. This means: +- use default system font as much as possible +- have a small stylesheet, rely on the default as much as we can + +In addition, I want support for: +- side notes +- code syntax highlight +- table of content + +The inspiration for this website, in term of style are the following: +- [[https://vincent.demeester.fr/posts/2018-08-16-gotest-tools-assertions/][Vincent Demeester]] +- [[https://braindump.jethro.dev/zettels/zettelkasten/][Jethro's Braindump]] +- [[https://hamberg.no/gtd/][GTD in 15 minutes – A Pragmatic Guide to Getting Things Done]] +- [[https://www.inkandswitch.com/local-first.html][Local-first software: You own your data, in spite of the cloud]] +- [[https://archive.casouri.cat/note/2018/blog-in-org-mode-revisited/index.html][Blog in Org Mode, Revisited]] +- [[https://kind.sigs.k8s.io/][kind]] +- [[http://willcrichton.net/notes/idioms-of-dynamic-languages/][Idioms of Dynamic Languages | Will Crichton]] +- [[https://peter.bourgon.org/blog/2019/09/11/programming-with-errors.html][Peter Bourgon · Programming with errors]] +- [[https://johv.dk/blog/bare-metal-assembly-tutorial.html][Getting started with bare-metal assembly — Jonas Hvid]] + +To be able to define the style a bit, let's try some things below. From this point on, +this is random content just to try my style out. 👼 + +There is more in [[../articles/sandbox.org][the sandbox]]. + +------ + +#+begin_abstract +Let's dig into how I setup my development environment when working on ~tektoncd/pipeline~ +#+end_abstract + +#+TOC: headlines 2 + +** sub-heading 1 + +Checking for errors is *very common* in Go, having =Comparison= function for it was a requirement. + +#+BEGIN_aside +This is a side note. If collection is a string, item must also be a string, and is +compared using =strings.Contains()=. If collection is a Map, contains will succeed if item +is a key in the map. +#+END_aside + +- =Error= fails if the error is =nil= *or* the error message is not the expected one. +- =ErrorContains= fails if the error is =nil= *or* the error message does not contain the expected substring. +- =ErrorType= fails if the error is =nil= *or* the error type is not the expected type. + +Let's first look at the most used : =Error= and =ErrorContains=. +When you're working on ~pipeline~, usually you want : + +1. make sure it compiles : ~go build -v ./..~ +2. Running unit tests : ~go test ./...~ (bonus use [[https://github.com/vdemeester/ram][~ram~]] for continuous testing) +3. End-to-end tests : ~go test -tags e2e ./...~ (or simply using `./test/` package) + + *Make sure you re-deploy before running the e2e tests* using ~ko apply -f ./config~, + otherwise you're testing the wrong code. + +#+BEGIN_SRC go + var err error + // will fail with : expected an error, got nil + assert.Check(t, cmp.Error(err, "message in a bottle")) + err = errors.Wrap(errors.New("other"), "wrapped") + // will fail with : expected error "other", got "wrapped: other" + assert.Check(t, cmp.Error(err, "other")) + // will succeed + assert.Check(t, cmp.ErrorContains(err, "other")) +#+END_SRC + +#+CAPTION: This is the caption for the next figure link (or table) +#+NAME: fig:SED-HR4049 +#+ATTR_ORG: :width 400/600 +#+ATTR_HTML: :width 100% +[[../images/emacs/2020-02-29-13-46-08.png]] + +If this is the case, then what makes dynamic languages feel easy? Can we take what we learn in answering this question and improve the ergonomics of our static languages? For example, in 2018, I don’t think there’s as strong an argument for “you don’t have to write out the types,” since modern type inference eliminates most of the keystrokes required (even though many major languages still lack such facilities). Plus, saving a few keystrokes does not seem like a critical bottleneck in the programming process. + +#+NAME: fig:tektoncd-logo +[[../images/emacs/2020-02-29-14-41-59.png]] + + +** sub-heading 2 + +Some content from my other org-mode files. + +#+begin_description +I already wrote 2 previous posts about golang and testing. It's something I care deeply about and I wanted to continue +writing about it. It took me a bit more time than I thought, but getting back to it. Since the [[http://vincent.demeester.fr/posts/2017-04-22-golang-testing-golden-file/][last post]], Daniel +Nephin +and I worked (but mainly Daniel 🤗) on bootstrapping a testing helper library. +#+end_description + +#+BEGIN_QUOTE +Package assert provides assertions for comparing expected values to actual values. When +assertion fails a helpful error message is printed. +#+END_QUOTE + +There is already some good =testing= helpers in the Go ecosystem : [[https://github.com/stretchr/testify][=testify=]], [[http://labix.org/gocheck][=gocheck=]], +[[https://github.com/onsi/ginkgo][=ginkgo=]] and a lot more — so why create a new one ? There is multiple reason for it, most +of them can be seen in the following [[https://github.com/gotestyourself/gotest.tools/issues/49#issuecomment-362436026][GitHub issue]]. + +[[https://github.com/dnephin/][Daniel]] also wrote a very useful [fn:1]converter if your code base is currently using =testify= : +=gty-migrate-from-testify=. + +#+BEGIN_SRC sh +$ go get -u gotest.tools/assert/cmd/gty-migrate-from-testify +# […] +$ go list \ + -f '{{.ImportPath}} {{if .XTestGoFiles}}{{"\n"}}{{.ImportPath}}_test{{end}}' \ + ./... | xargs gty-migrate-from-testify +#+END_SRC + +We'll Use =Assert= for the rest of the section but any example here would work with +=Check= too. When we said =Comparison= above, it's mainly the [[https://godoc.org/gotest.tools/assert#BoolOrComparison][BoolOrComparison]] interface — +it can either be a boolean expression, or a [[https://godoc.org/gotest.tools/assert/cmp#Comparison][cmp.Comparison]] type. =Assert= and =Check= code +will be /smart/ enough to detect which one it is. + +#+BEGIN_SRC go + assert.Assert(t, ok) + assert.Assert(t, err != nil) + assert.Assert(t, foo.IsBar()) +#+END_SRC + + +* What's next ? +:PROPERTIES: +:ID: 678bf6cb-68e7-4fc9-af50-23d283293ab1 +:END: + +One thing is to import old blog posts from [[https://vincent.demeester.fr][vincent.demeester.fr]]. This is easily done with +[[https://pandoc.org/][Pandoc]] and a small bash loop — and some manual adjusting later on 😛. + +#+begin_src bash +for post in ~/src/github.com/vdemeester/blog/content/posts/*.md; do + pandoc -f markdown -t org -o posts/$(basename -s .md ${post}).org ${post} +done +#+end_src + + +What is still /to do/ after this initial take. + +- [ ] List =FILETAGS= for taximony +- [ ] Maybe use [[https://css-tricks.com/snippets/css/complete-guide-grid/][css grid]] for the UI + +* Footnotes +:PROPERTIES: +:ID: 220a0cce-39c3-4b5f-aaa0-66e3a2450f04 +:END: + +[fn:1] foo is bar, bar is baz diff --git a/www/vincent.demeester.fr/posts/2020-04-15-emacs-bankruptcy-is-fun.org b/www/vincent.demeester.fr/posts/2020-04-15-emacs-bankruptcy-is-fun.org @@ -0,0 +1,108 @@ +#+title: Emacs bankruptcy is fun +#+date: <2020-04-15 Wed> +#+filetags: emacs configuration optimization +#+setupfile: ../templates/post.org + +* Introduction + +Since go 1.14 go released, I've had a broken =go-mode= setup on my Emacs. I was using +=lsp-mode= and =gopls= and well, the update broke everything. I initally try to fix it but +I made it worse. At the same time, I started to get fed up with some performance issue of +my configuration and how slow my Emacs starts, about 6s. + +I, thus, declared my third Emacs bankruptcy, =:disabled= everything and slowly started +from scratch, with the following goal: + +- Have it start quick, as less than a second, not too much more than =emacs -Q= would +- Disable anything that I don't use often initially +- Try to use as much built-in as possible (example: using =icomplete= instead of + =ivy=/=counsel=) + +* Do I really need this feature + +Following [[https://protesilaos.com/][Protesilaos Stavrou]]'s emacs videos (and [[https://protesilaos.com/dotemacs/][=dotemacs=]]) for a while now, I have a +tendency to try to use built-in feature as much as possible. The most obvious example is +using =icomplete= instead of =ivy=/=counsel=. + +When I started my /bankruptcy/, I disabled every single customization I had, either using +=:disabled= when using =use-package= *or* the =(when nil)= /hack/. I then started Emacs +and acted on what was missing : + +1. Do I really miss it ? An example would be, at least initially, the completion in a =go= + file. I do miss it, but I miss it *way less* than having Emacs lagging because of + =lsp-mode= and showing me wrong completion. +2. Is there a built-in option to what I previously used ? Here, the =icomplete= example + fits well, or =isearch= instead of =swiper=. +3. Do I need it at startup or /on-demand/ ? + +* Looking into what takes time + +In "Advanced Techniques for Reducing Emacs Startup Time"[fn:1], I discovered the [[https://github.com/jschaf/esup][=esup=]] +emacs library. In a gist, this is a profiler for Emacs. It starts a new Emacs instance and +look at the loading time. + +#+CAPTION: esup "result" view +[[../images/2020-04-15-16-12-54.png]] + +Then, you can do a simple loop: + +- Run =esup= +- Look at the top result +- Fix it (lazy load, removing the feature, …) +- Re-iterate + +* Loading on-demand + +Once you have the setup to know what takes time and what not, it's time to look into how +to load the most thing on demand. + +For this, [[https://github.com/jwiegley/use-package][=use-package=]] is amazing, it tremendously help autoloading modules on-demand. If +you are not using =use-package=, usually you are using =require=, which loads the +underlying source file (all of it). + +With =use-package=, there is multiple ways to load on demand: + +- =:commands= to add callable that will trigger loading the package +- =:bind=, =:bind*=, =:bind-keymap= and =:bind-keymap*= to bind key sequences to the + global keymap or a specific keymap. +- =:mode= and =:interpreter= establish a deferred binding with the =auto-mode-alist= and + =interpreter-mode-alist=. +- =:hook= allows adding functions onto the package hook +- =:defer= is the most generic one, all the previous keyword imply =:defer=. You can + specify a number of second of idle to load the package. + +#+BEGIN_aside +In a gist for =org-babel=, use =use-package ob-python= and never call =org-babel-do-languages=. +#+END_aside +Once this is done, you are left with edge cases, like =org-babel-do-languages=. Those are +to be handle case by case. The good thing about those cases is that you'll learn what +those function do and this will give you an even better understanding of what is +happening. + +Doing this exercise also forces you to make you see if you really use that feature or +not. I ended up removing entire feature from my configuration because they were taking +quite some time to load, and was used almost never. Instead I am forcing myself to learn +more what I can do with the built-in features first. + +* Conclusion + +All in all, this /bankruptcy/ was the most fun I had to do. I consider myself still in the +process but the base is there. + +1. I learned a lot ! +2. My Emacs starts in 0.6s against previously in 5s — =emacs -q= starts in about 0.3s so + there is still a little bit of room for improvement. +3. I discovered / re-discovered a lot of built-in feature +4. I started documenting my configuration, see [[../configurations/emacs.org][here]]. + +🎉 + +:update: +Well, I've look into the /portable dump/ feature of Emacs, thanks to [[https://archive.casouri.cat/note/2020/painless-transition-to-portable-dumper/index.html][Painless Transition +to Portable Dumper]]. And I am now down to =0.091s= for the startup. There is a few gotchas +with /portable dump/, I'll try to write about it later. +:end: + +* Footnotes + +[fn:1]: [[https://blog.d46.us/advanced-emacs-startup/][Advanced Techniques for Reducing Emacs Startup Time]] diff --git a/www/vincent.demeester.fr/posts/2020-06-21-website-update.org b/www/vincent.demeester.fr/posts/2020-06-21-website-update.org @@ -0,0 +1,16 @@ +#+title: website update +#+date: <2020-06-08 Mon> +#+filetags: website css new simple +#+setupfile: ../templates/post.org + +* Introduction + +A really small article to talk about small updates on the website (/well, maybe not that +small/). In a gist: a new css and articles changes. + +- Updating the style of the website to something really similar to [[https://newcss.net/][new.css]]. It is way + simpler and /pleasing/ (at least for me). +- My [[../articles][=/articles=]] are now exporting from my [[../articles/personal_knowledge_base.org][personal knowledge base]], powered by + [[../articles/org_roam.org][=org-roam=]]. +- A new [[https://dl.sbr.pm/][=/files=]] part is available, where I share some random file. See for example the + [[https://dl.sbr.pm/wallpapers/dynamics/][dynamic wallpaper]] part. diff --git a/www/vincent.demeester.fr/posts/2020-07-08-june-status-update.org b/www/vincent.demeester.fr/posts/2020-07-08-june-status-update.org @@ -0,0 +1,109 @@ +#+title: Status update, June 2020 +#+date: <2020-07-08 Wed> +#+filetags: status update +#+setupfile: ../templates/post.org + +* Introduction + +Time for the first new monthly status update! I do like those updates from [[https://drewdevault.com/2020/06/15/Status-update.html][Drew DeVault]] +and [[https://emersion.fr/blog/2020/status-update-19/][Simon Ser]], so I figured, why not trying myself 🙃. I am not sure where to start and +where to end, but I guess I'll figure things out as I go. + +* Tekton & OpenShift Pipelines + +As you know, /or may not/, I am working on the [[https://github.com/tektoncd/][TektonCD]] project and also on our [[https://redhat.com][RedHat]] +product [[https://www.openshift.com/learn/topics/pipelines][OpenShift Pipelines]]. As far as the month of June went : + +- We release [[https://github.com/tektoncd/pipeline/releases/tag/v0.13.0][v0.13.0]] "Bobtail Bishop" and a bunch of fixes ([[https://github.com/tektoncd/pipeline/releases/tag/v0.13.2][v0.13.2]]) + + It's the second release after the =v1beta1= bump, and we start to stabilise things. + + [[https://github.com/tektoncd/pipeline/releases/tag/v0.14.0][v0.14.0]] will (and actually is already) a more feature-packed release because there is + now the =finally= field support and cloud-events opt-in. +- But the *most important* contributions I tried to make during that month is the TEP + process. *TEP* stands for *Tekton Enhancement proposals*. + + #+begin_src markdown + # Tekton Enhancement Proposals (TEPs) + + A Tekton Enhancement Proposal (TEP) is a way to propose, communicate + and coordinate on new efforts for the Tekton project. You can read + the full details of the project in + [TEP-1](https://github.com/tektoncd/community/blob/master/teps/0001-tekton-enhancement-proposal-process.md). + + ## What is a TEP + + A standardized development process for Tekton is proposed in order to + + - provide a common structure and clear checkpoints for proposing + changes to Tekton + - ensure that the motivation for a change is clear + - allow for the enumeration stability milestones and stability + graduation criteria + - persist project information in a Version Control System (VCS) for + future Tekton users and contributors + - support the creation of _high value user facing_ information such + as: + - an overall project development roadmap + - motivation for impactful user facing changes + - ensure community participants are successfully able to drive changes + to completion across one or more releases while stakeholders are + adequately represented throughout the process + + This process is supported by a unit of work called a Tekton + Enhancement Proposal (TEP). A TEP attempts to combine aspects of the + following: + + - feature, and effort tracking document + - a product requirements document + - design document + + into one file which is created incrementally in collaboration with one + or more [Working + Groups](https://github.com/tektoncd/community/blob/master/working-groups.md) + (WGs). + + This process does not block authors from doing early design docs using + any means. It does not block authors from sharing those design docs + with the community (during Working groups, on Slack, GitHub, …. + + ,**This process acts as a requirement when a design docs is ready to be + implemented or integrated in the `tektoncd` projects**. In other words, + a change that impact other `tektoncd` projects or users cannot be + merged if there is no `TEP` associated with it. + + This TEP process is related to + - the generation of an architectural roadmap + - the fact that the what constitutes a feature is still undefined + - issue management + - the difference between an accepted design and a proposal + - the organization of design proposals + + This proposal attempts to place these concerns within a general + framework. + + + See [TEP-1](https://github.com/tektoncd/community/blob/master/teps/0001-tekton-enhancement-proposal-process.md) for more details. + #+end_src + +* =home=, Nixos and the rest of thing + +I did some big changes in my [[https://git.sr.ht/~vdemeester/home][=home=]] repository, it's still much a work-in-progress but it +is in a way better state than before. + +- It is more reproductible. All dependencies are managed by [[https://github.com/nmattia/niv][niv]] and all machines are using + a pinned version of channels from there. That way I can test the configuration (on the + CI) and cache the packages for all channels that my machines uses. I also can decide + when I want to upgrade a particular channel (nixos, unstable, …). +- I am slowly experimenting on simplifying things more and more. This is a bit related to + the [[file:2020-02-22-digital-minimalism.org][previous post]]. I am trying to use Gnome3 everywhere. Well configured, managed by + NixOS, it's enough for my test and reduce the configuration cruft I need to do. + +An ongoing work is my knowledge base and how it is published as part of this website : +[[https://vincent.demeester.fr/articles/][articles]]. I am trying to make all those publishable (for the one that do not hold any +secret). I'll document this a bit more at some point but… +- Configurations are now part of that knowledge base, the =docs= folder of [[https://git.sr.ht/~vdemeester/home][=home=]] is going + away. +- I am trying to use litterate configuration as much as possible. So slowly, the content + from [[https://git.sr.ht/~vdemeester/home][=home=]] will be a tangled version of my knowledge base. At least that is the goal as + of today. + +And I feel that's all /for June/. diff --git a/www/vincent.demeester.fr/posts/2020-12-01-nixify-www.org b/www/vincent.demeester.fr/posts/2020-12-01-nixify-www.org @@ -0,0 +1,8 @@ +#+title: Nixifying this website +#+date: <2020-12-01 Fri> +#+filetags: website nix +#+setupfile: ../templates/post.org + +* Introduction + +… diff --git a/www/vincent.demeester.fr/posts/diving-into-nix.org.draft b/www/vincent.demeester.fr/posts/diving-into-nix.org.draft @@ -0,0 +1,8 @@ +#+TITLE: Diving into Nix(OS) — introduction +#+SUBTITLE: Series about nixos +#+SETUPFILE: ../templates/post.org + +* TODO Introduction + +This will be the start of a serie of blog post around Nix, Nixpkgs and NixOS. The goal is +to present the pros and cons of Nix and how it benefits to use it in different use cases. diff --git a/www/vincent.demeester.fr/posts/quick-kubernetes-provisionning.org.draft b/www/vincent.demeester.fr/posts/quick-kubernetes-provisionning.org.draft @@ -0,0 +1,5 @@ +#+TITLE: Quickly provision Kubernetes +#+SUBTITLE: Experiment with different tools to see what is the most efficient way to provision a multi-node kubernetes cluster +#+SETUPFILE: ../templates/post.org + +* TODO Introduction diff --git a/www/vincent.demeester.fr/publish-common.el b/www/vincent.demeester.fr/publish-common.el @@ -0,0 +1,277 @@ +;;; publish-common.el --- Commons code for www publishing projects -*- lexical-binding: t; -*- +;; Author: Vincent Demeester <vincent@sbr.pm> + +;;; Commentary: +;; +;;; Code: +;; load org +(require 'org) +(require 'dash) +;; load org export functions +(require 'ox-publish) +(require 'ox-rss) +(require 'ox-html) +;; load org link functions +(require 'ol-man) +(require 'ol-git-link) +;; Those are mine +(require 'ol-github) +(require 'ol-gitlab) +(require 'org-attach) +;; load additional libraries +(require 'go-mode) +(require 'css-mode) +(require 'yaml-mode) +(require 'nix-mode) + +(require 's) + +(setq org-export-use-babel nil) +(setq org-link-abbrev-alist '(("att" . org-attach-expand-link))) + +;; setting to nil, avoids "Author: x" at the bottom +(setq org-export-with-section-numbers nil + org-export-with-smart-quotes t + org-export-with-toc nil) + +(defvar sbr-date-format "%b %d, %Y") + +(setq org-html-divs '((preamble "header" "top") + (content "main" "content") + (postamble "footer" "postamble")) + org-html-container-element "section" + org-html-metadata-timestamp-format sbr-date-format + org-html-checkbox-type 'unicode + org-html-html5-fancy t + org-html-doctype "html5" + org-html-htmlize-output-type 'css + org-html-htmlize-font-prefix "org-" + org-src-fontify-natively t + org-html-coding-system 'utf-8-unix) + +(defun sbr/org-export-format-drawer (name content) + "HTML export of drawer with NAME and CONTENT. +name is the name of the drawer, that will be used as class. +content is the content of the drawer" + (format "<div class='drawer %s'>\n<h6>%s</h6>\n%s</div>" + (downcase name) + (capitalize name) + content)) +(setq org-html-format-drawer-function 'sbr/org-export-format-drawer) + +(defun read-file (filePath) + "Return FILEPATH's file content." + (with-temp-buffer + (insert-file-contents filePath) + (buffer-string))) + +(defvar sbr-website-html-head + "<link rel='icon' type='image/x-icon' href='/images/favicon.ico'/> +<meta name='viewport' content='width=device-width, initial-scale=1'> +<link rel='stylesheet' href='/css/new.css' type='text/css'/> +<link rel='stylesheet' href='/css/syntax.css' type='text/css'/> +<link href='/index.xml' rel='alternate' type='application/rss+xml' title='Vincent Demeester' />") + +(defun sbr-website-html-preamble (plist) + "PLIST: An entry." + ;; Skip adding subtitle to the post if :KEYWORDS don't have 'post' has a + ;; keyword + (when (string-match-p "post" (format "%s" (plist-get plist :keywords))) + (plist-put plist + :subtitle (format "Published on %s by %s." + (org-export-get-date plist sbr-date-format) + (car (plist-get plist :author))))) + + ;; Below content will be added anyways + "<nav> +<img src=\"/images/favicon.ico\" id=\"sitelogo\"/> <a href='/'>home</a> / +<a href='/posts/'>posts</a> (<a href='/index.xml'>rss</a>) / +<a href='/articles/'>articles</a> / +<a href='https://dl.sbr.pm/'>files</a> / +<a href='/about/'>about</a></li> +</nav>") + +(defvar sbr-website-html-postamble + "<footer> + <span class='questions'>Questions, comments ? Please use my <a href=\"https://lists.sr.ht/~vdemeester/public-inbox\">public inbox</a> by sending a plain-text email to <a href=\"mailto:~vdemeester/public-inbox@lists.sr.ht\">~vdemeester/public-inbox@lists.sr.ht</a>.</span> + <span class='opinions'>Opinions stated here are my own and do not express the views of my employer, spouse, children, pets, neighbors, secret crushes, favorite authors, or anyone else who is not me. And maybe not even me, depending on how old this is.</span> + <span class='copyright'> + Content and design by Vincent Demeester + (<a rel='licence' href='http://creativecommons.org/licenses/by-nc-sa/3.0/'>Some rights reserved</a>) + </span><br /> + <span class='engine'> + Powered by <a href='https://www.gnu.org/software/emacs/'>Gnu Emacs</a> and <a href='https://orgmode.org'>orgmode</a> + </span> +</footer>") +(defvar site-attachments + (regexp-opt '("jpg" "jpeg" "gif" "png" "svg" + "ico" "cur" "css" "js" "woff" "html" "pdf" "otf")) + "File types that are published as static files.") + +(defun sbr/org-sitemap-format-entry (entry style project) + "Format posts with author and published data in the index page. + +ENTRY: file-name +STYLE: +PROJECT: `posts in this case." + (cond ((not (directory-name-p entry)) + (format "%s — [[file:%s][%s]] + :PROPERTIES: + :PUBDATE: [%s] + :END:" + (format-time-string "%Y-%m-%d" + (org-publish-find-date entry project)) + entry + (org-publish-find-title entry project) + (format-time-string "%Y-%m-%d" + (org-publish-find-date entry project)))) + ((eq style 'tree) (file-name-nondirectory (directory-file-name entry))) + (t entry))) + +(defun sbr/org-publish-sitemap (title list) + "" + (concat "#+TITLE: " title "\n\n" + (org-list-to-subtree list))) + +(defun sbr/org-get-first-paragraph (file) + "Get string content of first paragraph of file." + (ignore-errors + (with-temp-buffer + (insert-file-contents file) + (goto-char (point-min)) + (show-all) + (let ((first-begin (progn + (org-forward-heading-same-level 1) + (next-line) + (point))) + (first-end (progn + (org-next-visible-heading 1) + (point)))) + (buffer-substring first-begin first-end))))) + +(defun sbr/org-rss-publish-to-rss (plist filename pub-dir) + "Prepare rss.org file before exporting." + (let* ((postsdir (plist-get plist :base-directory))) + (with-current-buffer (find-file filename) + (erase-buffer) + (insert "#+TITLE: Posts\n") + (insert "#+AUTHOR: Vincent Demeester\n") + (insert "#+OPTIONS: toc:nil\n") + (let* ((files-all + (reverse (directory-files "." nil + "[0-9-]+.*\\.org$"))) + (files (seq-subseq files-all 0 (min (length files-all) 30)))) + (message (format "foo: %s" filename)) + (dolist (post files) + (let* ((post-file post) + (post-title (org-publish-find-title post-file plist)) + (preview-str (sbr/org-get-first-paragraph post-file)) + (date (replace-regexp-in-string + "\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\)-.*" + "\\1" post))) + (insert (concat "* [[file:" postsdir "/" post "][" post-title "]]\n\n")) + (org-set-property "ID" post) + (org-set-property "RSS_TITLE" post-title) + ;; ox-rss prepends html-link-home to permalink + (org-set-property "RSS_PERMALINK" + (concat postsdir "/" + (file-name-sans-extension post) + ".html")) + (org-set-property + "PUBDATE" + (format-time-string + "<%Y-%m-%d %a %H:%M>" + (org-time-string-to-time + (replace-regexp-in-string + "\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\)-.*" + "\\1" post)))) + (insert preview-str) + (newline 1) + (insert (concat "[[file:" postsdir "/" post "][(Read more)]]\n\n")))) + (save-buffer)))) + (let ((user-mail-address "t") + (org-export-with-broken-links t) + (org-rss-use-entry-url-as-guid nil)) + (org-rss-publish-to-rss plist filename pub-dir))) + +(advice-add #'org-export-get-reference :override #'unpackaged/org-export-get-reference) + +(defun unpackaged/org-export-get-reference (datum info) + "Like `org-export-get-reference', except uses heading titles instead of random numbers." + (let ((cache (plist-get info :internal-references))) + (or (car (rassq datum cache)) + (let* ((crossrefs (plist-get info :crossrefs)) + (cells (org-export-search-cells datum)) + ;; Preserve any pre-existing association between + ;; a search cell and a reference, i.e., when some + ;; previously published document referenced a location + ;; within current file (see + ;; `org-publish-resolve-external-link'). + ;; + ;; However, there is no guarantee that search cells are + ;; unique, e.g., there might be duplicate custom ID or + ;; two headings with the same title in the file. + ;; + ;; As a consequence, before re-using any reference to + ;; an element or object, we check that it doesn't refer + ;; to a previous element or object. + (new (or (cl-some + (lambda (cell) + (let ((stored (cdr (assoc cell crossrefs)))) + (when stored + (let ((old (org-export-format-reference stored))) + (and (not (assoc old cache)) stored))))) + cells) + (when (org-element-property :raw-value datum) + ;; Heading with a title + (unpackaged/org-export-new-title-reference datum cache)) + ;; NOTE: This probably breaks some Org Export + ;; feature, but if it does what I need, fine. + (org-export-format-reference + (org-export-new-reference cache)))) + (reference-string new)) + ;; Cache contains both data already associated to + ;; a reference and in-use internal references, so as to make + ;; unique references. + (dolist (cell cells) (push (cons cell new) cache)) + ;; Retain a direct association between reference string and + ;; DATUM since (1) not every object or element can be given + ;; a search cell (2) it permits quick lookup. + (push (cons reference-string datum) cache) + (plist-put info :internal-references cache) + reference-string)))) + +(defun unpackaged/org-export-new-title-reference (datum cache) + "Return new reference for DATUM that is unique in CACHE." + (cl-macrolet ((inc-suffixf (place) + `(progn + (string-match (rx bos + (minimal-match (group (1+ anything))) + (optional "--" (group (1+ digit))) + eos) + ,place) + ;; HACK: `s1' instead of a gensym. + (-let* (((s1 suffix) (list (match-string 1 ,place) + (match-string 2 ,place))) + (suffix (if suffix + (string-to-number suffix) + 0))) + (setf ,place (format "%s--%s" s1 (cl-incf suffix))))))) + (let* ((title (org-element-property :raw-value datum)) + (ref (url-hexify-string (substring-no-properties title))) + (parent (org-element-property :parent datum))) + (while (--any (equal ref (car it)) + cache) + ;; Title not unique: make it so. + (if parent + ;; Append ancestor title. + (setf title (concat (org-element-property :raw-value parent) + "--" title) + ref (url-hexify-string (substring-no-properties title)) + parent (org-element-property :parent parent)) + ;; No more ancestors: add and increment a number. + (inc-suffixf ref))) + ref))) + +(provide 'publish-common) +;;; publish-common.el ends here diff --git a/www/vincent.demeester.fr/publish.el b/www/vincent.demeester.fr/publish.el @@ -0,0 +1,138 @@ +;;; publish.el --- Publish www project -*- lexical-binding: t; -*- +;; Author: Vincent Demeester <vincent@sbr.pm> + +;;; Commentary: +;; This script will convert the org-mode files in this directory into +;; html. + +;;; Code: +(require 'package) +(require 'publish-common) + +(setq org-publish-project-alist + `(("posts" + :base-directory "posts" + :base-extension "org" + :recursive t + :publishing-function org-html-publish-to-html + :publishing-directory "./public/posts" + :exclude ,(regexp-opt '("README.org" "draft")) + :auto-sitemap t + :with-footnotes t + :with-toc nil + :with-drawers t + :sitemap-filename "index.org" + :sitemap-title "Posts" + :sitemap-format-entry sbr/org-sitemap-format-entry + :sitemap-style list + :sitemap-sort-files anti-chronologically + :sitemap-function sbr/org-publish-sitemap + :html-head-include-scripts nil + :html-head-include-default-style nil + :html-head ,sbr-website-html-head + :html-preamble sbr-website-html-preamble + :html-postamble ,sbr-website-html-postamble) + ("posts-rss" + :base-directory "posts" + :base-extension "org" + :recursive t + :html-link-home "https://vincent.demeester.fr/" + :rss-link-home "https://vincent.demeester.fr/posts/" + :html-link-use-abs-url t + :rss-extension "xml" + :publishing-directory "./public" + :publishing-function (sbr/org-rss-publish-to-rss) + :section-number nil + :exclude ".*" + :include ("index.org")) + ("articles" + :base-directory "articles" + :base-extension "org" + :recursive t + :publishing-function org-html-publish-to-html + :publishing-directory "./public/articles" + :exclude ,(regexp-opt '("README.org" "draft")) + :auto-sitemap t + :with-footnotes t + :with-toc nil + :with-drawers t + :sitemap-filename "sitemap.org" + :sitemap-title "Articles" + :sitemap-style tree + :sitemap-sort-files anti-chronologically + ;;:sitemap-format-entry sbr/org-sitemap-format-entry + ;;:sitemap-function sbr/org-publish-sitemap + :html-head-include-scripts nil + :html-head-include-default-style nil + :html-head ,sbr-website-html-head + :html-preamble sbr-website-html-preamble + :html-postamble ,sbr-website-html-postamble) + ("articles-assets" + :exclude ,(regexp-opt '("*.org")) + :base-directory "articles" + :base-extension ,site-attachments + :publishing-directory "./public/articles" + :publishing-function org-publish-attachment + :recursive t) + ("about" + :base-directory "about" + :base-extension "org" + :exclude ,(regexp-opt '("README.org" "draft")) + :index-filename "index.org" + :recursive nil + :with-footnotes t + :with-toc nil + :with-drawers t + :publishing-function org-html-publish-to-html + :publishing-directory "./public/about" + :html-head-include-scripts nil + :html-head-include-default-style nil + :html-head ,sbr-website-html-head + :html-preamble sbr-website-html-preamble + :html-postamble ,sbr-website-html-postamble) + ("index" + :base-directory "" + :base-extension "org" + :exclude ,(regexp-opt '("README.org" "draft")) + :index-filename "index.org" + :recursive nil + :with-footnotes t + :with-toc nil + :with-drawers t + :with-title nil + :publishing-function org-html-publish-to-html + :publishing-directory "./public" + :html-head-include-scripts nil + :html-head-include-default-style nil + :html-head ,sbr-website-html-head + :html-preamble sbr-website-html-preamble + :html-postamble ,sbr-website-html-postamble) + ("css" + :base-directory "./css" + :base-extension ,site-attachments + :recursive t + :publishing-directory "./public/css" + :publishing-function org-publish-attachment + :recursive t) + ("images" + :base-directory "./images" + :base-extension ,site-attachments + :publishing-directory "./public/images" + :publishing-function org-publish-attachment + :recursive t) + ("assets" + :base-directory "./assets" + :base-extension ,site-attachments + :publishing-directory "./public/assets" + :publishing-function org-publish-attachment + :recursive t) + ("legacy" + :base-directory "./legacy" + :base-extension ,site-attachments + :publishing-directory "./public/" + :publishing-function org-publish-attachment + :recursive t) + ("all" :components ("posts" "about" "index" "articles" "articles-assets" "css" "images" "assets" "legacy" "posts-rss")))) + +(provide 'publish) +;;; publish.el ends here diff --git a/www/vincent.demeester.fr/tasks.org b/www/vincent.demeester.fr/tasks.org @@ -0,0 +1,84 @@ +#+TITLE: tasks + +* TODO Should the publish/export code be in =src/home= + +A nice side effect would be that it can be used directly from emacs (no need to call =make= +from emacs). Otherwise, [[https://tecosaur.github.io/emacs-config/config.html#nicer-generated-heading][this]] will need to be adapted. + +* TODO nixify publishing + +See https://github.com/etu/etu.github.io/ + +* TODO [[https://abhinavsarkar.net/about/][About Me | abhinavsarkar.net]] + +[2020-05-07 Thu 16:25] + +Good automation setup. + +* TODO Simplify home again +[2020-06-11 Thu 10:49] + +Like [[https://ahuth.github.io/][Andrew Huth]] +- Subtitle is "Hi, …" +- List of /featured/ content (posts) +- Little explanation for articles +- List of some projects (docker, tekton, …) + +* TODO Add support for org-roam in publish + +- [ ] On reference, add the link +- [ ] Add a reference /aside/ or entry for references on the page + +* TODO New blog post capture template/snippet + +- Ask for date +- Ask for title +- Put in the right place + +Use skeleton, … +* TODO Have a "series" page ? +:PROPERTIES: +:CREATED:[2020-06-28 Sun 14:05] +:END: + +For series of article or posts ? + + +From: [[git:~/src/home/README.org::master@{2020-06-28}::1][git:~/src/home/README.org::master@{2020-06-28}::1]] +* DONE Simplify the home +CLOSED: [2020-06-06 Sat 15:50] +:LOGBOOK: +- State "DONE" from "TODO" [2020-06-06 Sat 15:50] +:END: +[2020-04-30 Thu 12:55] + +to sthg like [[https://filippo.io/][Filippo Valsorda]] +also https://duan.ca/articles/ + +Also look at http://git.foldling.org/ and +https://emacsninja.com/posts/farewell-github.html for a ~git.sbr.pm~ +(with gitlab.com/vdemeester header as header) + +https://www.garron.blog/en/ +https://yarmo.eu/ + +Could take https://newcss.net/ and go with it :D [[https://newcss.net/][new.css]] +Base on top of new.css but: +- a bit larger +- remove some nonsense +- support main > header +- add customs (org related) + +* DONE Article listing instead of sitemap +CLOSED: [2020-05-17 Sun 13:48] +:LOGBOOK: +- State "DONE" from "TODO" [2020-05-17 Sun 13:48] +:END: + +Manual listing of articles, linking to the sitemap (like ~configuration~) + +* DONE Add support for small screen like iPhone +CLOSED: [2020-06-06 Sat 15:50] +:LOGBOOK: +- State "DONE" from "TODO" [2020-06-06 Sat 15:50] +:END: diff --git a/www/vincent.demeester.fr/templates/articles.org b/www/vincent.demeester.fr/templates/articles.org @@ -0,0 +1,4 @@ +#+AUTHOR: Vincent Demeester +#+KEYWORDS: article +#+OPTIONS: toc:nil d:(not "LOGBOOK") +#+EXCLUDE_TAGS: noexport diff --git a/www/vincent.demeester.fr/templates/post.org b/www/vincent.demeester.fr/templates/post.org @@ -0,0 +1,4 @@ +#+AUTHOR: Vincent Demeester +#+KEYWORDS: post +#+OPTIONS: toc:nil pri:nil todo:nil d:(not "LOGBOOK") +#+EXCLUDE_TAGS: noexport