From 4e3224c012df9f74f010eb92203520515e8537b9 Mon Sep 17 00:00:00 2001 From: Dererk Date: Wed, 11 Nov 2015 16:34:34 -0300 Subject: Imported Upstream version 7.7.3 --- bower_components/jquery/dist/jquery.min.js | 6 ++++++ bower_components/jquery/dist/jquery.min.map | 1 + bower_components/jquery/src/sizzle/dist/sizzle.min.js | 3 +++ bower_components/jquery/src/sizzle/dist/sizzle.min.map | 1 + 4 files changed, 11 insertions(+) create mode 100644 bower_components/jquery/dist/jquery.min.js create mode 100644 bower_components/jquery/dist/jquery.min.map create mode 100644 bower_components/jquery/src/sizzle/dist/sizzle.min.js create mode 100644 bower_components/jquery/src/sizzle/dist/sizzle.min.map (limited to 'bower_components/jquery') diff --git a/bower_components/jquery/dist/jquery.min.js b/bower_components/jquery/dist/jquery.min.js new file mode 100644 index 0000000..f364443 --- /dev/null +++ b/bower_components/jquery/dist/jquery.min.js @@ -0,0 +1,6 @@ +/*! jQuery v1.11.3 | (c) 2005, 2015 jQuery Foundation, Inc. | jquery.org/license */ +!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={},l="1.11.3",m=function(a,b){return new m.fn.init(a,b)},n=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,o=/^-ms-/,p=/-([\da-z])/gi,q=function(a,b){return b.toUpperCase()};m.fn=m.prototype={jquery:l,constructor:m,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=m.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return m.each(this,a,b)},map:function(a){return this.pushStack(m.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},m.extend=m.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||m.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(m.isPlainObject(c)||(b=m.isArray(c)))?(b?(b=!1,f=a&&m.isArray(a)?a:[]):f=a&&m.isPlainObject(a)?a:{},g[d]=m.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},m.extend({expando:"jQuery"+(l+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===m.type(a)},isArray:Array.isArray||function(a){return"array"===m.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return!m.isArray(a)&&a-parseFloat(a)+1>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==m.type(a)||a.nodeType||m.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(k.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&m.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(o,"ms-").replace(p,q)},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=r(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?"":(a+"").replace(n,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(r(Object(a))?m.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[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=r(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&&(f=a[b],b=a,a=f),m.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||m.guid++,e):void 0},now:function(){return+new Date},support:k}),m.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function r(a){var b="length"in a&&a.length,c=m.type(a);return"function"===c||m.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var s=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,aa=/[+~]/,ba=/'|\\/g,ca=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),da=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)},ea=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fa){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(ba,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+ra(o[l]);w=aa.test(a)&&pa(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return ia(function(b){return b=+b,ia(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 pa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener("unload",ea,!1):e.attachEvent&&e.attachEvent("onunload",ea)),p=!f(g),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(g.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(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 p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.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},B=b?function(a,b){if(a===b)return l=!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===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?la(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.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=ga.selectors={cacheLength:50,createPseudo:ia,match:X,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(ca,da),a[3]=(a[3]||a[4]||a[5]||"").replace(ca,da),"~="===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]||ga.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]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(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(ca,da).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.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.replace(Q," ")+" ").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(),s=!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&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&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]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)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&&(s&&((l[u]||(l[u]={}))[a]=[w,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()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ia(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),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(ca,da),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return W.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(ca,da).toLowerCase(),function(b){var c;do if(c=p?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===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.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 Z.test(a.nodeName)},input:function(a){return Y.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:oa(function(){return[0]}),last:oa(function(a,b){return[b-1]}),eq:oa(function(a,b,c){return[0>c?c+b:c]}),even:oa(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:oa(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:oa(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:oa(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function sa(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;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=[w,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[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function ta(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 ua(a,b,c){for(var d=0,e=b.length;e>d;d++)ga(a,b[d],c);return c}function va(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 wa(a,b,c,d,e,f){return d&&!d[u]&&(d=wa(d)),e&&!e[u]&&(e=wa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ua(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:va(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=va(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?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=va(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sa(function(a){return a===b},h,!0),l=sa(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sa(ta(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wa(i>1&&ta(m),i>1&&ra(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xa(a.slice(i,e)),f>e&&xa(a=a.slice(e)),f>e&&ra(a))}m.push(c)}return ta(m)}function ya(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=va(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&ga.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,ya(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ca,da),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ca,da),aa.test(j[0].type)&&pa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&ra(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,aa.test(a)&&pa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);m.find=s,m.expr=s.selectors,m.expr[":"]=m.expr.pseudos,m.unique=s.uniqueSort,m.text=s.getText,m.isXMLDoc=s.isXML,m.contains=s.contains;var t=m.expr.match.needsContext,u=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,v=/^.[^:#\[\.,]*$/;function w(a,b,c){if(m.isFunction(b))return m.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return m.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(v.test(b))return m.filter(b,a,c);b=m.filter(b,a)}return m.grep(a,function(a){return m.inArray(a,b)>=0!==c})}m.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?m.find.matchesSelector(d,a)?[d]:[]:m.find.matches(a,m.grep(b,function(a){return 1===a.nodeType}))},m.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(m(a).filter(function(){for(b=0;e>b;b++)if(m.contains(d[b],this))return!0}));for(b=0;e>b;b++)m.find(a,d[b],c);return c=this.pushStack(e>1?m.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(w(this,a||[],!1))},not:function(a){return this.pushStack(w(this,a||[],!0))},is:function(a){return!!w(this,"string"==typeof a&&t.test(a)?m(a):a||[],!1).length}});var x,y=a.document,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=m.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||x).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof m?b[0]:b,m.merge(this,m.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:y,!0)),u.test(c[1])&&m.isPlainObject(b))for(c in b)m.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=y.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return x.find(a);this.length=1,this[0]=d}return this.context=y,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):m.isFunction(a)?"undefined"!=typeof x.ready?x.ready(a):a(m):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),m.makeArray(a,this))};A.prototype=m.fn,x=m(y);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};m.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!m(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),m.fn.extend({has:function(a){var b,c=m(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(m.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=t.test(a)||"string"!=typeof a?m(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&&m.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?m.unique(f):f)},index:function(a){return a?"string"==typeof a?m.inArray(this[0],m(a)):m.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(m.unique(m.merge(this.get(),m(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}m.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return m.dir(a,"parentNode")},parentsUntil:function(a,b,c){return m.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return m.dir(a,"nextSibling")},prevAll:function(a){return m.dir(a,"previousSibling")},nextUntil:function(a,b,c){return m.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return m.dir(a,"previousSibling",c)},siblings:function(a){return m.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return m.sibling(a.firstChild)},contents:function(a){return m.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:m.merge([],a.childNodes)}},function(a,b){m.fn[a]=function(c,d){var e=m.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=m.filter(d,e)),this.length>1&&(C[a]||(e=m.unique(e)),B.test(a)&&(e=e.reverse())),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return m.each(a.match(E)||[],function(a,c){b[c]=!0}),b}m.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):m.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){m.each(b,function(b,c){var d=m.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&m.each(arguments,function(a,c){var d;while((d=m.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?m.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},m.extend({Deferred:function(a){var b=[["resolve","done",m.Callbacks("once memory"),"resolved"],["reject","fail",m.Callbacks("once memory"),"rejected"],["notify","progress",m.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return m.Deferred(function(c){m.each(b,function(b,f){var g=m.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&m.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?m.extend(a,d):d}},e={};return d.pipe=d.then,m.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&&m.isFunction(a.promise)?e:0,g=1===f?a:m.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]&&m.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;m.fn.ready=function(a){return m.ready.promise().done(a),this},m.extend({isReady:!1,readyWait:1,holdReady:function(a){a?m.readyWait++:m.ready(!0)},ready:function(a){if(a===!0?!--m.readyWait:!m.isReady){if(!y.body)return setTimeout(m.ready);m.isReady=!0,a!==!0&&--m.readyWait>0||(H.resolveWith(y,[m]),m.fn.triggerHandler&&(m(y).triggerHandler("ready"),m(y).off("ready")))}}});function I(){y.addEventListener?(y.removeEventListener("DOMContentLoaded",J,!1),a.removeEventListener("load",J,!1)):(y.detachEvent("onreadystatechange",J),a.detachEvent("onload",J))}function J(){(y.addEventListener||"load"===event.type||"complete"===y.readyState)&&(I(),m.ready())}m.ready.promise=function(b){if(!H)if(H=m.Deferred(),"complete"===y.readyState)setTimeout(m.ready);else if(y.addEventListener)y.addEventListener("DOMContentLoaded",J,!1),a.addEventListener("load",J,!1);else{y.attachEvent("onreadystatechange",J),a.attachEvent("onload",J);var c=!1;try{c=null==a.frameElement&&y.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!m.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}I(),m.ready()}}()}return H.promise(b)};var K="undefined",L;for(L in m(k))break;k.ownLast="0"!==L,k.inlineBlockNeedsLayout=!1,m(function(){var a,b,c,d;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",k.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(d))}),function(){var a=y.createElement("div");if(null==k.deleteExpando){k.deleteExpando=!0;try{delete a.test}catch(b){k.deleteExpando=!1}}a=null}(),m.acceptData=function(a){var b=m.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var M=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,N=/([A-Z])/g;function O(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(N,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:M.test(c)?m.parseJSON(c):c}catch(e){}m.data(a,b,c)}else c=void 0}return c}function P(a){var b;for(b in a)if(("data"!==b||!m.isEmptyObject(a[b]))&&"toJSON"!==b)return!1; + +return!0}function Q(a,b,d,e){if(m.acceptData(a)){var f,g,h=m.expando,i=a.nodeType,j=i?m.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||m.guid++:h),j[k]||(j[k]=i?{}:{toJSON:m.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=m.extend(j[k],b):j[k].data=m.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[m.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[m.camelCase(b)])):f=g,f}}function R(a,b,c){if(m.acceptData(a)){var d,e,f=a.nodeType,g=f?m.cache:a,h=f?a[m.expando]:m.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){m.isArray(b)?b=b.concat(m.map(b,m.camelCase)):b in d?b=[b]:(b=m.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!P(d):!m.isEmptyObject(d))return}(c||(delete g[h].data,P(g[h])))&&(f?m.cleanData([a],!0):k.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}m.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?m.cache[a[m.expando]]:a[m.expando],!!a&&!P(a)},data:function(a,b,c){return Q(a,b,c)},removeData:function(a,b){return R(a,b)},_data:function(a,b,c){return Q(a,b,c,!0)},_removeData:function(a,b){return R(a,b,!0)}}),m.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.data(f),1===f.nodeType&&!m._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=m.camelCase(d.slice(5)),O(f,d,e[d])));m._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){m.data(this,a)}):arguments.length>1?this.each(function(){m.data(this,a,b)}):f?O(f,a,m.data(f,a)):void 0},removeData:function(a){return this.each(function(){m.removeData(this,a)})}}),m.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=m._data(a,b),c&&(!d||m.isArray(c)?d=m._data(a,b,m.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=m.queue(a,b),d=c.length,e=c.shift(),f=m._queueHooks(a,b),g=function(){m.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 m._data(a,c)||m._data(a,c,{empty:m.Callbacks("once memory").add(function(){m._removeData(a,b+"queue"),m._removeData(a,c)})})}}),m.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;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},W=/^(?:checkbox|radio)$/i;!function(){var a=y.createElement("input"),b=y.createElement("div"),c=y.createDocumentFragment();if(b.innerHTML="
a",k.leadingWhitespace=3===b.firstChild.nodeType,k.tbody=!b.getElementsByTagName("tbody").length,k.htmlSerialize=!!b.getElementsByTagName("link").length,k.html5Clone="<:nav>"!==y.createElement("nav").cloneNode(!0).outerHTML,a.type="checkbox",a.checked=!0,c.appendChild(a),k.appendChecked=a.checked,b.innerHTML="",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,c.appendChild(b),b.innerHTML="",k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,k.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){k.noCloneEvent=!1}),b.cloneNode(!0).click()),null==k.deleteExpando){k.deleteExpando=!0;try{delete b.test}catch(d){k.deleteExpando=!1}}}(),function(){var b,c,d=y.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(k[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),k[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var X=/^(?:input|select|textarea)$/i,Y=/^key/,Z=/^(?:mouse|pointer|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=/^([^.]*)(?:\.(.+)|)$/;function aa(){return!0}function ba(){return!1}function ca(){try{return y.activeElement}catch(a){}}m.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=m.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof m===K||a&&m.event.triggered===a.type?void 0:m.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(E)||[""],h=b.length;while(h--)f=_.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=m.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=m.event.special[o]||{},l=m.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&m.expr.match.needsContext.test(e),namespace:p.join(".")},i),(n=g[o])||(n=g[o]=[],n.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?n.splice(n.delegateCount++,0,l):n.push(l),m.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m.hasData(a)&&m._data(a);if(r&&(k=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=_.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=m.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,n=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=n.length;while(f--)g=n[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(n.splice(f,1),g.selector&&n.delegateCount--,l.remove&&l.remove.call(a,g));i&&!n.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||m.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)m.event.remove(a,o+b[j],c,d,!0);m.isEmptyObject(k)&&(delete r.handle,m._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,n,o=[d||y],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||y,3!==d.nodeType&&8!==d.nodeType&&!$.test(p+m.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[m.expando]?b:new m.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:m.makeArray(c,[b]),k=m.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!m.isWindow(d)){for(i=k.delegateType||p,$.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||y)&&o.push(l.defaultView||l.parentWindow||a)}n=0;while((h=o[n++])&&!b.isPropagationStopped())b.type=n>1?i:k.bindType||p,f=(m._data(h,"events")||{})[b.type]&&m._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&m.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&m.acceptData(d)&&g&&d[p]&&!m.isWindow(d)){l=d[g],l&&(d[g]=null),m.event.triggered=p;try{d[p]()}catch(r){}m.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=m.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(m._data(this,"events")||{})[a.type]||[],k=m.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=m.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((m.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!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(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?m(c,this).index(i)>=0:m.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h]","i"),ha=/^\s+/,ia=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,ja=/<([\w:]+)/,ka=/\s*$/g,ra={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:k.htmlSerialize?[0,"",""]:[1,"X
","
"]},sa=da(y),ta=sa.appendChild(y.createElement("div"));ra.optgroup=ra.option,ra.tbody=ra.tfoot=ra.colgroup=ra.caption=ra.thead,ra.th=ra.td;function ua(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==K?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==K?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||m.nodeName(d,b)?f.push(d):m.merge(f,ua(d,b));return void 0===b||b&&m.nodeName(a,b)?m.merge([a],f):f}function va(a){W.test(a.type)&&(a.defaultChecked=a.checked)}function wa(a,b){return m.nodeName(a,"table")&&m.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function xa(a){return a.type=(null!==m.find.attr(a,"type"))+"/"+a.type,a}function ya(a){var b=pa.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function za(a,b){for(var c,d=0;null!=(c=a[d]);d++)m._data(c,"globalEval",!b||m._data(b[d],"globalEval"))}function Aa(a,b){if(1===b.nodeType&&m.hasData(a)){var c,d,e,f=m._data(a),g=m._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)m.event.add(b,c,h[c][d])}g.data&&(g.data=m.extend({},g.data))}}function Ba(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!k.noCloneEvent&&b[m.expando]){e=m._data(b);for(d in e.events)m.removeEvent(b,d,e.handle);b.removeAttribute(m.expando)}"script"===c&&b.text!==a.text?(xa(b).text=a.text,ya(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),k.html5Clone&&a.innerHTML&&!m.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&W.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}m.extend({clone:function(a,b,c){var d,e,f,g,h,i=m.contains(a.ownerDocument,a);if(k.html5Clone||m.isXMLDoc(a)||!ga.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(ta.innerHTML=a.outerHTML,ta.removeChild(f=ta.firstChild)),!(k.noCloneEvent&&k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||m.isXMLDoc(a)))for(d=ua(f),h=ua(a),g=0;null!=(e=h[g]);++g)d[g]&&Ba(e,d[g]);if(b)if(c)for(h=h||ua(a),d=d||ua(f),g=0;null!=(e=h[g]);g++)Aa(e,d[g]);else Aa(a,f);return d=ua(f,"script"),d.length>0&&za(d,!i&&ua(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,l,n=a.length,o=da(b),p=[],q=0;n>q;q++)if(f=a[q],f||0===f)if("object"===m.type(f))m.merge(p,f.nodeType?[f]:f);else if(la.test(f)){h=h||o.appendChild(b.createElement("div")),i=(ja.exec(f)||["",""])[1].toLowerCase(),l=ra[i]||ra._default,h.innerHTML=l[1]+f.replace(ia,"<$1>")+l[2],e=l[0];while(e--)h=h.lastChild;if(!k.leadingWhitespace&&ha.test(f)&&p.push(b.createTextNode(ha.exec(f)[0])),!k.tbody){f="table"!==i||ka.test(f)?""!==l[1]||ka.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)m.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}m.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),k.appendChecked||m.grep(ua(p,"input"),va),q=0;while(f=p[q++])if((!d||-1===m.inArray(f,d))&&(g=m.contains(f.ownerDocument,f),h=ua(o.appendChild(f),"script"),g&&za(h),c)){e=0;while(f=h[e++])oa.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=m.expando,j=m.cache,l=k.deleteExpando,n=m.event.special;null!=(d=a[h]);h++)if((b||m.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)n[e]?m.event.remove(d,e):m.removeEvent(d,e,g.handle);j[f]&&(delete j[f],l?delete d[i]:typeof d.removeAttribute!==K?d.removeAttribute(i):d[i]=null,c.push(f))}}}),m.fn.extend({text:function(a){return V(this,function(a){return void 0===a?m.text(this):this.empty().append((this[0]&&this[0].ownerDocument||y).createTextNode(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=wa(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=wa(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?m.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||m.cleanData(ua(c)),c.parentNode&&(b&&m.contains(c.ownerDocument,c)&&za(ua(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&m.cleanData(ua(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&m.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return m.clone(this,a,b)})},html:function(a){return V(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(fa,""):void 0;if(!("string"!=typeof a||ma.test(a)||!k.htmlSerialize&&ga.test(a)||!k.leadingWhitespace&&ha.test(a)||ra[(ja.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(ia,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(m.cleanData(ua(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,m.cleanData(ua(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,l=this.length,n=this,o=l-1,p=a[0],q=m.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&na.test(p))return this.each(function(c){var d=n.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(i=m.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=m.map(ua(i,"script"),xa),f=g.length;l>j;j++)d=i,j!==o&&(d=m.clone(d,!0,!0),f&&m.merge(g,ua(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,m.map(g,ya),j=0;f>j;j++)d=g[j],oa.test(d.type||"")&&!m._data(d,"globalEval")&&m.contains(h,d)&&(d.src?m._evalUrl&&m._evalUrl(d.src):m.globalEval((d.text||d.textContent||d.innerHTML||"").replace(qa,"")));i=c=null}return this}}),m.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){m.fn[a]=function(a){for(var c,d=0,e=[],g=m(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),m(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Ca,Da={};function Ea(b,c){var d,e=m(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:m.css(e[0],"display");return e.detach(),f}function Fa(a){var b=y,c=Da[a];return c||(c=Ea(a,b),"none"!==c&&c||(Ca=(Ca||m("""") +src="https://w.soundcloud.com/player/?url=http://api.soundcloud.com/{preslug}/{sid}"> + +""" class SoundCloud(Directive): @@ -44,6 +46,7 @@ class SoundCloud(Directive): option_spec = { 'width': directives.positive_int, 'height': directives.positive_int, + "align": _align_choice } preslug = "tracks" @@ -57,6 +60,10 @@ class SoundCloud(Directive): 'preslug': self.preslug, } options.update(self.options) + if self.options.get('align') in _align_options_base: + options['align'] = ' align-' + self.options['align'] + else: + options['align'] = '' return [nodes.raw('', CODE.format(**options), format='html')] def check_content(self): diff --git a/nikola/plugins/compile/rest/thumbnail.py b/nikola/plugins/compile/rest/thumbnail.py index c24134a..37e0973 100644 --- a/nikola/plugins/compile/rest/thumbnail.py +++ b/nikola/plugins/compile/rest/thumbnail.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2014-2015 Pelle Nilsson and others. +# Copyright © 2014-2016 Pelle Nilsson and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/compile/rest/vimeo.py b/nikola/plugins/compile/rest/vimeo.py index 29ce5c1..f1ac6c3 100644 --- a/nikola/plugins/compile/rest/vimeo.py +++ b/nikola/plugins/compile/rest/vimeo.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# Copyright © 2012-2016 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -28,6 +28,7 @@ from docutils import nodes from docutils.parsers.rst import Directive, directives +from nikola.plugins.compile.rest import _align_choice, _align_options_base import requests import json @@ -48,10 +49,12 @@ class Plugin(RestExtension): return super(Plugin, self).set_site(site) -CODE = """ + """ VIDEO_DEFAULT_HEIGHT = 500 @@ -73,6 +76,7 @@ class Vimeo(Directive): option_spec = { "width": directives.positive_int, "height": directives.positive_int, + "align": _align_choice } # set to False for not querying the vimeo api for size @@ -92,6 +96,10 @@ class Vimeo(Directive): return err self.set_video_size() options.update(self.options) + if self.options.get('align') in _align_options_base: + options['align'] = ' align-' + self.options['align'] + else: + options['align'] = '' return [nodes.raw('', CODE.format(**options), format='html')] def check_modules(self): @@ -107,7 +115,7 @@ class Vimeo(Directive): if json: # we can attempt to retrieve video attributes from vimeo try: - url = ('//vimeo.com/api/v2/video/{0}' + url = ('https://vimeo.com/api/v2/video/{0}' '.json'.format(self.arguments[0])) data = requests.get(url).text video_attributes = json.loads(data)[0] diff --git a/nikola/plugins/compile/rest/youtube.py b/nikola/plugins/compile/rest/youtube.py index b3b84b0..b3dde62 100644 --- a/nikola/plugins/compile/rest/youtube.py +++ b/nikola/plugins/compile/rest/youtube.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# Copyright © 2012-2016 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -28,7 +28,7 @@ from docutils import nodes from docutils.parsers.rst import Directive, directives - +from nikola.plugins.compile.rest import _align_choice, _align_options_base from nikola.plugin_categories import RestExtension @@ -46,10 +46,11 @@ class Plugin(RestExtension): CODE = """\ -""" +
+ +
""" class Youtube(Directive): @@ -67,6 +68,7 @@ class Youtube(Directive): option_spec = { "width": directives.positive_int, "height": directives.positive_int, + "align": _align_choice } def run(self): @@ -78,6 +80,10 @@ class Youtube(Directive): 'height': 344, } options.update(self.options) + if self.options.get('align') in _align_options_base: + options['align'] = ' align-' + self.options['align'] + else: + options['align'] = '' return [nodes.raw('', CODE.format(**options), format='html')] def check_content(self): diff --git a/nikola/plugins/misc/__init__.py b/nikola/plugins/misc/__init__.py index c0d8961..518fac1 100644 --- a/nikola/plugins/misc/__init__.py +++ b/nikola/plugins/misc/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# Copyright © 2012-2016 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/misc/scan_posts.py b/nikola/plugins/misc/scan_posts.py index 9db4533..f584a05 100644 --- a/nikola/plugins/misc/scan_posts.py +++ b/nikola/plugins/misc/scan_posts.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# Copyright © 2012-2016 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -35,6 +35,8 @@ from nikola.plugin_categories import PostScanner from nikola import utils from nikola.post import Post +LOGGER = utils.get_logger('scan_posts', utils.STDERR_HANDLER) + class ScanPosts(PostScanner): """Scan posts in the site.""" @@ -87,15 +89,19 @@ class ScanPosts(PostScanner): continue else: seen.add(base_path) - post = Post( - base_path, - self.site.config, - dest_dir, - use_in_feeds, - self.site.MESSAGES, - template_name, - self.site.get_compiler(base_path) - ) - timeline.append(post) + try: + post = Post( + base_path, + self.site.config, + dest_dir, + use_in_feeds, + self.site.MESSAGES, + template_name, + self.site.get_compiler(base_path) + ) + timeline.append(post) + except Exception as err: + LOGGER.error('Error reading post {}'.format(base_path)) + raise err return timeline diff --git a/nikola/plugins/shortcode/gist.plugin b/nikola/plugins/shortcode/gist.plugin new file mode 100644 index 0000000..cd19a72 --- /dev/null +++ b/nikola/plugins/shortcode/gist.plugin @@ -0,0 +1,13 @@ +[Core] +name = gist +module = gist + +[Nikola] +plugincategory = Shortcode + +[Documentation] +author = Roberto Alsina +version = 0.1 +website = https://getnikola.com/ +description = Gist shortcode + diff --git a/nikola/plugins/shortcode/gist.py b/nikola/plugins/shortcode/gist.py new file mode 100644 index 0000000..64fd0d9 --- /dev/null +++ b/nikola/plugins/shortcode/gist.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +# This file is public domain according to its author, Brian Hsu + +"""Gist directive for reStructuredText.""" + +import requests + +from nikola.plugin_categories import ShortcodePlugin + + +class Plugin(ShortcodePlugin): + """Plugin for gist directive.""" + + name = "gist" + + def set_site(self, site): + """Set Nikola site.""" + self.site = site + site.register_shortcode('gist', self.handler) + return super(Plugin, self).set_site(site) + + def get_raw_gist_with_filename(self, gistID, filename): + """Get raw gist text for a filename.""" + url = '/'.join(("https://gist.github.com/raw", gistID, filename)) + return requests.get(url).text + + def get_raw_gist(self, gistID): + """Get raw gist text.""" + url = "https://gist.github.com/raw/{0}".format(gistID) + try: + return requests.get(url).text + except requests.exceptions.RequestException: + raise self.error('Cannot get gist for url={0}'.format(url)) + + def handler(self, gistID, filename=None, site=None, data=None, lang=None, post=None): + """Create HTML for gist.""" + if 'https://' in gistID: + gistID = gistID.split('/')[-1].strip() + else: + gistID = gistID.strip() + embedHTML = "" + rawGist = "" + + if filename is not None: + rawGist = (self.get_raw_gist_with_filename(gistID, filename)) + embedHTML = ('').format(gistID, filename) + else: + rawGist = (self.get_raw_gist(gistID)) + embedHTML = ('').format(gistID) + + output = '''{} + '''.format(embedHTML, rawGist) + + return output, [] diff --git a/nikola/plugins/task/__init__.py b/nikola/plugins/task/__init__.py index fd9a48f..4eeae62 100644 --- a/nikola/plugins/task/__init__.py +++ b/nikola/plugins/task/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# Copyright © 2012-2016 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/task/archive.py b/nikola/plugins/task/archive.py index 3cdd33b..303d349 100644 --- a/nikola/plugins/task/archive.py +++ b/nikola/plugins/task/archive.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# Copyright © 2012-2016 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/task/authors.py b/nikola/plugins/task/authors.py index 081d21d..ec61800 100644 --- a/nikola/plugins/task/authors.py +++ b/nikola/plugins/task/authors.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2015 Juanjo Conti. +# Copyright © 2015-2016 Juanjo Conti and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -35,6 +35,8 @@ except ImportError: from urllib.parse import urljoin # NOQA from collections import defaultdict +from blinker import signal + from nikola.plugin_categories import Task from nikola import utils @@ -47,13 +49,20 @@ class RenderAuthors(Task): def set_site(self, site): """Set Nikola site.""" + self.generate_author_pages = False if site.config["ENABLE_AUTHOR_PAGES"]: site.register_path_handler('author_index', self.author_index_path) site.register_path_handler('author', self.author_path) site.register_path_handler('author_atom', self.author_atom_path) site.register_path_handler('author_rss', self.author_rss_path) + signal('scanned').connect(self.posts_scanned) return super(RenderAuthors, self).set_site(site) + def posts_scanned(self, event): + """Called after posts are scanned via signal.""" + self.generate_author_pages = self.site.config["ENABLE_AUTHOR_PAGES"] and len(self._posts_per_author()) > 1 + self.site.GLOBAL_CONTEXT["author_pages_generated"] = self.generate_author_pages + def gen_tasks(self): """Render the author pages and feeds.""" kw = { @@ -78,12 +87,10 @@ class RenderAuthors(Task): "index_file": self.site.config['INDEX_FILE'], } - yield self.group_task() self.site.scan_posts() + yield self.group_task() - generate_author_pages = self.site.config["ENABLE_AUTHOR_PAGES"] and len(self._posts_per_author()) > 1 - self.site.GLOBAL_CONTEXT["author_pages_generated"] = generate_author_pages - if generate_author_pages: + if self.generate_author_pages: yield self.list_authors_page(kw) if not self._posts_per_author(): # this may be self.site.posts_per_author @@ -244,10 +251,13 @@ class RenderAuthors(Task): } return utils.apply_filters(task, kw['filters']) - def slugify_author_name(self, name): + def slugify_author_name(self, name, lang=None): """Slugify an author name.""" + if lang is None: # TODO: remove in v8 + utils.LOGGER.warn("RenderAuthors.slugify_author_name() called without language!") + lang = '' if self.site.config['SLUG_AUTHOR_PATH']: - name = utils.slugify(name) + name = utils.slugify(name, lang) return name def author_index_path(self, name, lang): @@ -272,13 +282,13 @@ class RenderAuthors(Task): return [_f for _f in [ self.site.config['TRANSLATIONS'][lang], self.site.config['AUTHOR_PATH'], - self.slugify_author_name(name), + self.slugify_author_name(name, lang), self.site.config['INDEX_FILE']] if _f] else: return [_f for _f in [ self.site.config['TRANSLATIONS'][lang], self.site.config['AUTHOR_PATH'], - self.slugify_author_name(name) + ".html"] if _f] + self.slugify_author_name(name, lang) + ".html"] if _f] def author_atom_path(self, name, lang): """Link to an author's Atom feed. @@ -288,7 +298,7 @@ class RenderAuthors(Task): link://author_atom/joe => /authors/joe.atom """ return [_f for _f in [self.site.config['TRANSLATIONS'][lang], - self.site.config['AUTHOR_PATH'], self.slugify_author_name(name) + ".atom"] if + self.site.config['AUTHOR_PATH'], self.slugify_author_name(name, lang) + ".atom"] if _f] def author_rss_path(self, name, lang): @@ -299,7 +309,7 @@ class RenderAuthors(Task): link://author_rss/joe => /authors/joe.rss """ return [_f for _f in [self.site.config['TRANSLATIONS'][lang], - self.site.config['AUTHOR_PATH'], self.slugify_author_name(name) + ".xml"] if + self.site.config['AUTHOR_PATH'], self.slugify_author_name(name, lang) + ".xml"] if _f] def _add_extension(self, path, extension): diff --git a/nikola/plugins/task/bundles.py b/nikola/plugins/task/bundles.py index e709133..b33d8e0 100644 --- a/nikola/plugins/task/bundles.py +++ b/nikola/plugins/task/bundles.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# Copyright © 2012-2016 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/task/copy_assets.py b/nikola/plugins/task/copy_assets.py index 2cab71a..4ed7414 100644 --- a/nikola/plugins/task/copy_assets.py +++ b/nikola/plugins/task/copy_assets.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# Copyright © 2012-2016 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/task/copy_files.py b/nikola/plugins/task/copy_files.py index 0488011..6f6cfb8 100644 --- a/nikola/plugins/task/copy_files.py +++ b/nikola/plugins/task/copy_files.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# Copyright © 2012-2016 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/task/galleries.py b/nikola/plugins/task/galleries.py index d3f1db7..edfd33d 100644 --- a/nikola/plugins/task/galleries.py +++ b/nikola/plugins/task/galleries.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# Copyright © 2012-2016 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -33,7 +33,6 @@ import io import json import mimetypes import os -import sys try: from urlparse import urljoin except ImportError: @@ -86,6 +85,8 @@ class Galleries(Task, ImageProcessor): 'tzinfo': site.tzinfo, 'comments_in_galleries': site.config['COMMENTS_IN_GALLERIES'], 'generate_rss': site.config['GENERATE_RSS'], + 'preserve_exif_data': site.config['PRESERVE_EXIF_DATA'], + 'exif_whitelist': site.config['EXIF_WHITELIST'], } # Verify that no folder in GALLERY_FOLDERS appears twice @@ -93,8 +94,8 @@ class Galleries(Task, ImageProcessor): for source, dest in self.kw['gallery_folders'].items(): if source in appearing_paths or dest in appearing_paths: problem = source if source in appearing_paths else dest - utils.LOGGER.error("The gallery input or output folder '{0}' appears in more than one entry in GALLERY_FOLDERS, exiting.".format(problem)) - sys.exit(1) + utils.LOGGER.error("The gallery input or output folder '{0}' appears in more than one entry in GALLERY_FOLDERS, ignoring.".format(problem)) + continue appearing_paths.add(source) appearing_paths.add(dest) @@ -115,10 +116,11 @@ class Galleries(Task, ImageProcessor): if len(candidates) == 1: return candidates[0] self.logger.error("Gallery name '{0}' is not unique! Possible output paths: {1}".format(name, candidates)) + raise RuntimeError("Gallery name '{0}' is not unique! Possible output paths: {1}".format(name, candidates)) else: self.logger.error("Unknown gallery '{0}'!".format(name)) self.logger.info("Known galleries: " + str(list(self.proper_gallery_links.keys()))) - sys.exit(1) + raise RuntimeError("Unknown gallery '{0}'!".format(name)) def gallery_path(self, name, lang): """Link to an image gallery's path. @@ -173,6 +175,7 @@ class Galleries(Task, ImageProcessor): for k, v in self.site.GLOBAL_CONTEXT['template_hooks'].items(): self.kw['||template_hooks|{0}||'.format(k)] = v._items + self.site.scan_posts() yield self.group_task() template_name = "gallery.tmpl" @@ -194,13 +197,6 @@ class Galleries(Task, ImageProcessor): # Create image list, filter exclusions image_list = self.get_image_list(gallery) - # Sort as needed - # Sort by date - if self.kw['sort_by_date']: - image_list.sort(key=lambda a: self.image_date(a)) - else: # Sort by name - image_list.sort() - # Create thumbnails and large images in destination for image in image_list: for task in self.create_target_images(image, input_folder): @@ -211,8 +207,6 @@ class Galleries(Task, ImageProcessor): for task in self.remove_excluded_image(image, input_folder): yield task - crumbs = utils.get_crumbs(gallery, index_folder=self) - for lang in self.kw['translations']: # save navigation links as dependencies self.kw['navigation_links|{0}'.format(lang)] = self.kw['global_context']['navigation_links'](lang) @@ -242,7 +236,7 @@ class Galleries(Task, ImageProcessor): img_titles = [] for fn in image_name_list: name_without_ext = os.path.splitext(os.path.basename(fn))[0] - img_titles.append(utils.unslugify(name_without_ext)) + img_titles.append(utils.unslugify(name_without_ext, lang)) else: img_titles = [''] * len(image_name_list) @@ -266,7 +260,7 @@ class Galleries(Task, ImageProcessor): context["folders"] = natsort.natsorted( folders, alg=natsort.ns.F | natsort.ns.IC) - context["crumbs"] = crumbs + context["crumbs"] = utils.get_crumbs(gallery, index_folder=self, lang=lang) context["permalink"] = self.site.link("gallery", gallery, lang) context["enable_comments"] = self.kw['comments_in_galleries'] context["thumbnail_size"] = self.kw["thumbnail_size"] @@ -423,6 +417,8 @@ class Galleries(Task, ImageProcessor): # may break) if post.title == 'index': post.title = os.path.split(gallery)[1] + # Register the post (via #2417) + self.site.post_per_input_file[index_path] = post else: post = None return post @@ -482,7 +478,8 @@ class Galleries(Task, ImageProcessor): 'targets': [thumb_path], 'actions': [ (self.resize_image, - (img, thumb_path, self.kw['thumbnail_size'])) + (img, thumb_path, self.kw['thumbnail_size'], False, self.kw['preserve_exif_data'], + self.kw['exif_whitelist'])) ], 'clean': True, 'uptodate': [utils.config_changed({ @@ -497,7 +494,8 @@ class Galleries(Task, ImageProcessor): 'targets': [orig_dest_path], 'actions': [ (self.resize_image, - (img, orig_dest_path, self.kw['max_image_size'])) + (img, orig_dest_path, self.kw['max_image_size'], False, self.kw['preserve_exif_data'], + self.kw['exif_whitelist'])) ], 'clean': True, 'uptodate': [utils.config_changed({ @@ -558,6 +556,18 @@ class Galleries(Task, ImageProcessor): url = '/'.join(os.path.relpath(p, os.path.dirname(output_name) + os.sep).split(os.sep)) return url + all_data = list(zip(img_list, thumbs, img_titles)) + + if self.kw['sort_by_date']: + all_data.sort(key=lambda a: self.image_date(a[0])) + else: # Sort by name + all_data.sort(key=lambda a: a[0]) + + if all_data: + img_list, thumbs, img_titles = zip(*all_data) + else: + img_list, thumbs, img_titles = [], [], [] + photo_array = [] for img, thumb, title in zip(img_list, thumbs, img_titles): w, h = _image_size_cache.get(thumb, (None, None)) @@ -591,6 +601,18 @@ class Galleries(Task, ImageProcessor): def make_url(url): return urljoin(self.site.config['BASE_URL'], url.lstrip('/')) + all_data = list(zip(img_list, dest_img_list, img_titles)) + + if self.kw['sort_by_date']: + all_data.sort(key=lambda a: self.image_date(a[0])) + else: # Sort by name + all_data.sort(key=lambda a: a[0]) + + if all_data: + img_list, dest_img_list, img_titles = zip(*all_data) + else: + img_list, dest_img_list, img_titles = [], [], [] + items = [] for img, srcimg, title in list(zip(dest_img_list, img_list, img_titles))[:self.kw["feed_length"]]: img_size = os.stat( diff --git a/nikola/plugins/task/gzip.py b/nikola/plugins/task/gzip.py index aaa213d..79a11dc 100644 --- a/nikola/plugins/task/gzip.py +++ b/nikola/plugins/task/gzip.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# Copyright © 2012-2016 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/task/indexes.py b/nikola/plugins/task/indexes.py index 2ab97fa..8ecd1de 100644 --- a/nikola/plugins/task/indexes.py +++ b/nikola/plugins/task/indexes.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# Copyright © 2012-2016 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -36,6 +36,7 @@ except ImportError: from nikola.plugin_categories import Task from nikola import utils +from nikola.nikola import _enclosure class Indexes(Task): @@ -51,6 +52,7 @@ class Indexes(Task): site.register_path_handler('index_atom', self.index_atom_path) site.register_path_handler('section_index', self.index_section_path) site.register_path_handler('section_index_atom', self.index_section_atom_path) + site.register_path_handler('section_index_rss', self.index_section_rss_path) return super(Indexes, self).set_site(site) def _get_filtered_posts(self, lang, show_untranslated_posts): @@ -77,6 +79,10 @@ class Indexes(Task): "translations": self.site.config['TRANSLATIONS'], "messages": self.site.MESSAGES, "output_folder": self.site.config['OUTPUT_FOLDER'], + "feed_length": self.site.config['FEED_LENGTH'], + "feed_links_append_query": self.site.config["FEED_LINKS_APPEND_QUERY"], + "feed_teasers": self.site.config["FEED_TEASERS"], + "feed_plain": self.site.config["FEED_PLAIN"], "filters": self.site.config['FILTERS'], "index_file": self.site.config['INDEX_FILE'], "show_untranslated_posts": self.site.config['SHOW_UNTRANSLATED_POSTS'], @@ -85,6 +91,7 @@ class Indexes(Task): "strip_indexes": self.site.config['STRIP_INDEXES'], "blog_title": self.site.config["BLOG_TITLE"], "generate_atom": self.site.config["GENERATE_ATOM"], + "site_url": self.site.config["SITE_URL"], } template_name = "index.tmpl" @@ -110,8 +117,6 @@ class Indexes(Task): yield self.site.generic_index_renderer(lang, filtered_posts, indexes_title, template_name, context, kw, 'render_indexes', page_link, page_path) if self.site.config['POSTS_SECTIONS']: - - kw["posts_section_are_indexes"] = self.site.config['POSTS_SECTION_ARE_INDEXES'] index_len = len(kw['index_file']) groups = defaultdict(list) @@ -145,16 +150,16 @@ class Indexes(Task): context["pagekind"] = ["section_page"] context["description"] = self.site.config['POSTS_SECTION_DESCRIPTIONS'](lang)[section_slug] if section_slug in self.site.config['POSTS_SECTION_DESCRIPTIONS'](lang) else "" - if kw["posts_section_are_indexes"]: + if self.site.config["POSTS_SECTION_ARE_INDEXES"]: context["pagekind"].append("index") - kw["posts_section_title"] = self.site.config['POSTS_SECTION_TITLE'](lang) + posts_section_title = self.site.config['POSTS_SECTION_TITLE'](lang) section_title = None - if type(kw["posts_section_title"]) is dict: - if section_slug in kw["posts_section_title"]: - section_title = kw["posts_section_title"][section_slug] - elif type(kw["posts_section_title"]) is str: - section_title = kw["posts_section_title"] + if type(posts_section_title) is dict: + if section_slug in posts_section_title: + section_title = posts_section_title[section_slug] + elif type(posts_section_title) is str: + section_title = posts_section_title if not section_title: section_title = post_list[0].section_name(lang) section_title = section_title.format(name=post_list[0].section_name(lang)) @@ -168,7 +173,37 @@ class Indexes(Task): task['basename'] = self.name yield task - if not self.site.config["STORY_INDEX"]: + # RSS feed for section + deps = [] + deps_uptodate = [] + if kw["show_untranslated_posts"]: + posts = post_list[:kw['feed_length']] + else: + posts = [x for x in post_list if x.is_translation_available(lang)][:kw['feed_length']] + for post in posts: + deps += post.deps(lang) + deps_uptodate += post.deps_uptodate(lang) + + feed_url = urljoin(self.site.config['BASE_URL'], self.site.link('section_index_rss', section_slug, lang).lstrip('/')) + output_name = os.path.join(kw['output_folder'], self.site.path('section_index_rss', section_slug, lang).lstrip(os.sep)) + task = { + 'basename': self.name, + 'name': os.path.normpath(output_name), + 'file_dep': deps, + 'targets': [output_name], + 'actions': [(utils.generic_rss_renderer, + (lang, kw["blog_title"](lang), kw["site_url"], + context["description"], posts, output_name, + kw["feed_teasers"], kw["feed_plain"], kw['feed_length'], feed_url, + _enclosure, kw["feed_links_append_query"]))], + + 'task_dep': ['render_posts'], + 'clean': True, + 'uptodate': [utils.config_changed(kw, 'nikola.plugins.indexes')] + deps_uptodate, + } + yield task + + if not self.site.config["PAGE_INDEX"]: return kw = { "translations": self.site.config['TRANSLATIONS'], @@ -207,7 +242,7 @@ class Indexes(Task): for post in post_list: # If there is an index.html pending to be created from - # a story, do not generate the STORY_INDEX + # a page, do not generate the PAGE_INDEX if post.destination_path(lang) == short_destination: should_render = False else: @@ -252,7 +287,7 @@ class Indexes(Task): self.site, extension=extension) - def index_section_path(self, name, lang, is_feed=False): + def index_section_path(self, name, lang, is_feed=False, is_rss=False): """Link to the index for a section. Example: @@ -264,6 +299,8 @@ class Indexes(Task): if is_feed: extension = ".atom" index_file = os.path.splitext(self.site.config['INDEX_FILE'])[0] + extension + elif is_rss: + index_file = 'rss.xml' else: index_file = self.site.config['INDEX_FILE'] if name in self.number_of_pages_section[lang]: @@ -298,3 +335,12 @@ class Indexes(Task): link://section_index_atom/cars => /cars/index.atom """ return self.index_section_path(name, lang, is_feed=True) + + def index_section_rss_path(self, name, lang): + """Link to the RSS feed for a section. + + Example: + + link://section_index_rss/cars => /cars/rss.xml + """ + return self.index_section_path(name, lang, is_rss=True) diff --git a/nikola/plugins/task/listings.py b/nikola/plugins/task/listings.py index 891f361..e694aa5 100644 --- a/nikola/plugins/task/listings.py +++ b/nikola/plugins/task/listings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# Copyright © 2012-2016 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -29,12 +29,11 @@ from __future__ import unicode_literals, print_function from collections import defaultdict -import sys import os import lxml.html from pygments import highlight -from pygments.lexers import get_lexer_for_filename, TextLexer +from pygments.lexers import get_lexer_for_filename, guess_lexer, TextLexer import natsort from nikola.plugin_categories import Task @@ -55,6 +54,7 @@ class Listings(Task): def set_site(self, site): """Set Nikola site.""" site.register_path_handler('listing', self.listing_path) + site.register_path_handler('listing_source', self.listing_source_path) # We need to prepare some things for the listings path handler to work. @@ -73,7 +73,7 @@ class Listings(Task): if source in appearing_paths or dest in appearing_paths: problem = source if source in appearing_paths else dest utils.LOGGER.error("The listings input or output folder '{0}' appears in more than one entry in LISTINGS_FOLDERS, exiting.".format(problem)) - sys.exit(1) + continue appearing_paths.add(source) appearing_paths.add(dest) @@ -127,7 +127,11 @@ class Listings(Task): try: lexer = get_lexer_for_filename(in_name) except: - lexer = TextLexer() + try: + lexer = guess_lexer(fd.read()) + except: + lexer = TextLexer() + fd.seek(0) code = highlight(fd.read(), lexer, utils.NikolaPygmentsHTML(in_name)) title = os.path.basename(in_name) else: @@ -145,7 +149,7 @@ class Listings(Task): os.path.join( self.kw['output_folder'], output_folder)))) - if self.site.config['COPY_SOURCES'] and in_name: + if in_name: source_link = permalink[:-5] # remove '.html' else: source_link = None @@ -238,19 +242,35 @@ class Listings(Task): 'uptodate': [utils.config_changed(uptodate, 'nikola.plugins.task.listings:source')], 'clean': True, }, self.kw["filters"]) - if self.site.config['COPY_SOURCES']: - rel_name = os.path.join(rel_path, f) - rel_output_name = os.path.join(output_folder, rel_path, f) - self.register_output_name(input_folder, rel_name, rel_output_name) - out_name = os.path.join(self.kw['output_folder'], rel_output_name) - yield utils.apply_filters({ - 'basename': self.name, - 'name': out_name, - 'file_dep': [in_name], - 'targets': [out_name], - 'actions': [(utils.copy_file, [in_name, out_name])], - 'clean': True, - }, self.kw["filters"]) + + rel_name = os.path.join(rel_path, f) + rel_output_name = os.path.join(output_folder, rel_path, f) + self.register_output_name(input_folder, rel_name, rel_output_name) + out_name = os.path.join(self.kw['output_folder'], rel_output_name) + yield utils.apply_filters({ + 'basename': self.name, + 'name': out_name, + 'file_dep': [in_name], + 'targets': [out_name], + 'actions': [(utils.copy_file, [in_name, out_name])], + 'clean': True, + }, self.kw["filters"]) + + def listing_source_path(self, name, lang): + """A link to the source code for a listing. + + It will try to use the file name if it's not ambiguous, or the file path. + + Example: + + link://listing_source/hello.py => /listings/tutorial/hello.py + + link://listing_source/tutorial/hello.py => /listings/tutorial/hello.py + """ + result = self.listing_path(name, lang) + if result[-1].endswith('.html'): + result[-1] = result[-1][:-5] + return result def listing_path(self, namep, lang): """A link to a listing. @@ -275,14 +295,14 @@ class Listings(Task): # ambiguities. if len(self.improper_input_file_mapping[name]) > 1: utils.LOGGER.error("Using non-unique listing name '{0}', which maps to more than one listing name ({1})!".format(name, str(self.improper_input_file_mapping[name]))) - sys.exit(1) + return ["ERROR"] if len(self.site.config['LISTINGS_FOLDERS']) > 1: utils.LOGGER.notice("Using listings names in site.link() without input directory prefix while configuration's LISTINGS_FOLDERS has more than one entry.") name = list(self.improper_input_file_mapping[name])[0] break else: utils.LOGGER.error("Unknown listing name {0}!".format(namep)) - sys.exit(1) + return ["ERROR"] if not name.endswith(os.sep + self.site.config["INDEX_FILE"]): name += '.html' path_parts = name.split(os.sep) diff --git a/nikola/plugins/task/pages.py b/nikola/plugins/task/pages.py index 8d41035..7d8287b 100644 --- a/nikola/plugins/task/pages.py +++ b/nikola/plugins/task/pages.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# Copyright © 2012-2016 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -54,7 +54,7 @@ class RenderPages(Task): if post.is_post: context = {'pagekind': ['post_page']} else: - context = {'pagekind': ['story_page']} + context = {'pagekind': ['story_page', 'page_page']} for task in self.site.generic_page_renderer(lang, post, kw["filters"], context): task['uptodate'] = task['uptodate'] + [config_changed(kw, 'nikola.plugins.task.pages')] task['basename'] = self.name diff --git a/nikola/plugins/task/posts.py b/nikola/plugins/task/posts.py index 8735beb..fe10c5f 100644 --- a/nikola/plugins/task/posts.py +++ b/nikola/plugins/task/posts.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# Copyright © 2012-2016 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/task/py3_switch.py b/nikola/plugins/task/py3_switch.py index 930c593..2ff4e2d 100644 --- a/nikola/plugins/task/py3_switch.py +++ b/nikola/plugins/task/py3_switch.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# Copyright © 2012-2016 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -51,7 +51,7 @@ PY2_BARBS = [ "Python 2 is the safety blanket of languages. Be a big kid and switch to Python 3", "Python 2 is old and busted. Python 3 is the new hotness.", "Nice unicode you have there, would be a shame something happened to it.. switch to python 3!.", - "Don’t get in the way of progress! Upgrade to Python 3 and save a developer’s mind today!", + "Don't get in the way of progress! Upgrade to Python 3 and save a developer's mind today!", "Winners don't use Python 2 -- Signed: The FBI", "Python 2? What year is it?", "I just wanna tell you how I'm feeling\n" diff --git a/nikola/plugins/task/redirect.py b/nikola/plugins/task/redirect.py index 2d4eba4..b170b81 100644 --- a/nikola/plugins/task/redirect.py +++ b/nikola/plugins/task/redirect.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# Copyright © 2012-2016 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -50,7 +50,7 @@ class Redirect(Task): yield self.group_task() if kw['redirections']: for src, dst in kw["redirections"]: - src_path = os.path.join(kw["output_folder"], src) + src_path = os.path.join(kw["output_folder"], src.lstrip('/')) yield utils.apply_filters({ 'basename': self.name, 'name': src_path, diff --git a/nikola/plugins/task/robots.py b/nikola/plugins/task/robots.py index 7c7f5df..8537fc8 100644 --- a/nikola/plugins/task/robots.py +++ b/nikola/plugins/task/robots.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# Copyright © 2012-2016 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/task/rss.py b/nikola/plugins/task/rss.py index be57f5c..780559b 100644 --- a/nikola/plugins/task/rss.py +++ b/nikola/plugins/task/rss.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# Copyright © 2012-2016 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -34,6 +34,7 @@ except ImportError: from urllib.parse import urljoin # NOQA from nikola import utils +from nikola.nikola import _enclosure from nikola.plugin_categories import Task @@ -97,7 +98,7 @@ class GenerateRSS(Task): (lang, kw["blog_title"](lang), kw["site_url"], kw["blog_description"](lang), posts, output_name, kw["feed_teasers"], kw["feed_plain"], kw['feed_length'], feed_url, - None, kw["feed_links_append_query"]))], + _enclosure, kw["feed_links_append_query"]))], 'task_dep': ['render_posts'], 'clean': True, diff --git a/nikola/plugins/task/scale_images.py b/nikola/plugins/task/scale_images.py index e55dc6c..2b483ae 100644 --- a/nikola/plugins/task/scale_images.py +++ b/nikola/plugins/task/scale_images.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2014-2015 Pelle Nilsson and others. +# Copyright © 2014-2016 Pelle Nilsson and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -71,8 +71,8 @@ class ScaleImage(Task, ImageProcessor): def process_image(self, src, dst, thumb): """Resize an image.""" - self.resize_image(src, dst, self.kw['max_image_size'], False) - self.resize_image(src, thumb, self.kw['image_thumbnail_size'], False) + self.resize_image(src, dst, self.kw['max_image_size'], False, preserve_exif_data=self.kw['preserve_exif_data'], exif_whitelist=self.kw['exif_whitelist']) + self.resize_image(src, thumb, self.kw['image_thumbnail_size'], False, preserve_exif_data=self.kw['preserve_exif_data'], exif_whitelist=self.kw['exif_whitelist']) def gen_tasks(self): """Copy static files into the output folder.""" @@ -82,6 +82,8 @@ class ScaleImage(Task, ImageProcessor): 'image_folders': self.site.config['IMAGE_FOLDERS'], 'output_folder': self.site.config['OUTPUT_FOLDER'], 'filters': self.site.config['FILTERS'], + 'preserve_exif_data': self.site.config['PRESERVE_EXIF_DATA'], + 'exif_whitelist': self.site.config['EXIF_WHITELIST'], } self.image_ext_list = self.image_ext_list_builtin diff --git a/nikola/plugins/task/sitemap/__init__.py b/nikola/plugins/task/sitemap/__init__.py index 90acdd3..64fcb45 100644 --- a/nikola/plugins/task/sitemap/__init__.py +++ b/nikola/plugins/task/sitemap/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# Copyright © 2012-2016 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -158,7 +158,7 @@ class Sitemap(LateTask): continue alternates = [] if post: - for lang in kw['translations']: + for lang in post.translated_to: alt_url = post.permalink(lang=lang, absolute=True) if encodelink(loc) == alt_url: continue @@ -215,7 +215,7 @@ class Sitemap(LateTask): loc = urljoin(base_url, base_path + path) alternates = [] if post: - for lang in kw['translations']: + for lang in post.translated_to: alt_url = post.permalink(lang=lang, absolute=True) if encodelink(loc) == alt_url: continue diff --git a/nikola/plugins/task/sources.py b/nikola/plugins/task/sources.py index f782ad4..0d77aba 100644 --- a/nikola/plugins/task/sources.py +++ b/nikola/plugins/task/sources.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# Copyright © 2012-2016 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/task/tags.py b/nikola/plugins/task/tags.py index 6d9d495..8b4683e 100644 --- a/nikola/plugins/task/tags.py +++ b/nikola/plugins/task/tags.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# Copyright © 2012-2016 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -29,7 +29,6 @@ from __future__ import unicode_literals import json import os -import sys import natsort try: from urlparse import urljoin @@ -38,6 +37,7 @@ except ImportError: from nikola.plugin_categories import Task from nikola import utils +from nikola.nikola import _enclosure class RenderTags(Task): @@ -97,31 +97,31 @@ class RenderTags(Task): if not self.site.posts_per_tag and not self.site.posts_per_category: return - if kw['category_path'] == kw['tag_path']: - tags = {self.slugify_tag_name(tag): tag for tag in self.site.posts_per_tag.keys()} - cats = {tuple(self.slugify_category_name(category)): category for category in self.site.posts_per_category.keys()} - categories = {k[0]: v for k, v in cats.items() if len(k) == 1} - intersect = set(tags.keys()) & set(categories.keys()) - if len(intersect) > 0: - for slug in intersect: - utils.LOGGER.error("Category '{0}' and tag '{1}' both have the same slug '{2}'!".format('/'.join(categories[slug]), tags[slug], slug)) - sys.exit(1) - - # Test for category slug clashes - categories = {} - for category in self.site.posts_per_category.keys(): - slug = tuple(self.slugify_category_name(category)) - for part in slug: - if len(part) == 0: - utils.LOGGER.error("Category '{0}' yields invalid slug '{1}'!".format(category, '/'.join(slug))) - sys.exit(1) - if slug in categories: - other_category = categories[slug] - utils.LOGGER.error('You have categories that are too similar: {0} and {1}'.format(category, other_category)) - utils.LOGGER.error('Category {0} is used in: {1}'.format(category, ', '.join([p.source_path for p in self.site.posts_per_category[category]]))) - utils.LOGGER.error('Category {0} is used in: {1}'.format(other_category, ', '.join([p.source_path for p in self.site.posts_per_category[other_category]]))) - sys.exit(1) - categories[slug] = category + for lang in kw["translations"]: + if kw['category_path'][lang] == kw['tag_path'][lang]: + tags = {self.slugify_tag_name(tag, lang): tag for tag in self.site.tags_per_language[lang]} + cats = {tuple(self.slugify_category_name(category, lang)): category for category in self.site.posts_per_category.keys()} + categories = {k[0]: v for k, v in cats.items() if len(k) == 1} + intersect = set(tags.keys()) & set(categories.keys()) + if len(intersect) > 0: + for slug in intersect: + utils.LOGGER.error("Category '{0}' and tag '{1}' both have the same slug '{2}' for language {3}!".format('/'.join(categories[slug]), tags[slug], slug, lang)) + + # Test for category slug clashes + categories = {} + for category in self.site.posts_per_category.keys(): + slug = tuple(self.slugify_category_name(category, lang)) + for part in slug: + if len(part) == 0: + utils.LOGGER.error("Category '{0}' yields invalid slug '{1}'!".format(category, '/'.join(slug))) + raise RuntimeError("Category '{0}' yields invalid slug '{1}'!".format(category, '/'.join(slug))) + if slug in categories: + other_category = categories[slug] + utils.LOGGER.error('You have categories that are too similar: {0} and {1} (language {2})'.format(category, other_category, lang)) + utils.LOGGER.error('Category {0} is used in: {1}'.format(category, ', '.join([p.source_path for p in self.site.posts_per_category[category]]))) + utils.LOGGER.error('Category {0} is used in: {1}'.format(other_category, ', '.join([p.source_path for p in self.site.posts_per_category[other_category]]))) + raise RuntimeError("Category '{0}' yields invalid slug '{1}'!".format(category, '/'.join(slug))) + categories[slug] = category tag_list = list(self.site.posts_per_tag.items()) cat_list = list(self.site.posts_per_category.items()) @@ -185,7 +185,7 @@ class RenderTags(Task): task['clean'] = True yield utils.apply_filters(task, kw['filters']) - def _create_tags_page(self, kw, include_tags=True, include_categories=True): + def _create_tags_page(self, kw, lang, include_tags=True, include_categories=True): """Create a global "all your tags/categories" page for each language.""" categories = [cat.category_name for cat in self.site.category_hierarchy] has_categories = (categories != []) and include_categories @@ -193,59 +193,59 @@ class RenderTags(Task): kw = kw.copy() if include_categories: kw['categories'] = categories - for lang in kw["translations"]: - tags = natsort.natsorted([tag for tag in self.site.tags_per_language[lang] - if len(self.site.posts_per_tag[tag]) >= kw["taglist_minimum_post_count"]], - alg=natsort.ns.F | natsort.ns.IC) - has_tags = (tags != []) and include_tags - if include_tags: - kw['tags'] = tags - output_name = os.path.join( - kw['output_folder'], self.site.path('tag_index' if has_tags else 'category_index', None, lang)) - context = {} - if has_categories and has_tags: - context["title"] = kw["messages"][lang]["Tags and Categories"] - elif has_categories: - context["title"] = kw["messages"][lang]["Categories"] - else: - context["title"] = kw["messages"][lang]["Tags"] - if has_tags: - context["items"] = [(tag, self.site.link("tag", tag, lang)) for tag - in tags] - else: - context["items"] = None - if has_categories: - context["cat_items"] = [(tag, self.site.link("category", tag, lang)) for tag - in categories] - context['cat_hierarchy'] = [(node.name, node.category_name, node.category_path, - self.site.link("category", node.category_name), - node.indent_levels, node.indent_change_before, - node.indent_change_after) - for node in self.site.category_hierarchy] - else: - context["cat_items"] = None - context["permalink"] = self.site.link("tag_index" if has_tags else "category_index", None, lang) - context["description"] = context["title"] - context["pagekind"] = ["list", "tags_page"] - task = self.site.generic_post_list_renderer( - lang, - [], - output_name, - template_name, - kw['filters'], - context, - ) - task['uptodate'] = task['uptodate'] + [utils.config_changed(kw, 'nikola.plugins.task.tags:page')] - task['basename'] = str(self.name) - yield task + tags = natsort.natsorted([tag for tag in self.site.tags_per_language[lang] + if len(self.site.posts_per_tag[tag]) >= kw["taglist_minimum_post_count"]], + alg=natsort.ns.F | natsort.ns.IC) + has_tags = (tags != []) and include_tags + if include_tags: + kw['tags'] = tags + output_name = os.path.join( + kw['output_folder'], self.site.path('tag_index' if has_tags else 'category_index', None, lang)) + context = {} + if has_categories and has_tags: + context["title"] = kw["messages"][lang]["Tags and Categories"] + elif has_categories: + context["title"] = kw["messages"][lang]["Categories"] + else: + context["title"] = kw["messages"][lang]["Tags"] + if has_tags: + context["items"] = [(tag, self.site.link("tag", tag, lang)) for tag + in tags] + else: + context["items"] = None + if has_categories: + context["cat_items"] = [(tag, self.site.link("category", tag, lang)) for tag + in categories] + context['cat_hierarchy'] = [(node.name, node.category_name, node.category_path, + self.site.link("category", node.category_name), + node.indent_levels, node.indent_change_before, + node.indent_change_after) + for node in self.site.category_hierarchy] + else: + context["cat_items"] = None + context["permalink"] = self.site.link("tag_index" if has_tags else "category_index", None, lang) + context["description"] = context["title"] + context["pagekind"] = ["list", "tags_page"] + task = self.site.generic_post_list_renderer( + lang, + [], + output_name, + template_name, + kw['filters'], + context, + ) + task['uptodate'] = task['uptodate'] + [utils.config_changed(kw, 'nikola.plugins.task.tags:page')] + task['basename'] = str(self.name) + yield task def list_tags_page(self, kw): """Create a global "all your tags/categories" page for each language.""" - if self.site.config['TAG_PATH'] == self.site.config['CATEGORY_PATH']: - yield self._create_tags_page(kw, True, True) - else: - yield self._create_tags_page(kw, False, True) - yield self._create_tags_page(kw, True, False) + for lang in kw["translations"]: + if self.site.config['TAG_PATH'][lang] == self.site.config['CATEGORY_PATH'][lang]: + yield self._create_tags_page(kw, lang, True, True) + else: + yield self._create_tags_page(kw, lang, False, True) + yield self._create_tags_page(kw, lang, True, False) def _get_title(self, tag, is_category): if is_category: @@ -253,9 +253,9 @@ class RenderTags(Task): else: return tag - def _get_indexes_title(self, tag, is_category, lang, messages): + def _get_indexes_title(self, tag, nice_tag, is_category, lang, messages): titles = self.site.config['CATEGORY_PAGES_TITLES'] if is_category else self.site.config['TAG_PAGES_TITLES'] - return titles[lang][tag] if lang in titles and tag in titles[lang] else messages[lang]["Posts about %s"] % tag + return titles[lang][tag] if lang in titles and tag in titles[lang] else messages[lang]["Posts about %s"] % nice_tag def _get_description(self, tag, is_category, lang): descriptions = self.site.config['CATEGORY_PAGES_DESCRIPTIONS'] if is_category else self.site.config['TAG_PAGES_DESCRIPTIONS'] @@ -290,7 +290,7 @@ class RenderTags(Task): context_source["category"] = tag context_source["category_path"] = self.site.parse_category_name(tag) context_source["tag"] = title - indexes_title = self._get_indexes_title(title, is_category, lang, kw["messages"]) + indexes_title = self._get_indexes_title(tag, title, is_category, lang, kw["messages"]) context_source["description"] = self._get_description(tag, is_category, lang) if is_category: context_source["subcategories"] = self._get_subcategories(tag) @@ -312,7 +312,7 @@ class RenderTags(Task): context["category"] = tag context["category_path"] = self.site.parse_category_name(tag) context["tag"] = title - context["title"] = self._get_indexes_title(title, is_category, lang, kw["messages"]) + context["title"] = self._get_indexes_title(tag, title, is_category, lang, kw["messages"]) context["posts"] = post_list context["permalink"] = self.site.link(kind, tag, lang) context["kind"] = kind @@ -379,17 +379,20 @@ class RenderTags(Task): (lang, "{0} ({1})".format(kw["blog_title"](lang), self._get_title(tag, is_category)), kw["site_url"], None, post_list, output_name, kw["feed_teasers"], kw["feed_plain"], kw['feed_length'], - feed_url, None, kw["feed_link_append_query"]))], + feed_url, _enclosure, kw["feed_link_append_query"]))], 'clean': True, 'uptodate': [utils.config_changed(kw, 'nikola.plugins.task.tags:rss')] + deps_uptodate, 'task_dep': ['render_posts'], } return utils.apply_filters(task, kw['filters']) - def slugify_tag_name(self, name): + def slugify_tag_name(self, name, lang): """Slugify a tag name.""" + if lang is None: # TODO: remove in v8 + utils.LOGGER.warn("RenderTags.slugify_tag_name() called without language!") + lang = '' if self.site.config['SLUG_TAG_PATH']: - name = utils.slugify(name) + name = utils.slugify(name, lang) return name def tag_index_path(self, name, lang): @@ -430,13 +433,13 @@ class RenderTags(Task): return [_f for _f in [ self.site.config['TRANSLATIONS'][lang], self.site.config['TAG_PATH'][lang], - self.slugify_tag_name(name), + self.slugify_tag_name(name, lang), self.site.config['INDEX_FILE']] if _f] else: return [_f for _f in [ self.site.config['TRANSLATIONS'][lang], self.site.config['TAG_PATH'][lang], - self.slugify_tag_name(name) + ".html"] if _f] + self.slugify_tag_name(name, lang) + ".html"] if _f] def tag_atom_path(self, name, lang): """A link to a tag's Atom feed. @@ -446,7 +449,7 @@ class RenderTags(Task): link://tag_atom/cats => /tags/cats.atom """ return [_f for _f in [self.site.config['TRANSLATIONS'][lang], - self.site.config['TAG_PATH'][lang], self.slugify_tag_name(name) + ".atom"] if + self.site.config['TAG_PATH'][lang], self.slugify_tag_name(name, lang) + ".atom"] if _f] def tag_rss_path(self, name, lang): @@ -457,15 +460,18 @@ class RenderTags(Task): link://tag_rss/cats => /tags/cats.xml """ return [_f for _f in [self.site.config['TRANSLATIONS'][lang], - self.site.config['TAG_PATH'][lang], self.slugify_tag_name(name) + ".xml"] if + self.site.config['TAG_PATH'][lang], self.slugify_tag_name(name, lang) + ".xml"] if _f] - def slugify_category_name(self, name): + def slugify_category_name(self, name, lang): """Slugify a category name.""" + if lang is None: # TODO: remove in v8 + utils.LOGGER.warn("RenderTags.slugify_category_name() called without language!") + lang = '' path = self.site.parse_category_name(name) if self.site.config['CATEGORY_OUTPUT_FLAT_HIERARCHY']: path = path[-1:] # only the leaf - result = [self.slugify_tag_name(part) for part in path] + result = [self.slugify_tag_name(part, lang) for part in path] result[0] = self.site.config['CATEGORY_PREFIX'] + result[0] if not self.site.config['PRETTY_URLS']: result = ['-'.join(result)] @@ -485,11 +491,11 @@ class RenderTags(Task): if self.site.config['PRETTY_URLS']: return [_f for _f in [self.site.config['TRANSLATIONS'][lang], self.site.config['CATEGORY_PATH'][lang]] if - _f] + self.slugify_category_name(name) + [self.site.config['INDEX_FILE']] + _f] + self.slugify_category_name(name, lang) + [self.site.config['INDEX_FILE']] else: return [_f for _f in [self.site.config['TRANSLATIONS'][lang], self.site.config['CATEGORY_PATH'][lang]] if - _f] + self._add_extension(self.slugify_category_name(name), ".html") + _f] + self._add_extension(self.slugify_category_name(name, lang), ".html") def category_atom_path(self, name, lang): """A link to a category's Atom feed. @@ -500,7 +506,7 @@ class RenderTags(Task): """ return [_f for _f in [self.site.config['TRANSLATIONS'][lang], self.site.config['CATEGORY_PATH'][lang]] if - _f] + self._add_extension(self.slugify_category_name(name), ".atom") + _f] + self._add_extension(self.slugify_category_name(name, lang), ".atom") def category_rss_path(self, name, lang): """A link to a category's RSS feed. @@ -511,4 +517,4 @@ class RenderTags(Task): """ return [_f for _f in [self.site.config['TRANSLATIONS'][lang], self.site.config['CATEGORY_PATH'][lang]] if - _f] + self._add_extension(self.slugify_category_name(name), ".xml") + _f] + self._add_extension(self.slugify_category_name(name, lang), ".xml") diff --git a/nikola/plugins/template/__init__.py b/nikola/plugins/template/__init__.py index d416ad7..d5efd61 100644 --- a/nikola/plugins/template/__init__.py +++ b/nikola/plugins/template/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# Copyright © 2012-2016 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/nikola/plugins/template/jinja.py b/nikola/plugins/template/jinja.py index e7df102..5a2135f 100644 --- a/nikola/plugins/template/jinja.py +++ b/nikola/plugins/template/jinja.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# Copyright © 2012-2016 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -29,8 +29,8 @@ from __future__ import unicode_literals import os +import io import json -from collections import deque try: import jinja2 from jinja2 import meta @@ -47,23 +47,27 @@ class JinjaTemplates(TemplateSystem): name = "jinja" lookup = None dependency_cache = {} + per_file_cache = {} def __init__(self): """Initialize Jinja2 environment with extended set of filters.""" if jinja2 is None: return - self.lookup = jinja2.Environment() + + def set_directories(self, directories, cache_folder): + """Create a new template lookup with set directories.""" + if jinja2 is None: + req_missing(['jinja2'], 'use this theme') + cache_folder = os.path.join(cache_folder, 'jinja') + makedirs(cache_folder) + cache = jinja2.FileSystemBytecodeCache(cache_folder) + self.lookup = jinja2.Environment(bytecode_cache=cache) self.lookup.trim_blocks = True self.lookup.lstrip_blocks = True self.lookup.filters['tojson'] = json.dumps self.lookup.globals['enumerate'] = enumerate self.lookup.globals['isinstance'] = isinstance self.lookup.globals['tuple'] = tuple - - def set_directories(self, directories, cache_folder): - """Create a new template lookup with set directories.""" - if jinja2 is None: - req_missing(['jinja2'], 'use this theme') self.directories = directories self.create_lookup() @@ -88,36 +92,46 @@ class JinjaTemplates(TemplateSystem): if jinja2 is None: req_missing(['jinja2'], 'use this theme') template = self.lookup.get_template(template_name) - output = template.render(**context) + data = template.render(**context) if output_name is not None: makedirs(os.path.dirname(output_name)) - with open(output_name, 'w+') as output: - output.write(output.encode('utf8')) - return output + with io.open(output_name, 'w', encoding='utf-8') as output: + output.write(data) + return data def render_template_to_string(self, template, context): """Render template to a string using context.""" return self.lookup.from_string(template).render(**context) + def get_string_deps(self, text): + """Find dependencies for a template string.""" + deps = set([]) + ast = self.lookup.parse(text) + dep_names = meta.find_referenced_templates(ast) + for dep_name in dep_names: + filename = self.lookup.loader.get_source(self.lookup, dep_name)[1] + sub_deps = [filename] + self.get_deps(filename) + self.dependency_cache[dep_name] = sub_deps + deps |= set(sub_deps) + return list(deps) + + def get_deps(self, filename): + """Return paths to dependencies for the template loaded from filename.""" + with io.open(filename, 'r', encoding='utf-8') as fd: + text = fd.read() + return self.get_string_deps(text) + def template_deps(self, template_name): """Generate list of dependencies for a template.""" - # Cache the lists of dependencies for each template name. if self.dependency_cache.get(template_name) is None: - # Use a breadth-first search to find all templates this one - # depends on. - queue = deque([template_name]) - visited_templates = set([template_name]) - deps = [] - while len(queue) > 0: - curr = queue.popleft() - source, filename = self.lookup.loader.get_source(self.lookup, - curr)[:2] - deps.append(filename) - ast = self.lookup.parse(source) - dep_names = meta.find_referenced_templates(ast) - for dep_name in dep_names: - if (dep_name not in visited_templates and dep_name is not None): - visited_templates.add(dep_name) - queue.append(dep_name) - self.dependency_cache[template_name] = deps + filename = self.lookup.loader.get_source(self.lookup, template_name)[1] + self.dependency_cache[template_name] = [filename] + self.get_deps(filename) return self.dependency_cache[template_name] + + def get_template_path(self, template_name): + """Get the path to a template or return None.""" + try: + t = self.lookup.get_template(template_name) + return t.filename + except jinja2.TemplateNotFound: + return None diff --git a/nikola/plugins/template/mako.py b/nikola/plugins/template/mako.py index 6da21db..0c9bb64 100644 --- a/nikola/plugins/template/mako.py +++ b/nikola/plugins/template/mako.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# Copyright © 2012-2016 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -27,12 +27,13 @@ """Mako template handler.""" from __future__ import unicode_literals, print_function, absolute_import +import io import os import shutil import sys import tempfile -from mako import util, lexer, parsetree +from mako import exceptions, util, lexer, parsetree from mako.lookup import TemplateLookup from mako.template import Template from markupsafe import Markup # It's ok, Mako requires it @@ -54,9 +55,8 @@ class MakoTemplates(TemplateSystem): directories = [] cache_dir = None - def get_deps(self, filename): - """Get dependencies for a template (internal function).""" - text = util.read_file(filename) + def get_string_deps(self, text, filename=None): + """Find dependencies for a template string.""" lex = lexer.Lexer(text=text, filename=filename) lex.parse() @@ -65,8 +65,17 @@ class MakoTemplates(TemplateSystem): keyword = getattr(n, 'keyword', None) if keyword in ["inherit", "namespace"] or isinstance(n, parsetree.IncludeTag): deps.append(n.attributes['file']) + # Some templates will include "foo.tmpl" and we need paths, so normalize them + # using the template lookup + for i, d in enumerate(deps): + deps[i] = self.get_template_path(d) return deps + def get_deps(self, filename): + """Get paths to dependencies for a template.""" + text = util.read_file(filename) + return self.get_string_deps(text, filename) + def set_directories(self, directories, cache_folder): """Create a new template lookup with set directories.""" cache_dir = os.path.join(cache_folder, '.mako.tmp') @@ -108,14 +117,14 @@ class MakoTemplates(TemplateSystem): data = template.render_unicode(**context) if output_name is not None: makedirs(os.path.dirname(output_name)) - with open(output_name, 'w+') as output: + with io.open(output_name, 'w', encoding='utf-8') as output: output.write(data) return data def render_template_to_string(self, template, context): """Render template to a string using context.""" context.update(self.filters) - return Template(template).render(**context) + return Template(template, lookup=self.lookup).render(**context) def template_deps(self, template_name): """Generate list of dependencies for a template.""" @@ -126,10 +135,18 @@ class MakoTemplates(TemplateSystem): dep_filenames = self.get_deps(template.filename) deps = [template.filename] for fname in dep_filenames: - deps += self.template_deps(fname) - self.cache[template_name] = tuple(deps) + deps += [fname] + self.get_deps(fname) + self.cache[template_name] = deps return list(self.cache[template_name]) + def get_template_path(self, template_name): + """Get the path to a template or return None.""" + try: + t = self.lookup.get_template(template_name) + return t.filename + except exceptions.TopLevelLookupException: + return None + def striphtml(text): """Strip HTML tags from text.""" diff --git a/nikola/post.py b/nikola/post.py index f8039e0..37e4241 100644 --- a/nikola/post.py +++ b/nikola/post.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# Copyright © 2012-2016 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -43,6 +43,7 @@ except ImportError: from . import utils +from blinker import signal import dateutil.tz import lxml.html import natsort @@ -51,7 +52,7 @@ try: except ImportError: pyphen = None -from math import ceil +from math import ceil # for reading time feature # for tearDown with _reload we cannot use 'from import' to get forLocaleBorg import nikola.utils @@ -109,7 +110,6 @@ class Post(object): self.base_url = self.config['BASE_URL'] self.is_draft = False self.is_private = False - self.is_mathjax = False self.strip_indexes = self.config['STRIP_INDEXES'] self.index_file = self.config['INDEX_FILE'] self.pretty_urls = self.config['PRETTY_URLS'] @@ -136,6 +136,7 @@ class Post(object): self._dependency_file_page = defaultdict(list) self._dependency_uptodate_fragment = defaultdict(list) self._dependency_uptodate_page = defaultdict(list) + self._depfile = defaultdict(list) default_metadata, self.newstylemeta = get_meta(self, self.config['FILE_METADATA_REGEXP'], self.config['UNSLUGIFY_TITLES']) @@ -160,8 +161,14 @@ class Post(object): for lang in sorted(self.translated_to): default_metadata.update(self.meta[lang]) + # Load data field from metadata + self.data = Functionary(lambda: None, self.default_lang) + for lang in self.translations: + if self.meta[lang].get('data') is not None: + self.data[lang] = utils.load_data(self.meta[lang]['data']) + if 'date' not in default_metadata and not use_in_feeds: - # For stories we don't *really* need a date + # For pages we don't *really* need a date if self.config['__invariant__']: default_metadata['date'] = datetime.datetime(2013, 12, 31, 23, 59, 59, tzinfo=tzinfo) else: @@ -169,7 +176,10 @@ class Post(object): os.stat(self.source_path).st_ctime).replace(tzinfo=dateutil.tz.tzutc()).astimezone(tzinfo) # If time zone is set, build localized datetime. - self.date = to_datetime(self.meta[self.default_lang]['date'], tzinfo) + try: + self.date = to_datetime(self.meta[self.default_lang]['date'], tzinfo) + except ValueError: + raise ValueError("Invalid date '{0}' in file {1}".format(self.meta[self.default_lang]['date'], source_path)) if 'updated' not in default_metadata: default_metadata['updated'] = default_metadata.get('date', None) @@ -178,12 +188,11 @@ class Post(object): if 'title' not in default_metadata or 'slug' not in default_metadata \ or 'date' not in default_metadata: - raise OSError("You must set a title (found '{0}'), a slug (found " - "'{1}') and a date (found '{2}')! [in file " - "{3}]".format(default_metadata.get('title', None), - default_metadata.get('slug', None), - default_metadata.get('date', None), - source_path)) + raise ValueError("You must set a title (found '{0}'), a slug (found '{1}') and a date (found '{2}')! " + "[in file {3}]".format(default_metadata.get('title', None), + default_metadata.get('slug', None), + default_metadata.get('date', None), + source_path)) if 'type' not in default_metadata: # default value is 'text' @@ -223,9 +232,6 @@ class Post(object): self.use_in_feeds = use_in_feeds and not is_draft and not is_private \ and not self.publish_later - # If mathjax is a tag, or it's a ipynb post, then enable mathjax rendering support - self.is_mathjax = ('mathjax' in self.tags) or (self.compiler.name == 'ipynb') - # Register potential extra dependencies self.compiler.register_extra_dependencies(self) @@ -258,6 +264,17 @@ class Post(object): else: return False + @property + def is_mathjax(self): + """True if this post has the mathjax tag in the current language or is a python notebook.""" + if self.compiler.name == 'ipynb': + return True + lang = nikola.utils.LocaleBorg().current_lang + if self.is_translation_available(lang): + return 'mathjax' in self.tags_for_language(lang) + # If it has math in ANY other language, enable it. Better inefficient than broken. + return 'mathjax' in self.alltags + @property def alltags(self): """Return ALL the tags for this post.""" @@ -416,6 +433,24 @@ class Post(object): if add == 'page' or add == 'both': self._dependency_uptodate_page[lang].append((is_callable, dependency)) + def register_depfile(self, dep, dest=None, lang=None): + """Register a dependency in the dependency file.""" + if not dest: + dest = self.translated_base_path(lang) + self._depfile[dest].append(dep) + + @staticmethod + def write_depfile(dest, deps_list): + """Write a depfile for a given language.""" + deps_path = dest + '.dep' + if deps_list: + deps_list = [p for p in deps_list if p != dest] # Don't depend on yourself (#1671) + with io.open(deps_path, "w+", encoding="utf8") as deps_file: + deps_file.write('\n'.join(deps_list)) + else: + if os.path.isfile(deps_path): + os.unlink(deps_path) + def _get_dependencies(self, deps_list): deps = [] for dep in deps_list: @@ -448,6 +483,8 @@ class Post(object): cand_3 = get_translation_candidate(self.config, self.metadata_path, lang) if os.path.exists(cand_3): deps.append(cand_3) + if self.meta('data', lang): + deps.append(self.meta('data', lang)) deps += self._get_dependencies(self._dependency_file_page[lang]) deps += self._get_dependencies(self._dependency_file_page[None]) return sorted(deps) @@ -482,7 +519,15 @@ class Post(object): self.compile_html( self.translated_source_path(lang), dest, - self.is_two_file), + self.is_two_file) + Post.write_depfile(dest, self._depfile[dest]) + + signal('compiled').send({ + 'source': self.translated_source_path(lang), + 'dest': dest, + 'post': self, + }) + if self.meta('password'): # TODO: get rid of this feature one day (v8?; warning added in v7.3.0.) LOGGER.warn("The post {0} is using the `password` attribute, which may stop working in the future.") @@ -788,7 +833,7 @@ class Post(object): slug = slug[0] else: slug = self.meta[lang]['section'].split(',')[0] if 'section' in self.meta[lang] else self.messages[lang]["Uncategorized"] - return utils.slugify(slug) + return utils.slugify(slug, lang) def permalink(self, lang=None, absolute=False, extension='.html', query=None): """Return permalink for a post.""" @@ -864,7 +909,7 @@ def re_meta(line, match=None): return (None,) -def _get_metadata_from_filename_by_regex(filename, metadata_regexp, unslugify_titles): +def _get_metadata_from_filename_by_regex(filename, metadata_regexp, unslugify_titles, lang): """Try to reed the metadata from the filename based on the given re. This requires to use symbolic group names in the pattern. @@ -879,7 +924,7 @@ def _get_metadata_from_filename_by_regex(filename, metadata_regexp, unslugify_ti for key, value in match.groupdict().items(): k = key.lower().strip() # metadata must be lowercase if k == 'title' and unslugify_titles: - meta[k] = unslugify(value, discard_numbers=False) + meta[k] = unslugify(value, lang, discard_numbers=False) else: meta[k] = value @@ -1043,7 +1088,8 @@ def get_meta(post, file_metadata_regexp=None, unslugify_titles=False, lang=None) if file_metadata_regexp is not None: meta.update(_get_metadata_from_filename_by_regex(post.source_path, file_metadata_regexp, - unslugify_titles)) + unslugify_titles, + post.default_lang)) compiler_meta = {} @@ -1062,7 +1108,7 @@ def get_meta(post, file_metadata_regexp=None, unslugify_titles=False, lang=None) if 'slug' not in meta: # If no slug is found in the metadata use the filename meta['slug'] = slugify(unicode_str(os.path.splitext( - os.path.basename(post.source_path))[0])) + os.path.basename(post.source_path))[0]), post.default_lang) if 'title' not in meta: # If no title is found, use the filename without extension @@ -1081,6 +1127,7 @@ def hyphenate(dom, _lang): lang = LEGAL_VALUES['PYPHEN_LOCALES'].get(_lang, pyphen.language_fallback(_lang)) else: utils.req_missing(['pyphen'], 'hyphenate texts', optional=True) + hyphenator = None if pyphen is not None and lang is not None: # If pyphen does exist, we tell the user when configuring the site. # If it does not support a language, we ignore it quietly. @@ -1089,6 +1136,7 @@ def hyphenate(dom, _lang): except KeyError: LOGGER.error("Cannot find hyphenation dictoniaries for {0} (from {1}).".format(lang, _lang)) LOGGER.error("Pyphen cannot be installed to ~/.local (pip install --user).") + if hyphenator is not None: for tag in ('p', 'li', 'span'): for node in dom.xpath("//%s[not(parent::pre)]" % tag): skip_node = False diff --git a/nikola/shortcodes.py b/nikola/shortcodes.py new file mode 100644 index 0000000..b11ddac --- /dev/null +++ b/nikola/shortcodes.py @@ -0,0 +1,339 @@ +# -*- coding: utf-8 -*- + +# Copyright © 2012-2016 Roberto Alsina and others. + +# Permission is hereby granted, free of charge, to any +# person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the +# Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the +# Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice +# shall be included in all copies or substantial portions of +# the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +"""Support for Hugo-style shortcodes.""" + +from __future__ import unicode_literals +from .utils import LOGGER +import sys + + +# Constants +_TEXT = 1 +_SHORTCODE_START = 2 +_SHORTCODE_END = 3 + + +class ParsingError(Exception): + """Used for forwarding parsing error messages to apply_shortcodes.""" + + pass + + +def _format_position(data, pos): + """Return position formatted as line/column. + + This is used for prettier error messages. + """ + line = 0 + col = 0 + llb = '' # last line break + for c in data[:pos]: + if c == '\r' or c == '\n': + if llb and c != llb: + llb = '' + else: + line += 1 + col = 0 + llb = c + else: + col += 1 + llb = '' + return "line {0}, column {1}".format(line + 1, col + 1) + + +def _skip_whitespace(data, pos, must_be_nontrivial=False): + """Return first position after whitespace. + + If must_be_nontrivial is set to True, raises ParsingError + if no whitespace is found. + """ + if must_be_nontrivial: + if pos == len(data) or not data[pos].isspace(): + raise ParsingError("Expecting whitespace at {0}!".format(_format_position(data, pos))) + while pos < len(data): + if not data[pos].isspace(): + break + pos += 1 + return pos + + +def _skip_nonwhitespace(data, pos): + """Return first position not before pos which contains a non-whitespace character.""" + while pos < len(data): + if data[pos].isspace(): + break + pos += 1 + return pos + + +def _parse_quoted_string(data, start): + """Parse a quoted string starting at position start in data. + + Returns the position after the string followed by the string itself. + """ + value = '' + qc = data[start] + pos = start + 1 + while pos < len(data): + char = data[pos] + if char == '\\': + if pos + 1 < len(data): + value += data[pos + 1] + pos += 2 + else: + raise ParsingError("Unexpected end of data while escaping ({0})".format(_format_position(data, pos))) + elif (char == "'" or char == '"') and char == qc: + return pos + 1, value + else: + value += char + pos += 1 + raise ParsingError("Unexpected end of unquoted string (started at {0})!".format(_format_position(data, start))) + + +def _parse_unquoted_string(data, start, stop_at_equals): + """Parse an unquoted string starting at position start in data. + + Returns the position after the string followed by the string itself. + In case stop_at_equals is set to True, an equal sign will terminate + the string. + """ + value = '' + pos = start + while pos < len(data): + char = data[pos] + if char == '\\': + if pos + 1 < len(data): + value += data[pos + 1] + pos += 2 + else: + raise ParsingError("Unexpected end of data while escaping ({0})".format(_format_position(data, pos))) + elif char.isspace(): + break + elif char == '=' and stop_at_equals: + break + elif char == "'" or char == '"': + raise ParsingError("Unexpected quotation mark in unquoted string ({0})".format(_format_position(data, pos))) + else: + value += char + pos += 1 + return pos, value + + +def _parse_string(data, start, stop_at_equals=False, must_have_content=False): + """Parse a string starting at position start in data. + + Returns the position after the string, followed by the string itself, and + followed by a flog indicating whether the following character is an equals + sign (only set if stop_at_equals is True). + + If must_have_content is set to True, no empty unquoted strings are accepted. + """ + if start == len(data): + raise ParsingError("Expecting string, but found end of input!") + char = data[start] + if char == '"' or char == "'": + end, value = _parse_quoted_string(data, start) + has_content = True + else: + end, value = _parse_unquoted_string(data, start, stop_at_equals) + has_content = len(value) > 0 + if must_have_content and not has_content: + raise ParsingError("String starting at {0} must be non-empty!".format(_format_position(data, start))) + + next_is_equals = False + if stop_at_equals and end + 1 < len(data): + next_is_equals = (data[end] == '=') + return end, value, next_is_equals + + +def _parse_shortcode_args(data, start, shortcode_name, start_pos): + """When pointed to after a shortcode's name in a shortcode tag, parses the shortcode's arguments until '%}}'. + + Returns the position after '%}}', followed by a tuple (args, kw). + + name and start_pos are only used for formatting error messages. + """ + args = [] + kwargs = {} + + pos = start + while True: + # Skip whitespaces + try: + pos = _skip_whitespace(data, pos, must_be_nontrivial=True) + except ParsingError: + if not args and not kwargs: + raise ParsingError("Shortcode '{0}' starting at {1} is not terminated correctly with '%}}}}'!".format(shortcode_name, _format_position(data, start_pos))) + else: + raise ParsingError("Syntax error in shortcode '{0}' at {1}: expecting whitespace!".format(shortcode_name, _format_position(data, pos))) + if pos == len(data): + break + # Check for end of shortcode + if pos + 3 <= len(data) and data[pos:pos + 3] == '%}}': + return pos + 3, (args, kwargs) + # Read name + pos, name, next_is_equals = _parse_string(data, pos, stop_at_equals=True, must_have_content=True) + if next_is_equals: + # Read value + pos, value, _ = _parse_string(data, pos + 1, stop_at_equals=False, must_have_content=False) + # Store keyword argument + kwargs[name] = value + else: + # Store positional argument + args.append(name) + + raise ParsingError("Shortcode '{0}' starting at {1} is not terminated correctly with '%}}}}'!".format(shortcode_name, _format_position(data, start_pos))) + + +def _split_shortcodes(data): + """Given input data, splits it into a sequence of texts, shortcode starts and shortcode ends. + + Returns a list of tuples of the following forms: + + 1. (_TEXT, text) + 2. (_SHORTCODE_START, text, start, name, args) + 3. (_SHORTCODE_END, text, start, name) + + Here, text is the raw text represented by the token; start is the starting position in data + of the token; name is the name of the shortcode; and args is a tuple (args, kw) as returned + by _parse_shortcode_args. + """ + pos = 0 + result = [] + while pos < len(data): + # Search for shortcode start + start = data.find('{{%', pos) + if start < 0: + result.append((_TEXT, data[pos:])) + break + result.append((_TEXT, data[pos:start])) + # Extract name + name_start = _skip_whitespace(data, start + 3) + name_end = _skip_nonwhitespace(data, name_start) + name = data[name_start:name_end] + if not name: + raise ParsingError("Syntax error: '{{{{%' must be followed by shortcode name ({0})!".format(_format_position(data, start))) + # Finish shortcode + if name[0] == '/': + # This is a closing shortcode + name = name[1:] + end_start = _skip_whitespace(data, name_end) # start of '%}}' + pos = end_start + 3 + # Must be followed by '%}}' + if pos > len(data) or data[end_start:pos] != '%}}': + raise ParsingError("Syntax error: '{{{{% /{0}' must be followed by ' %}}}}' ({1})!".format(name, _format_position(data, end_start))) + result.append((_SHORTCODE_END, data[start:pos], start, name)) + elif name == '%}}': + raise ParsingError("Syntax error: '{{{{%' must be followed by shortcode name ({0})!".format(_format_position(data, start))) + else: + # This is an opening shortcode + pos, args = _parse_shortcode_args(data, name_end, shortcode_name=name, start_pos=start) + result.append((_SHORTCODE_START, data[start:pos], start, name, args)) + return result + + +# FIXME: in v8, get rid of with_dependencies +def apply_shortcodes(data, registry, site=None, filename=None, raise_exceptions=False, lang=None, with_dependencies=False, extra_context={}): + """Apply Hugo-style shortcodes on data. + + {{% name parameters %}} will end up calling the registered "name" function with the given parameters. + {{% name parameters %}} something {{% /name %}} will call name with the parameters and + one extra "data" parameter containing " something ". + + If raise_exceptions is set to True, instead of printing error messages and terminating, errors are + passed on as exceptions to the caller. + + The site parameter is passed with the same name to the shortcodes so they can access Nikola state. + + >>> print(apply_shortcodes('==> {{% foo bar=baz %}} <==', {'foo': lambda *a, **k: k['bar']})) + ==> baz <== + >>> print(apply_shortcodes('==> {{% foo bar=baz %}}some data{{% /foo %}} <==', {'foo': lambda *a, **k: k['bar']+k['data']})) + ==> bazsome data <== + """ + empty_string = data[:0] # same string type as data; to make Python 2 happy + try: + # Split input data into text, shortcodes and shortcode endings + sc_data = _split_shortcodes(data) + # Now process data + result = [] + dependencies = [] + pos = 0 + while pos < len(sc_data): + current = sc_data[pos] + if current[0] == _TEXT: + result.append(current[1]) + pos += 1 + elif current[0] == _SHORTCODE_END: + raise ParsingError("Found shortcode ending '{{{{% /{0} %}}}}' which isn't closing a started shortcode ({1})!".format(current[3], _format_position(data, current[2]))) + elif current[0] == _SHORTCODE_START: + name = current[3] + # Check if we can find corresponding ending + found = None + for p in range(pos + 1, len(sc_data)): + if sc_data[p][0] == _SHORTCODE_END and sc_data[p][3] == name: + found = p + break + if found: + # Found ending. Extract data argument: + data_arg = [] + for p in range(pos + 1, found): + data_arg.append(sc_data[p][1]) + data_arg = empty_string.join(data_arg) + pos = found + 1 + else: + # Single shortcode + pos += 1 + data_arg = '' + args, kw = current[4] + kw['site'] = site + kw['data'] = data_arg + kw['lang'] = lang + kw.update(extra_context) + if name in registry: + f = registry[name] + if getattr(f, 'nikola_shortcode_pass_filename', None): + kw['filename'] = filename + res = f(*args, **kw) + if not isinstance(res, tuple): # For backards compatibility + res = (res, []) + else: + LOGGER.error('Unknown shortcode {0} (started at {1})', name, _format_position(data, current[2])) + res = ('', []) + result.append(res[0]) + dependencies += res[1] + if with_dependencies: + return empty_string.join(result), dependencies + return empty_string.join(result) + except ParsingError as e: + if raise_exceptions: + # Throw up + raise e + if filename: + LOGGER.error("Shortcode error in file {0}: {1}".format(filename, e)) + else: + LOGGER.error("Shortcode error: {0}".format(e)) + sys.exit(1) diff --git a/nikola/state.py b/nikola/state.py new file mode 100644 index 0000000..6632e4f --- /dev/null +++ b/nikola/state.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- + +# Copyright © 2012-2016 Roberto Alsina and others. + +# Permission is hereby granted, free of charge, to any +# person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the +# Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the +# Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice +# shall be included in all copies or substantial portions of +# the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +"""Persistent state implementation.""" + +import json +import os +import shutil +import tempfile +import threading + +from . import utils + + +class Persistor(): + """Persist stuff in a place. + + This is an intentionally dumb implementation. It is *not* meant to be + fast, or useful for arbitrarily large data. Use lightly. + + Intentionally it has no namespaces, sections, etc. Use as a + responsible adult. + """ + + def __init__(self, path): + """Where do you want it persisted.""" + self._path = path + self._local = threading.local() + self._local.data = {} + + def _set_site(self, site): + """Set site and create path directory.""" + self._site = site + utils.makedirs(os.path.dirname(self._path)) + + def get(self, key): + """Get data stored in key.""" + self._read() + return self._local.data.get(key) + + def set(self, key, value): + """Store value in key.""" + self._read() + self._local.data[key] = value + self._save() + + def delete(self, key): + """Delete key and the value it contains.""" + self._read() + if key in self._local.data: + self._local.data.pop(key) + self._save() + + def _read(self): + if os.path.isfile(self._path): + with open(self._path) as inf: + self._local.data = json.load(inf) + + def _save(self): + dname = os.path.dirname(self._path) + with tempfile.NamedTemporaryFile(dir=dname, delete=False) as outf: + # TODO replace with encoding='utf-8' and mode 'w+' in v8 + tname = outf.name + data = json.dumps(self._local.data, sort_keys=True, indent=2) + try: + outf.write(data) + except TypeError: + outf.write(data.encode('utf-8')) + shutil.move(tname, self._path) diff --git a/nikola/utils.py b/nikola/utils.py index 3359514..068cb3a 100644 --- a/nikola/utils.py +++ b/nikola/utils.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# Copyright © 2012-2016 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -31,7 +31,6 @@ import calendar import datetime import dateutil.tz import hashlib -import husl import io import locale import logging @@ -56,6 +55,19 @@ except ImportError: from urllib.parse import urlparse, urlunparse # NOQA import warnings import PyRSS2Gen as rss +try: + import pytoml as toml +except ImportError: + toml = None +try: + import yaml +except ImportError: + yaml = None +try: + import husl +except ImportError: + husl = None + from collections import defaultdict, Callable, OrderedDict from logbook.compat import redirect_logging from logbook.more import ExceptionHandler, ColorizedStderrHandler @@ -69,7 +81,7 @@ from doit.cmdparse import CmdParse from nikola import DEBUG -__all__ = ('CustomEncoder', 'get_theme_path', 'get_theme_chain', 'load_messages', 'copy_tree', +__all__ = ('CustomEncoder', 'get_theme_path', 'get_theme_path_real', 'get_theme_chain', 'load_messages', 'copy_tree', 'copy_file', 'slugify', 'unslugify', 'to_datetime', 'apply_filters', 'config_changed', 'get_crumbs', 'get_tzname', 'get_asset_path', '_reload', 'unicode_str', 'bytes_str', 'unichr', 'Functionary', @@ -81,7 +93,8 @@ __all__ = ('CustomEncoder', 'get_theme_path', 'get_theme_chain', 'load_messages' 'adjust_name_for_index_path', 'adjust_name_for_index_link', 'NikolaPygmentsHTML', 'create_redirect', 'TreeNode', 'flatten_tree_structure', 'parse_escaped_hierarchical_category_name', - 'join_hierarchical_category_path', 'indent') + 'join_hierarchical_category_path', 'clean_before_deployment', 'indent', + 'load_data') # Are you looking for 'generic_rss_renderer'? # It's defined in nikola.nikola.Nikola (the site object). @@ -126,7 +139,7 @@ def get_logger(name, handlers): STDERR_HANDLER = [ColorfulStderrHandler( - level=logbook.NOTICE if not DEBUG else logbook.DEBUG, + level=logbook.INFO if not DEBUG else logbook.DEBUG, format_string=u'[{record.time:%Y-%m-%dT%H:%M:%SZ}] {record.level_name}: {record.channel}: {record.message}' )] @@ -302,7 +315,7 @@ class TranslatableSetting(object): self.overriden_default = False self.values = defaultdict() - if isinstance(inp, dict): + if isinstance(inp, dict) and inp: self.translated = True self.values.update(inp) if self.default_lang not in self.values.keys(): @@ -546,6 +559,8 @@ class config_changed(tools.config_changed): byte_data = data digest = hashlib.md5(byte_data).hexdigest() # LOGGER.debug('{{"{0}": {1}}}'.format(digest, byte_data)) + # Humanized format: + # LOGGER.debug('[Digest {0} for {2}]\n{1}\n[Digest {0} for {2}]'.format(digest, byte_data, self.identifier)) return digest else: raise Exception('Invalid type of config_changed parameter -- got ' @@ -570,24 +585,30 @@ class config_changed(tools.config_changed): sort_keys=True)) -def get_theme_path(theme, _themes_dir='themes'): +def get_theme_path_real(theme, themes_dirs): """Return the path where the given theme's files are located. Looks in ./themes and in the place where themes go when installed. """ - dir_name = os.path.join(_themes_dir, theme) - if os.path.isdir(dir_name): - return dir_name + for themes_dir in themes_dirs: + dir_name = os.path.join(themes_dir, theme) + if os.path.isdir(dir_name): + return dir_name dir_name = resource_filename('nikola', os.path.join('data', 'themes', theme)) if os.path.isdir(dir_name): return dir_name raise Exception("Can't find theme '{0}'".format(theme)) -def get_template_engine(themes, _themes_dir='themes'): +def get_theme_path(theme): + """Return the theme's path, which equals the theme's name.""" + return theme + + +def get_template_engine(themes): """Get template engine used by a given theme.""" for theme_name in themes: - engine_path = os.path.join(get_theme_path(theme_name, _themes_dir), 'engine') + engine_path = os.path.join(theme_name, 'engine') if os.path.isfile(engine_path): with open(engine_path) as fd: return fd.readlines()[0].strip() @@ -595,21 +616,24 @@ def get_template_engine(themes, _themes_dir='themes'): return 'mako' -def get_parent_theme_name(theme_name, _themes_dir='themes'): +def get_parent_theme_name(theme_name, themes_dirs=None): """Get name of parent theme.""" - parent_path = os.path.join(get_theme_path(theme_name, _themes_dir), 'parent') + parent_path = os.path.join(theme_name, 'parent') if os.path.isfile(parent_path): with open(parent_path) as fd: - return fd.readlines()[0].strip() + parent = fd.readlines()[0].strip() + if themes_dirs: + return get_theme_path_real(parent, themes_dirs) + return parent return None -def get_theme_chain(theme, _themes_dir='themes'): - """Create the full theme inheritance chain.""" - themes = [theme] +def get_theme_chain(theme, themes_dirs): + """Create the full theme inheritance chain including paths.""" + themes = [get_theme_path_real(theme, themes_dirs)] while True: - parent = get_parent_theme_name(themes[-1], _themes_dir) + parent = get_parent_theme_name(themes[-1], themes_dirs=themes_dirs) # Avoid silly loops if parent is None or parent in themes: break @@ -633,7 +657,7 @@ class LanguageNotFoundError(Exception): return 'cannot find language {0}'.format(self.lang) -def load_messages(themes, translations, default_lang): +def load_messages(themes, translations, default_lang, themes_dirs): """Load theme's messages into context. All the messages from parent themes are loaded, @@ -643,10 +667,12 @@ def load_messages(themes, translations, default_lang): oldpath = list(sys.path) for theme_name in themes[::-1]: msg_folder = os.path.join(get_theme_path(theme_name), 'messages') - default_folder = os.path.join(get_theme_path('base'), 'messages') + default_folder = os.path.join(get_theme_path_real('base', themes_dirs), 'messages') sys.path.insert(0, default_folder) sys.path.insert(0, msg_folder) english = __import__('messages_en') + # If we don't do the reload, the module is cached + _reload(english) for lang in list(translations.keys()): try: translation = __import__('messages_' + lang) @@ -665,6 +691,7 @@ def load_messages(themes, translations, default_lang): del(translation) except ImportError as orig: raise LanguageNotFoundError(lang, orig) + del(english) sys.path = oldpath return messages @@ -741,20 +768,22 @@ _slugify_strip_re = re.compile(r'[^+\w\s-]') _slugify_hyphenate_re = re.compile(r'[-\s]+') -def slugify(value, force=False): +def slugify(value, lang=None, force=False): u"""Normalize string, convert to lowercase, remove non-alpha characters, convert spaces to hyphens. From Django's "django/template/defaultfilters.py". - >>> print(slugify('áéí.óú')) + >>> print(slugify('áéí.óú', lang='en')) aeiou - >>> print(slugify('foo/bar')) + >>> print(slugify('foo/bar', lang='en')) foobar - >>> print(slugify('foo bar')) + >>> print(slugify('foo bar', lang='en')) foo-bar """ + if lang is None: # TODO: remove in v8 + LOGGER.warn("slugify() called without language!") if not isinstance(value, unicode_str): raise ValueError("Not a unicode object: {0}".format(value)) if USE_SLUGIFY or force: @@ -779,12 +808,14 @@ def slugify(value, force=False): return value -def unslugify(value, discard_numbers=True): +def unslugify(value, lang=None, discard_numbers=True): """Given a slug string (as a filename), return a human readable string. If discard_numbers is True, numbers right at the beginning of input will be removed. """ + if lang is None: # TODO: remove in v8 + LOGGER.warn("unslugify() called without language!") if discard_numbers: value = re.sub('^[0-9]+', '', value) value = re.sub('([_\-\.])', ' ', value) @@ -796,7 +827,7 @@ def encodelink(iri): """Given an encoded or unencoded link string, return an encoded string suitable for use as a link in HTML and XML.""" iri = unicodenormalize('NFC', iri) link = OrderedDict(urlparse(iri)._asdict()) - link['path'] = urlquote(urlunquote(link['path']).encode('utf-8')) + link['path'] = urlquote(urlunquote(link['path']).encode('utf-8'), safe="/~") try: link['netloc'] = link['netloc'].encode('utf-8').decode('idna').encode('idna').decode('utf-8') except UnicodeDecodeError: @@ -909,7 +940,7 @@ def apply_filters(task, filters, skip_ext=None): return task -def get_crumbs(path, is_file=False, index_folder=None): +def get_crumbs(path, is_file=False, index_folder=None, lang=None): """Create proper links for a crumb bar. index_folder is used if you want to use title from index file @@ -945,8 +976,10 @@ def get_crumbs(path, is_file=False, index_folder=None): for i, crumb in enumerate(crumbs[-3::-1]): # Up to parent folder only _path = '/'.join(['..'] * (i + 1)) _crumbs.append([_path, crumb]) - _crumbs.insert(0, ['.', crumbs[-2]]) # file's folder - _crumbs.insert(0, ['#', crumbs[-1]]) # file itself + if len(crumbs) >= 2: + _crumbs.insert(0, ['.', crumbs[-2]]) # file's folder + if len(crumbs) >= 1: + _crumbs.insert(0, ['#', crumbs[-1]]) # file itself else: for i, crumb in enumerate(crumbs[::-1]): _path = '/'.join(['..'] * i) or '#' @@ -962,12 +995,12 @@ def get_crumbs(path, is_file=False, index_folder=None): index_post = index_folder.parse_index(folder, '', '') folder = folder.replace(crumb, '') if index_post: - crumb = index_post.title() or crumb + crumb = index_post.title(lang) or crumb _crumbs[i][1] = crumb return list(reversed(_crumbs)) -def get_asset_path(path, themes, files_folders={'files': ''}, _themes_dir='themes', output_dir='output'): +def get_asset_path(path, themes, files_folders={'files': ''}, output_dir='output'): """Return the "real", absolute path to the asset. By default, it checks which theme provides the asset. @@ -976,27 +1009,24 @@ def get_asset_path(path, themes, files_folders={'files': ''}, _themes_dir='theme If it's not provided by either, it will be chacked in output, where it may have been created by another plugin. - >>> print(get_asset_path('assets/css/rst.css', ['bootstrap3', 'base'])) + >>> print(get_asset_path('assets/css/rst.css', get_theme_chain('bootstrap3', ['themes']))) /.../nikola/data/themes/base/assets/css/rst.css - >>> print(get_asset_path('assets/css/theme.css', ['bootstrap3', 'base'])) + >>> print(get_asset_path('assets/css/theme.css', get_theme_chain('bootstrap3', ['themes']))) /.../nikola/data/themes/bootstrap3/assets/css/theme.css - >>> print(get_asset_path('nikola.py', ['bootstrap3', 'base'], {'nikola': ''})) + >>> print(get_asset_path('nikola.py', get_theme_chain('bootstrap3', ['themes']), {'nikola': ''})) /.../nikola/nikola.py - >>> print(get_asset_path('nikola.py', ['bootstrap3', 'base'], {'nikola': 'nikola'})) + >>> print(get_asset_path('nikola.py', get_theme_chain('bootstrap3', ['themes']), {'nikola': 'nikola'})) None - >>> print(get_asset_path('nikola/nikola.py', ['bootstrap3', 'base'], {'nikola': 'nikola'})) + >>> print(get_asset_path('nikola/nikola.py', get_theme_chain('bootstrap3', ['themes']), {'nikola': 'nikola'})) /.../nikola/nikola.py """ for theme_name in themes: - candidate = os.path.join( - get_theme_path(theme_name, _themes_dir), - path - ) + candidate = os.path.join(get_theme_path(theme_name), path) if os.path.isfile(candidate): return candidate for src, rel_dst in files_folders.items(): @@ -1054,7 +1084,6 @@ class LocaleBorg(object): NOTE: never use locale.getlocale() , it can return values that locale.setlocale will not accept in Windows XP, 7 and pythons 2.6, 2.7, 3.3 Examples: "Spanish", "French" can't do the full circle set / get / set - That used to break calendar, but now seems is not the case, with month at least """ initialized = False @@ -1173,20 +1202,16 @@ class LocaleBorg(object): res = handler(month_no, lang) if res is not None: return res - if sys.version_info[0] == 3: # Python 3 - with calendar.different_locale(self.locales[lang]): - s = calendar.month_name[month_no] - # for py3 s is unicode - else: # Python 2 - with calendar.TimeEncoding(self.locales[lang]): - s = calendar.month_name[month_no] + old_lang = self.current_lang + self.__set_locale(lang) + s = calendar.month_name[month_no] + self.__set_locale(old_lang) + if sys.version_info[0] == 2: enc = self.encodings[lang] if not enc: enc = 'UTF-8' s = s.decode(enc) - # paranoid about calendar ending in the wrong locale (windows) - self.__set_locale(self.current_lang) return s def formatted_date(self, date_format, date): @@ -1294,9 +1319,9 @@ def demote_headers(doc, level=1): r = range(1 + level, 7) for i in reversed(r): # html headers go to 6, so we can’t “lower” beneath five - elements = doc.xpath('//h' + str(i)) - for e in elements: - e.tag = 'h' + str(i + level) + elements = doc.xpath('//h' + str(i)) + for e in elements: + e.tag = 'h' + str(i + level) def get_root_dir(): @@ -1337,10 +1362,10 @@ def get_translation_candidate(config, path, lang): cache/posts/fancy.post.html >>> print(get_translation_candidate(config, 'cache/posts/fancy.post.html', 'es')) cache/posts/fancy.post.es.html - >>> print(get_translation_candidate(config, 'cache/stories/charts.html', 'es')) - cache/stories/charts.es.html - >>> print(get_translation_candidate(config, 'cache/stories/charts.html', 'en')) - cache/stories/charts.html + >>> print(get_translation_candidate(config, 'cache/pages/charts.html', 'es')) + cache/pages/charts.es.html + >>> print(get_translation_candidate(config, 'cache/pages/charts.html', 'en')) + cache/pages/charts.html >>> config = {'TRANSLATIONS_PATTERN': '{path}.{ext}.{lang}', 'DEFAULT_LANG': 'en', 'TRANSLATIONS': {'es':'1', 'en': 1}} >>> print(get_translation_candidate(config, '*.rst', 'es')) @@ -1553,7 +1578,7 @@ class NikolaPygmentsHTML(HtmlFormatter): self.nclasses = classes super(NikolaPygmentsHTML, self).__init__( cssclass='code', linenos=linenos, linenostart=linenostart, nowrap=False, - lineanchors=slugify(anchor_ref, force=True), anchorlinenos=True) + lineanchors=slugify(anchor_ref, lang=LocaleBorg().current_lang, force=True), anchorlinenos=True) def wrap(self, source, outfile): """Wrap the ``source``, which is a generator yielding individual lines, in custom generators.""" @@ -1790,21 +1815,29 @@ def colorize_str_from_base_color(string, base_color): lightness and saturation untouched using HUSL colorspace. """ def hash_str(string, pos): - return hashlib.md5(string.encode('utf-8')).digest()[pos] + x = hashlib.md5(string.encode('utf-8')).digest()[pos] + try: + # Python 2: a string + # TODO: remove in v8 + return ord(x) + except TypeError: + # Python 3: already an integer + return x def degreediff(dega, degb): return min(abs(dega - degb), abs((degb - dega) + 360)) - def husl_similar_from_base(string, base_color): - h, s, l = husl.hex_to_husl(base_color) - old_h = h - idx = 0 - while degreediff(old_h, h) < 27 and idx < 16: - h = 360.0 * (float(hash_str(string, idx)) / 255) - idx += 1 - return husl.husl_to_hex(h, s, l) - - return husl_similar_from_base(string, base_color) + if husl is None: + req_missing(['husl'], 'Use color mixing (section colors)', + optional=True) + return base_color + h, s, l = husl.hex_to_husl(base_color) + old_h = h + idx = 0 + while degreediff(old_h, h) < 27 and idx < 16: + h = 360.0 * (float(hash_str(string, idx)) / 255) + idx += 1 + return husl.husl_to_hex(h, s, l) def color_hsl_adjust_hex(hexstr, adjust_h=None, adjust_s=None, adjust_l=None): @@ -1849,6 +1882,25 @@ def dns_sd(port, inet6): return None +def clean_before_deployment(site): + """Clean drafts and future posts before deployment.""" + undeployed_posts = [] + deploy_drafts = site.config.get('DEPLOY_DRAFTS', True) + deploy_future = site.config.get('DEPLOY_FUTURE', False) + if not (deploy_drafts and deploy_future): # == !drafts || !future + # Remove drafts and future posts + out_dir = site.config['OUTPUT_FOLDER'] + site.scan_posts() + for post in site.timeline: + if (not deploy_drafts and post.is_draft) or (not deploy_future and post.publish_later): + for lang in post.translated_to: + remove_file(os.path.join(out_dir, post.destination_path(lang))) + source_path = post.destination_path(lang, post.source_ext(True)) + remove_file(os.path.join(out_dir, source_path)) + undeployed_posts.append(post) + return undeployed_posts + + # Stolen from textwrap in Python 3.4.3. def indent(text, prefix, predicate=None): """Add 'prefix' to the beginning of selected lines in 'text'. @@ -1866,3 +1918,25 @@ def indent(text, prefix, predicate=None): for line in text.splitlines(True): yield (prefix + line if predicate(line) else line) return ''.join(prefixed_lines()) + + +def load_data(path): + """Given path to a file, load data from it.""" + ext = os.path.splitext(path)[-1] + loader = None + if ext in {'.yml', '.yaml'}: + loader = yaml + if yaml is None: + req_missing(['yaml'], 'use YAML data files') + return {} + elif ext in {'.json', '.js'}: + loader = json + elif ext in {'.toml', '.tml'}: + if toml is None: + req_missing(['toml'], 'use TOML data files') + return {} + loader = toml + if loader is None: + return + with io.open(path, 'r', encoding='utf8') as inf: + return loader.load(inf) diff --git a/nikola/winutils.py b/nikola/winutils.py index 3ea179b..6e341b8 100644 --- a/nikola/winutils.py +++ b/nikola/winutils.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2015 Roberto Alsina and others. +# Copyright © 2012-2016 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated diff --git a/requirements-extras.txt b/requirements-extras.txt index 9538af4..1345889 100644 --- a/requirements-extras.txt +++ b/requirements-extras.txt @@ -1,13 +1,15 @@ -r requirements.txt Markdown>=2.4.0 Jinja2>=2.7.2 +husl>=4.0.2 pyphen>=0.9.1 micawber>=0.3.0 pygal>=2.0.0 typogrify>=2.0.4 phpserialize>=1.3 webassets>=0.10.1 -ipython[notebook]>=2.0.0 +notebook>=4.0.0 +ipykernel>=4.0.0 ghp-import2>=1.0.0 -ws4py==0.3.4 +ws4py==0.3.5 watchdog==0.8.3 diff --git a/requirements-tests.txt b/requirements-tests.txt index ab2e541..69d4539 100644 --- a/requirements-tests.txt +++ b/requirements-tests.txt @@ -1,8 +1,8 @@ -r requirements-extras.txt -mock==1.3.0 -coverage==4.0.1 -pytest==2.8.2 -pytest-cov==2.2.0 -freezegun==0.3.5 -python-coveralls==2.5.0 -colorama==0.3.3 +mock==2.0.0 +coverage==4.2 +pytest==3.0.3 +pytest-cov==2.4.0 +freezegun==0.3.7 +python-coveralls==2.9.0 +colorama>=0.3.4 diff --git a/requirements.txt b/requirements.txt index 0b5fe4e..83b4a6c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ -doit>=0.28.0 +doit>=0.28.0,<=0.29.0 Pygments>=1.6 Pillow>=2.4.0 -python-dateutil==2.4.2 +python-dateutil>=2.4.0 docutils>=0.12 mako>=1.0.0 unidecode>=0.04.16 @@ -13,4 +13,4 @@ blinker>=1.3 setuptools>=5.4.1 natsort>=3.5.2 requests>=2.2.0 -husl>=4.0.2 +piexif>=1.0.3 diff --git a/scripts/baseline.sh b/scripts/baseline.sh new file mode 100755 index 0000000..2553a07 --- /dev/null +++ b/scripts/baseline.sh @@ -0,0 +1,33 @@ +#!/bin/bash +PYVER=$(scripts/getpyver.py short) +if [[ $PYVER == '3.5' || $PYVER == '2.7' ]]; then + if [[ "$1" == "check" ]]; then + echo -e "\033[36m>> Downloading baseline for $PYVER...\033[0m" + # we only support 2.7 and 3.5 + wget https://github.com/getnikola/invariant-builds/archive/v$PYVER'.zip' + unzip -q 'v'$PYVER'.zip' + rm -rf baseline$PYVER + mv invariant-builds-$PYVER baseline + rm 'v'$PYVER'.zip' + fi +else + echo -e "\033[35m>> Version $PYVER does not support baseline testing.\033[0m" + exit 0 +fi +nikola init -qd nikola-baseline-build +cd nikola-baseline-build +cp ../tests/data/1-nolinks.rst posts/1.rst +rm "pages/creating-a-theme.rst" "pages/extending.txt" "pages/internals.txt" "pages/manual.rst" "pages/social_buttons.txt" "pages/theming.rst" "pages/path_handlers.txt" "pages/charts.txt" +LC_ALL='en_US.UTF-8' PYTHONHASHSEED=0 nikola build --invariant +if [[ "$1" == "check" ]]; then + echo -e "\033[36m>> Testing baseline...\033[0m" + diff -ubwr ../baseline output + if [[ $? == 0 ]]; then + echo -e "\033[32;1m>> Baseline test successful\033[0m" + else + CODE=$? + echo -e "\033[31;1m>> Failed with exit code $CODE\033[0m" + echo "If this change was intentional, the baseline site needs to be rebuilt (maintainers only). Otherwise, please fix this issue." + exit $CODE + fi +fi diff --git a/scripts/document_path_handlers.py b/scripts/document_path_handlers.py index 692c8e0..4c2e378 100755 --- a/scripts/document_path_handlers.py +++ b/scripts/document_path_handlers.py @@ -9,8 +9,8 @@ print(""".. title: Path Handlers for Nikola .. slug: path-handlers .. author: The Nikola Team -Nikola supports special links with the syntax ``link://kind/name``. Here is -the description for all the supported kinds. +Nikola supports special links with the syntax ``link://kind/name``. In the templates you can also +use ``_link(kind, name)`` Here is the description for all the supported kinds. .. class:: dl-horizontal """) @@ -18,5 +18,5 @@ the description for all the supported kinds. for k in sorted(n.path_handlers.keys()): v = n.path_handlers[k] print(k) - print('\n'.join(' '+l.strip() for l in v.__doc__.splitlines())) + print('\n'.join(' ' + l.strip() for l in v.__doc__.splitlines())) print() diff --git a/scripts/generate_symlinked_list.sh b/scripts/generate_symlinked_list.sh index fe7a7cc..56f514b 100755 --- a/scripts/generate_symlinked_list.sh +++ b/scripts/generate_symlinked_list.sh @@ -6,4 +6,12 @@ WDir="${PWD##*/}" [[ $WDir == 'scripts' ]] && cd .. dst='nikola/data/symlinked.txt' + +# Remove stale symlinks +for f in $(git ls-files -s | awk '/120000/{print $4}'); do + if [[ ! -e $f ]]; then + git rm -f $f + fi +done + git ls-files -s | awk '/120000/{print $4}' > $dst diff --git a/scripts/getbaseline.sh b/scripts/getbaseline.sh deleted file mode 100755 index 9006579..0000000 --- a/scripts/getbaseline.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash -cd tests/data -for i in $@; do - if [[ $i == '3.5' || $i == '2.7' ]]; then - # we only support 2.7 and 3.5 - wget https://github.com/getnikola/invariant-builds/archive/v$i'.zip' - unzip 'v'$i'.zip' - rm -rf baseline$i - mv invariant-builds-$i baseline$i - rm 'v'$i'.zip' - else - echo 'Version '$i' does not support baseline testing.' - fi -done diff --git a/scripts/github-release.py b/scripts/github-release.py index 8280fda..a9ab898 100755 --- a/scripts/github-release.py +++ b/scripts/github-release.py @@ -1,7 +1,7 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import subprocess -import sys import os +import argparse if not os.path.exists('.pypt/gh-token'): print("To use this script, you must create a GitHub token first.") @@ -9,11 +9,19 @@ if not os.path.exists('.pypt/gh-token'): print("Then, put it in a file named .pypt/gh-token") exit(1) -inpf = input if sys.version_info[0] == 3 else raw_input +parser = argparse.ArgumentParser(description="GitHub Release helper") +parser.add_argument("FILE", nargs=1, help="Markdown file to use") +parser.add_argument("TAG", nargs=1, help="Tag name (usually vX.Y.Z)") + +args = parser.parse_args() + +if not args.TAG[0].startswith("v"): + print("WARNING: tag should start with v") + i = input("Add `v` to tag? [y/n] ") + if i.lower().strip().startswith('y'): + args.TAG[0] = 'v' + args.TAG[0] -FILE = inpf("Markdown file to use: ") BASEDIR = os.getcwd() REPO = 'getnikola/nikola' -TAG = inpf("Tag name (usually vX.Y.Z): ") -subprocess.call(['.pypt/ghrel', FILE, BASEDIR, REPO, TAG]) +subprocess.call(['.pypt/ghrel', args.FILE[0], BASEDIR, REPO, args.TAG[0]]) diff --git a/scripts/jinjify.py b/scripts/jinjify.py index 3a3a9d8..8f14323 100755 --- a/scripts/jinjify.py +++ b/scripts/jinjify.py @@ -6,6 +6,7 @@ import os import re import json import shutil +import tempfile import colorama import jinja2 @@ -216,6 +217,24 @@ def mako2jinja(input_file): return output + +def jinjify_shortcodes(in_dir, out_dir): + for fname in os.listdir(in_dir): + if not fname.endswith('.tmpl'): + continue + in_file = os.path.join(in_dir, fname) + out_file = os.path.join(out_dir, fname) + with open(in_file) as inf: + data = mako2jinja(inf) + with open(out_file, 'w') as outf: + outf.write(data) + + +def usage(): + print("Usage: python {} [in-dir] [out-dir]".format(sys.argv[0])) + print("OR") + print("Usage: python {} [in-file] [out-file]".format(sys.argv[0])) + if __name__ == "__main__": if len(sys.argv) == 1: print('Performing standard conversions:') @@ -225,7 +244,20 @@ if __name__ == "__main__": ): print(' {0} -> {1}'.format(m, j)) jinjify(m, j) + jinjify_shortcodes('nikola/data/shortcodes/mako', 'nikola/data/shortcodes/jinja') elif len(sys.argv) != 3: - print('ERROR: needs input and output directory, or no arguments for default conversions.') - else: + print('ERROR: needs input and output directory (file), or no arguments for default conversions.') + usage() + elif os.path.isdir(sys.argv[1]) and (os.path.isdir(sys.argv[2]) or not os.path.exists(sys.argv[2])): jinjify(sys.argv[1], sys.argv[2]) + elif os.path.isfile(sys.argv[1]) and (os.path.isfile(sys.argv[2]) or not os.path.exists(sys.argv[2])): + tmpdir = tempfile.mkdtemp() + indir = os.path.sep.join((tmpdir, 'in', 'templates')) + outdir = os.path.sep.join((tmpdir, 'out', 'templates')) + os.makedirs(indir) + shutil.copy(sys.argv[1], indir) + jinjify(os.path.dirname(indir), os.path.dirname(outdir)) + shutil.move(os.path.sep.join((outdir, os.path.basename(sys.argv[1]))), sys.argv[2]) + else: + print('ERROR: the two arguments must be both directories or files') + usage() diff --git a/scripts/langstatus.py b/scripts/langstatus.py new file mode 100755 index 0000000..c31067d --- /dev/null +++ b/scripts/langstatus.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +import nikola.nikola +import os.path +import glob + +used = [] +exist = [] +for tr in nikola.nikola.LEGAL_VALUES['TRANSLATIONS']: + if isinstance(tr, tuple): + used.append(tr[0]) + used.append(tr) + +for file in glob.glob(os.path.join('nikola', 'data', 'themes', 'base', + 'messages', 'messages_*.py')): + lang = file.split('_', 1)[1][:-3] + exist.append(lang) + if lang in used: + print('{0}: found'.format(lang)) + elif os.path.islink(file): + print('\x1b[1;1m\x1b[1;30m{0}: symlink\x1b[0m'.format(lang)) + else: + print('\x1b[1;1m\x1b[1;31m{0}: NOT found\x1b[0m'.format(lang)) diff --git a/scripts/release b/scripts/release new file mode 100755 index 0000000..ce7b49e --- /dev/null +++ b/scripts/release @@ -0,0 +1,159 @@ +#!/bin/zsh +# The Release Script +# Based on Python Project Template by Chris Warrick +# Copyright © 2013-2016, Chris Warrick. +# All rights reserved. + +# Permission is hereby granted, free of charge, to any +# person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the +# Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the +# Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice +# shall be included in all copies or substantial portions of +# the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +function status { + echo $@ +} + +function warning { + echo 'WARNING: '$@ +} + +function error { + echo 'ERROR: '$@ +} + +function cleanup { + rm -rf Nikola.egg-info build || true + rm -rf **/__pycache__ || true +} + +status '*** Nikola Release Scripts' +dates=$(date '+%s') + +# Make sure we're up-to-date +git checkout master +git pull origin master + +echo -n "Version (in format X.Y.Z): " +read version + +echo $version | grep '^[0-9]\{1,\}\.[0-9]\{1,\}\.[0-9]\{1,\}$' > /dev/null + +if [[ $? != 0 ]]; then + echo $version | grep '^[0-9]\{1,\}\.[0-9]\{1,\}\.[0-9]\{1,\}\-[0-9A-Za-z-]\{1,\}$' > /dev/null + if [[ $? != 0 ]]; then + warning 'version number is not compliant with versioning scheme (Semantic Versioning 2.0)' + echo -n 'Continue? [y/N] ' + read vercont + if [[ $vercont == 'y' || $vercont == 'Y' ]]; then + echo 'Continuing.' + else + exit 2 + fi + else + status 'NOTICE: pre-release version number in use.' + echo -n 'Continue? [Y/n] ' + read vercont + if [[ $vercont == 'n' || $vercont == 'N' ]]; then + exit 2 + else + echo 'Continuing.' + fi + fi +fi + + +cleanup + +status 'Replacing versions in files...' + +sed "s/version=.*/version='$version',/g" setup.py -i +sed "s/version = .*/version = '$version'/g" docs/sphinx/conf.py -i +sed "s/release = .*/release = '$version'/g" docs/sphinx/conf.py -i +sed "s/:Version: .*/:Version: $version/g" docs/*.txt docs/*.rst -i +sed "s/:Version: .*/:Version: Nikola $version/g" docs/man/nikola.rst -i +sed "s/__version__ = .*/__version__ = '$version'/g" nikola/__init__.py -i +sed "s/version: .*/version: $version/g" snapcraft/stable/snapcraft.yaml -i +sed "s/source-tag: .*/source-tag: v$version/g" snapcraft/stable/snapcraft.yaml -i +sed "s/Nikola==.*'/Nikola==$version'/g" snapcraft/nikola.py -i + +setopt errexit # Exit on errors + +# Slightly convoluted underline automation +underline=$(python -c "import sys; sys.stdout.write((len('v$version')) * '=')") +perl -0777 -i -pe "s/master\n======/v$version\n$underline/" CHANGES.txt +# Man pages +rst2man.py docs/man/nikola.rst > docs/man/nikola.1 +gzip -f docs/man/nikola.1 + +status 'Updating path handler documentation...' +scripts/document_path_handlers.py > docs/path_handlers.txt +status 'Updating Jinja2 templates...' +scripts/jinjify.py +status 'Updating Bower...' +scripts/update-bower.sh +status 'Updating symlinked list...' +scripts/generate_symlinked_list.sh +status 'Updating website (conf.py and docs)...' +scripts/generate_conf.py > ../nikola-site/listings/conf.py +cp AUTHORS.txt CHANGES.txt ../nikola-site/stories +cp docs/*.* ../nikola-site/stories +sed 's/Nikola v[0-9\.A_Za-z-]\{1,\}/Nikola'" v$version/g" ../nikola-site/stories/conf.txt -i +status 'Generating locales...' +scripts/import_po.py +status 'List of locales:' +scripts/langstatus.py + +unsetopt errexit + +status 'If any locales are shown as "NOT found" and they have full translations, please add them in nikola.py. Check this in another terminal (or not) and press Enter to continue.' +read localefix + +status 'Importing...' +python -c "import nikola" +if [[ $? == 1 ]]; then + error "Import failed. Fix your code or don't come back." + exit 1 +fi + +status 'This is the last chance to quit. Hit ^C now if you want to.' +read bailout + +cleanup + +./setup.py sdist bdist_wheel || exit $? +twine upload -s dist/Nikola-$version.tar.gz dist/Nikola-$version*.whl || exit $? + +cleanup + +git add -A --ignore-errors . || exit $? + +git commit -S -asm "Version $version" || exit $? +git tag -sm "Version $version" v$version || exit $? +git push --follow-tags origin master || exit $? + +status "Done!" + +echo "Next steps (see Release Checklist):" +echo " * Write announcements, create blog posts" +echo " * Create a GitHub release" +echo " * Update GitHub Issues milestones" +echo " * Update Nikola’s website" +echo " * Send out announcement e-mails" +echo " * Update AUR PKGBUILDs" diff --git a/scripts/set_version.py b/scripts/set_version.py deleted file mode 100755 index 7e6c3e0..0000000 --- a/scripts/set_version.py +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -"""Script to set the version number wherever it's needed before a release.""" - -from __future__ import unicode_literals, print_function -import io -import os -import re -import sys -import glob -import subprocess -import io - - -def sed_like_thing(pattern, repl, path): - """Like re.sub but applies to a file instead of a string.""" - - with io.open(path, 'r', encoding='utf8') as inf: - data = inf.read() - - data = re.sub(pattern, repl, data) - - with io.open(path, 'w+', encoding='utf8') as outf: - outf.write(data) - -if __name__ == "__main__": - inpf = raw_input if sys.version_info[0] == 2 else input - version = inpf("New version number (in format X.Y.Z): ").strip() - - for doc in glob.glob(os.path.join("docs/*.txt")): - sed_like_thing(":Version: .*", ":Version: {0}".format(version), doc) - - sed_like_thing("version='.+'", "version='{0}'".format(version), 'setup.py') - sed_like_thing("version = .*", "version = '{0}'".format(version), os.path.join('docs', 'sphinx', 'conf.py')) - sed_like_thing("release = .*", "release = '{0}'".format(version), os.path.join('docs', 'sphinx', 'conf.py')) - sed_like_thing('__version__ = ".*"', '__version__ = "{0}"'.format(version), os.path.join('nikola', '__init__.py')) - sed_like_thing('New in master', 'New in v{0}'.format(version), 'CHANGES.txt') - sed_like_thing(':Version: .*', ':Version: Nikola v{0}'.format(version), os.path.join('docs', 'man', 'nikola.rst')) - man = subprocess.check_output(["rst2man", os.path.join('docs', 'man', 'nikola.rst')]) - with io.open(os.path.join('docs', 'man', 'nikola.1'), 'w', encoding='utf-8') as fh: - try: - man = man.decode('utf-8') - except AttributeError: - pass - fh.write(man) - subprocess.call(["gzip", "-f", os.path.join('docs', 'man', 'nikola.1')]) diff --git a/setup.cfg b/setup.cfg index 5995929..8386242 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,3 +3,7 @@ universal = 1 [flake8] ignore=E501 + +[tool:pytest] +norecursedirs = .git +addopts = --cov nikola --cov-report term-missing diff --git a/setup.py b/setup.py index dd2cf12..64b5e9d 100755 --- a/setup.py +++ b/setup.py @@ -3,7 +3,6 @@ # Don't use __future__ in this script, it breaks buildout # from __future__ import print_function import os -import subprocess import sys import shutil from setuptools import setup, find_packages @@ -75,8 +74,8 @@ def expands_symlinks_for_windows(): path to the file it points to. If not corrected, installing from a git clone will end with some files with bad content - After install the working copy will be dirty (symlink markers overwritten with - real content) + After install the working copy will be dirty (symlink markers overwritten + with real content) """ if sys.platform != 'win32': return @@ -90,7 +89,8 @@ def expands_symlinks_for_windows(): sys.path = oldpath del sys.modules['winutils'] if failures != -1: - print('WARNING: your working copy is now dirty by changes in samplesite, sphinx and themes') + print('WARNING: your working copy is now dirty by changes in ' + 'samplesite, sphinx and themes') if failures > 0: raise Exception("Error: \n\tnot all symlinked files could be fixed." + "\n\tYour best bet is to start again from clean.") @@ -112,7 +112,7 @@ class nikola_install(install): setup(name='Nikola', - version='7.7.3', + version='7.8.1', description='A modular, fast, simple, static website and blog generator', long_description=open('README.rst').read(), author='Roberto Alsina and others', @@ -128,6 +128,7 @@ setup(name='Nikola', 'Intended Audience :: End Users/Desktop', 'License :: OSI Approved :: MIT License', 'Operating System :: MacOS', + 'Operating System :: MacOS :: MacOS X', 'Operating System :: Microsoft :: Windows', 'Operating System :: OS Independent', 'Operating System :: POSIX', @@ -152,7 +153,7 @@ setup(name='Nikola', 'docs/extending.txt']), ('share/man/man1', ['docs/man/nikola.1.gz']), ], - entry_points = { + entry_points={ 'console_scripts': [ 'nikola = nikola.__main__:main' ] diff --git a/snapcraft/README b/snapcraft/README new file mode 100644 index 0000000..d026ae8 --- /dev/null +++ b/snapcraft/README @@ -0,0 +1,25 @@ +The folders edge/stable contain the files needed to build a snap package for Nikola. +Edge will build from master, stable from the latest release. + +To build, in Ubuntu 16.04: + +apt install snapd snapcraft +snap login some.account@whatever.com +./build.sh + +To just use it: + +apt install snapd +snap login some.account@whatever.com +snap install nikola + + +That should leave you with a working "nikola" command (/snap/bin/nikola if /snap/bin is not in your $PATH), +which is functional with the following caveats: + +* Locales are a bit broken: https://bugs.launchpad.net/ubuntu/+source/snapd/+bug/1576411 +* The -b option in nikola serve and nikola auto will not work (need to use snap-xdg-open it seems) +* Plugin installation is tricky +* Your site needs to live in your home +* You can't use "nikola deploy". +* Probaby other things I have not noticed yet. diff --git a/snapcraft/build.sh b/snapcraft/build.sh new file mode 100755 index 0000000..d0148d9 --- /dev/null +++ b/snapcraft/build.sh @@ -0,0 +1,6 @@ +#!/bin/sh +snapcraft +cp nikola.py stage/usr/bin/nikola +find prime/ -name '*.a' -exec rm {} \; +snapcraft + diff --git a/snapcraft/edge/build.sh b/snapcraft/edge/build.sh new file mode 100755 index 0000000..fa6dc14 --- /dev/null +++ b/snapcraft/edge/build.sh @@ -0,0 +1,6 @@ +#!/bin/sh +snapcraft +cp ../nikola.py prime/usr/bin/nikola +find prime/ -name '*.a' -exec rm {} \; +snapcraft + diff --git a/snapcraft/edge/nikola.png b/snapcraft/edge/nikola.png new file mode 100644 index 0000000..ce583bf Binary files /dev/null and b/snapcraft/edge/nikola.png differ diff --git a/snapcraft/edge/nikola.sh b/snapcraft/edge/nikola.sh new file mode 100755 index 0000000..82da543 --- /dev/null +++ b/snapcraft/edge/nikola.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +export HOME=$SNAP_USER_DATA + +export I18NPATH=$SNAP/usr/share/i18n +export LOCPATH=$SNAP_USER_DATA + +APPLANG=en_US +APPENC=UTF-8 +APPLOC="$APPLANG.$APPENC" + +# generate a locale so we get properly working charsets and graphics +if [ ! -e $SNAP_USER_DATA/$APPLOC ]; then + localedef --prefix=$SNAP_USER_DATA -f $APPENC -i $APPLANG $SNAP_USER_DATA/$APPLOC +fi + +export LC_ALL=$APPLOC +export LANG=$APPLOC +export LANGUAGE=${APPLANG%_*} + +$SNAP/usr/bin/nikola "$@" diff --git a/snapcraft/edge/release.sh b/snapcraft/edge/release.sh new file mode 100755 index 0000000..f05935e --- /dev/null +++ b/snapcraft/edge/release.sh @@ -0,0 +1 @@ +snapcraft push nikola_master_amd64.snap --release edge diff --git a/snapcraft/edge/requirements.txt b/snapcraft/edge/requirements.txt new file mode 100644 index 0000000..063f588 --- /dev/null +++ b/snapcraft/edge/requirements.txt @@ -0,0 +1,27 @@ +Markdown>=2.4.0 +Jinja2>=2.7.2 +pyphen>=0.9.1 +micawber>=0.3.0 +pygal>=2.0.0 +typogrify>=2.0.4 +phpserialize>=1.3 +webassets>=0.10.1 +ghp-import2>=1.0.0 +ws4py==0.3.5 +watchdog==0.8.3 +doit>=0.28.0,<=0.29.0 +Pygments>=1.6 +python-dateutil>=2.4.0 +docutils>=0.12 +mako>=1.0.0 +unidecode>=0.04.16 +lxml>=3.3.5 +Yapsy>=1.11.223 +PyRSS2Gen>=1.1 +logbook>=0.7.0 +blinker>=1.3 +setuptools>=5.4.1 +natsort>=3.5.2 +requests>=2.2.0 +husl>=4.0.2 +piexif>=1.0.3 diff --git a/snapcraft/edge/snapcraft.yaml b/snapcraft/edge/snapcraft.yaml new file mode 100644 index 0000000..a9d31dd --- /dev/null +++ b/snapcraft/edge/snapcraft.yaml @@ -0,0 +1,32 @@ +name: nikola +version: master +summary: A static website generator +description: A static website generator +confinement: strict + +apps: + nikola: + command: nikola.sh + plugs: [network, network-bind, home] + +parts: + nikola: + plugin: copy + files: + nikola.sh: nikola.sh + nikola-source: + plugin: python3 + source: git://github.com/getnikola/nikola.git + requirements: requirements.txt + stage-packages: + - locales + - libc-bin + - python3-lxml + - python3-pil + build-packages: + - zlib1g-dev + - libjpeg-turbo8-dev + - libpng12-dev + - libxslt1-dev + - libxml2-dev + - gcc diff --git a/snapcraft/nikola.png b/snapcraft/nikola.png new file mode 100644 index 0000000..ce583bf Binary files /dev/null and b/snapcraft/nikola.png differ diff --git a/snapcraft/nikola.py b/snapcraft/nikola.py new file mode 100755 index 0000000..624fcd3 --- /dev/null +++ b/snapcraft/nikola.py @@ -0,0 +1,10 @@ +#!/snap/nikola/current/usr/bin/python3 +# EASY-INSTALL-ENTRY-SCRIPT: 'Nikola==7.8.1' +__requires__ = 'Nikola==7.8.1' +import sys +from pkg_resources import load_entry_point + +if __name__ == '__main__': + sys.exit( + load_entry_point('Nikola', 'console_scripts', 'nikola')() + ) diff --git a/snapcraft/nikola.sh b/snapcraft/nikola.sh new file mode 100755 index 0000000..82da543 --- /dev/null +++ b/snapcraft/nikola.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +export HOME=$SNAP_USER_DATA + +export I18NPATH=$SNAP/usr/share/i18n +export LOCPATH=$SNAP_USER_DATA + +APPLANG=en_US +APPENC=UTF-8 +APPLOC="$APPLANG.$APPENC" + +# generate a locale so we get properly working charsets and graphics +if [ ! -e $SNAP_USER_DATA/$APPLOC ]; then + localedef --prefix=$SNAP_USER_DATA -f $APPENC -i $APPLANG $SNAP_USER_DATA/$APPLOC +fi + +export LC_ALL=$APPLOC +export LANG=$APPLOC +export LANGUAGE=${APPLANG%_*} + +$SNAP/usr/bin/nikola "$@" diff --git a/snapcraft/requirements.txt b/snapcraft/requirements.txt new file mode 100644 index 0000000..063f588 --- /dev/null +++ b/snapcraft/requirements.txt @@ -0,0 +1,27 @@ +Markdown>=2.4.0 +Jinja2>=2.7.2 +pyphen>=0.9.1 +micawber>=0.3.0 +pygal>=2.0.0 +typogrify>=2.0.4 +phpserialize>=1.3 +webassets>=0.10.1 +ghp-import2>=1.0.0 +ws4py==0.3.5 +watchdog==0.8.3 +doit>=0.28.0,<=0.29.0 +Pygments>=1.6 +python-dateutil>=2.4.0 +docutils>=0.12 +mako>=1.0.0 +unidecode>=0.04.16 +lxml>=3.3.5 +Yapsy>=1.11.223 +PyRSS2Gen>=1.1 +logbook>=0.7.0 +blinker>=1.3 +setuptools>=5.4.1 +natsort>=3.5.2 +requests>=2.2.0 +husl>=4.0.2 +piexif>=1.0.3 diff --git a/snapcraft/snapcraft.yaml b/snapcraft/snapcraft.yaml new file mode 100644 index 0000000..d149c79 --- /dev/null +++ b/snapcraft/snapcraft.yaml @@ -0,0 +1,33 @@ +name: nikola +version: 7.7.12 +summary: A static website generator +description: A static website generator +confinement: strict + +apps: + nikola: + command: nikola.sh + plugs: [network, network-bind, home] + +parts: + nikola: + plugin: copy + files: + nikola.sh: nikola.sh + nikola-source: + plugin: python3 + source: git://github.com/getnikola/nikola.git + source-tag: v7.7.12 + requirements: requirements.txt + stage-packages: + - locales + - libc-bin + - python3-lxml + - python3-pil + build-packages: + - zlib1g-dev + - libjpeg-turbo8-dev + - libpng12-dev + - libxslt1-dev + - libxml2-dev + - gcc diff --git a/snapcraft/stable/build.sh b/snapcraft/stable/build.sh new file mode 100755 index 0000000..fa6dc14 --- /dev/null +++ b/snapcraft/stable/build.sh @@ -0,0 +1,6 @@ +#!/bin/sh +snapcraft +cp ../nikola.py prime/usr/bin/nikola +find prime/ -name '*.a' -exec rm {} \; +snapcraft + diff --git a/snapcraft/stable/nikola.png b/snapcraft/stable/nikola.png new file mode 100644 index 0000000..ce583bf Binary files /dev/null and b/snapcraft/stable/nikola.png differ diff --git a/snapcraft/stable/nikola.sh b/snapcraft/stable/nikola.sh new file mode 100755 index 0000000..82da543 --- /dev/null +++ b/snapcraft/stable/nikola.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +export HOME=$SNAP_USER_DATA + +export I18NPATH=$SNAP/usr/share/i18n +export LOCPATH=$SNAP_USER_DATA + +APPLANG=en_US +APPENC=UTF-8 +APPLOC="$APPLANG.$APPENC" + +# generate a locale so we get properly working charsets and graphics +if [ ! -e $SNAP_USER_DATA/$APPLOC ]; then + localedef --prefix=$SNAP_USER_DATA -f $APPENC -i $APPLANG $SNAP_USER_DATA/$APPLOC +fi + +export LC_ALL=$APPLOC +export LANG=$APPLOC +export LANGUAGE=${APPLANG%_*} + +$SNAP/usr/bin/nikola "$@" diff --git a/snapcraft/stable/release.sh b/snapcraft/stable/release.sh new file mode 100755 index 0000000..332b426 --- /dev/null +++ b/snapcraft/stable/release.sh @@ -0,0 +1 @@ +snapcraft push nikola_*_amd64.snap --release stable,beta,candidate diff --git a/snapcraft/stable/requirements.txt b/snapcraft/stable/requirements.txt new file mode 100644 index 0000000..063f588 --- /dev/null +++ b/snapcraft/stable/requirements.txt @@ -0,0 +1,27 @@ +Markdown>=2.4.0 +Jinja2>=2.7.2 +pyphen>=0.9.1 +micawber>=0.3.0 +pygal>=2.0.0 +typogrify>=2.0.4 +phpserialize>=1.3 +webassets>=0.10.1 +ghp-import2>=1.0.0 +ws4py==0.3.5 +watchdog==0.8.3 +doit>=0.28.0,<=0.29.0 +Pygments>=1.6 +python-dateutil>=2.4.0 +docutils>=0.12 +mako>=1.0.0 +unidecode>=0.04.16 +lxml>=3.3.5 +Yapsy>=1.11.223 +PyRSS2Gen>=1.1 +logbook>=0.7.0 +blinker>=1.3 +setuptools>=5.4.1 +natsort>=3.5.2 +requests>=2.2.0 +husl>=4.0.2 +piexif>=1.0.3 diff --git a/snapcraft/stable/snapcraft.yaml b/snapcraft/stable/snapcraft.yaml new file mode 100644 index 0000000..e85e88b --- /dev/null +++ b/snapcraft/stable/snapcraft.yaml @@ -0,0 +1,33 @@ +name: nikola +version: 7.8.1 +summary: A static website generator +description: A static website generator +confinement: strict + +apps: + nikola: + command: nikola.sh + plugs: [network, network-bind, home] + +parts: + nikola: + plugin: copy + files: + nikola.sh: nikola.sh + nikola-source: + plugin: python3 + source: git://github.com/getnikola/nikola.git + source-tag: v7.8.1 + requirements: requirements.txt + stage-packages: + - locales + - libc-bin + - python3-lxml + - python3-pil + build-packages: + - zlib1g-dev + - libjpeg-turbo8-dev + - libpng12-dev + - libxslt1-dev + - libxml2-dev + - gcc diff --git a/tests/base.py b/tests/base.py index 14af18a..2322c15 100644 --- a/tests/base.py +++ b/tests/base.py @@ -20,6 +20,7 @@ import unittest import logbook import nikola.utils +import nikola.shortcodes nikola.utils.LOGGER.handlers.append(logbook.TestHandler()) from yapsy.PluginManager import PluginManager @@ -35,51 +36,7 @@ from nikola.plugin_categories import ( RestExtension ) - -if sys.version_info < (2, 7): - - try: - import unittest2 - _unittest2 = True - except ImportError: - _unittest2 = False - - if _unittest2: - BaseTestCase = unittest2.TestCase - - else: - - class BaseTestCase(unittest.TestCase): - """ Base class for providing 2.6 compatibility """ - - def assertIs(self, first, second, msg=None): - self.assertTrue(first is second) - - def assertIsNot(self, first, second, msg=None): - self.assertTrue(first is not second) - - def assertIsNone(self, expr, msg=None): - self.assertTrue(expr is None) - - def assertIsNotNone(self, expr, msg=None): - self.assertTrue(expr is not None) - - def assertIn(self, first, second, msg=None): - self.assertTrue(first in second) - - def assertNotIn(self, first, second, msg=None): - self.assertTrue(first not in second) - - def assertIsInstance(self, obj, cls, msg=None): - self.assertTrue(isinstance(obj, cls)) - - def assertNotIsInstance(self, obj, cls, msg=None): - self.assertFalse(isinstance(obj, cls)) - - -else: - BaseTestCase = unittest.TestCase - +BaseTestCase = unittest.TestCase @contextmanager def cd(path): @@ -183,6 +140,8 @@ class FakePost(object): self._title = title self._slug = slug self._meta = {'slug': slug} + self.default_lang = 'en' + self._depfile = {} def title(self): return self._title @@ -219,6 +178,7 @@ class FakeSite(object): "RestExtension": RestExtension }) self.loghandlers = nikola.utils.STDERR_HANDLER # TODO remove on v8 + self.shortcode_registry = {} self.plugin_manager.setPluginInfoExtension('plugin') if sys.version_info[0] == 3: places = [ @@ -238,6 +198,7 @@ class FakeSite(object): ] self.debug = True self.rst_transforms = [] + self.post_per_input_file = {} # This is to make plugin initialization happy self.template_system = self self.name = 'mako' @@ -257,3 +218,15 @@ class FakeSite(object): def render_template(self, name, _, context): return('') + + # this code duplicated in nikola/nikola.py + def register_shortcode(self, name, f): + """Register function f to handle shortcode "name".""" + if name in self.shortcode_registry: + nikola.utils.LOGGER.warn('Shortcode name conflict: %s', name) + return + self.shortcode_registry[name] = f + + def apply_shortcodes(self, data, *a, **kw): + """Apply shortcodes from the registry on data.""" + return nikola.shortcodes.apply_shortcodes(data, self.shortcode_registry, **kw) diff --git a/tests/conftest.py b/tests/conftest.py index fbb09c8..68309d5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,7 @@ import os import pytest + @pytest.yield_fixture(autouse=True) def ensure_chdir(): x = os.getcwd() diff --git a/tests/data/translated_titles/conf.py b/tests/data/translated_titles/conf.py index 3d0d829..8faa7a9 100644 --- a/tests/data/translated_titles/conf.py +++ b/tests/data/translated_titles/conf.py @@ -141,8 +141,8 @@ POSTS = ( ("posts/*.txt", "posts", "post.tmpl"), ) PAGES = ( - ("stories/*.rst", "stories", "story.tmpl"), - ("stories/*.txt", "stories", "story.tmpl"), + ("pages/*.rst", "pages", "story.tmpl"), + ("pages/*.txt", "pages", "story.tmpl"), ) # One or more folders containing files to be copied as-is into the output. @@ -436,10 +436,10 @@ COMMENT_SYSTEM_ID = "nikolademo" # the "noannotations" metadata. # ANNOTATIONS = False -# Create index.html for story folders? -# STORY_INDEX = False -# Enable comments on story pages? -# COMMENTS_IN_STORIES = False +# Create index.html for page folders? +# PAGE_INDEX = False +# Enable comments on page pages? +# COMMENTS_IN_PAGES = False # Enable comments on picture gallery pages? # COMMENTS_IN_GALLERIES = False diff --git a/tests/data/translated_titles/pages/1.pl.txt b/tests/data/translated_titles/pages/1.pl.txt new file mode 100644 index 0000000..a888c1f --- /dev/null +++ b/tests/data/translated_titles/pages/1.pl.txt @@ -0,0 +1,4 @@ +.. title: Bar +.. slug: 1 + +Bar diff --git a/tests/data/translated_titles/pages/1.txt b/tests/data/translated_titles/pages/1.txt new file mode 100644 index 0000000..45fb214 --- /dev/null +++ b/tests/data/translated_titles/pages/1.txt @@ -0,0 +1,5 @@ +.. title: Foo +.. slug: 1 +.. date: 2001/01/01 00:00:00 + +Foo diff --git a/tests/data/translated_titles/stories/1.pl.txt b/tests/data/translated_titles/stories/1.pl.txt deleted file mode 100644 index a888c1f..0000000 --- a/tests/data/translated_titles/stories/1.pl.txt +++ /dev/null @@ -1,4 +0,0 @@ -.. title: Bar -.. slug: 1 - -Bar diff --git a/tests/data/translated_titles/stories/1.txt b/tests/data/translated_titles/stories/1.txt deleted file mode 100644 index 45fb214..0000000 --- a/tests/data/translated_titles/stories/1.txt +++ /dev/null @@ -1,5 +0,0 @@ -.. title: Foo -.. slug: 1 -.. date: 2001/01/01 00:00:00 - -Foo diff --git a/tests/test_command_import_wordpress.py b/tests/test_command_import_wordpress.py index d424162..c05188f 100644 --- a/tests/test_command_import_wordpress.py +++ b/tests/test_command_import_wordpress.py @@ -15,6 +15,7 @@ class BasicCommandImportWordpress(BaseTestCase): def setUp(self): self.module = nikola.plugins.command.import_wordpress self.import_command = self.module.CommandImportWordpress() + self.import_command.onefile = False self.import_filename = os.path.abspath(os.path.join( os.path.dirname(__file__), 'wordpress_export_example.xml')) @@ -170,6 +171,8 @@ class CommandImportWordpressTest(BasicCommandImportWordpress): def test_populate_context(self): channel = self.import_command.get_channel_from_file( self.import_filename) + self.import_command.html2text = False + self.import_command.transform_to_markdown = False self.import_command.transform_to_html = False self.import_command.use_wordpress_compiler = False context = self.import_command.populate_context(channel) @@ -194,6 +197,8 @@ class CommandImportWordpressTest(BasicCommandImportWordpress): self.import_command.no_downloads = False self.import_command.export_categories_as_categories = False self.import_command.export_comments = False + self.import_command.html2text = False + self.import_command.transform_to_markdown = False self.import_command.transform_to_html = False self.import_command.use_wordpress_compiler = False self.import_command.tag_saniziting_strategy = 'first' @@ -206,6 +211,7 @@ class CommandImportWordpressTest(BasicCommandImportWordpress): write_metadata = mock.MagicMock() write_content = mock.MagicMock() + write_post = mock.MagicMock() write_attachments_info = mock.MagicMock() download_mock = mock.MagicMock() @@ -224,7 +230,7 @@ class CommandImportWordpressTest(BasicCommandImportWordpress): self.assertTrue(write_metadata.called) write_metadata.assert_any_call( - 'new_site/stories/kontakt.meta'.replace('/', os.sep), 'Kontakt', + 'new_site/pages/kontakt.meta'.replace('/', os.sep), 'Kontakt', 'kontakt', '2009-07-16 20:20:32', '', [], **{'wp-status': 'publish'}) self.assertTrue(write_content.called) @@ -238,51 +244,39 @@ Some source code. ```Python import sys - print sys.version ``` The end. - """, True) self.assertTrue(write_attachments_info.called) write_attachments_info.assert_any_call('new_site/posts/2008/07/arzt-und-pfusch-s-i-c-k.attachments.json'.replace('/', os.sep), {10: {'wordpress_user_name': 'Niko', 'files_meta': [{'width': 300, 'height': 299}, - {'width': b'150', 'size': 'thumbnail', 'height': b'150'}], + {'width': 150, 'size': 'thumbnail', 'height': 150}], 'excerpt': 'Arzt+Pfusch - S.I.C.K.', 'date_utc': '2009-07-16 19:40:37', 'content': 'Das Cover von Arzt+Pfusch - S.I.C.K.', 'files': ['/wp-content/uploads/2008/07/arzt_und_pfusch-sick-cover.png', '/wp-content/uploads/2008/07/arzt_und_pfusch-sick-cover-150x150.png'], - 'title': 'Arzt+Pfusch - S.I.C.K.'}}) + 'title': 'Arzt+Pfusch - S.I.C.K.'}}) write_content.assert_any_call( 'new_site/posts/2008/07/arzt-und-pfusch-s-i-c-k.md'.replace('/', os.sep), '''Arzt+Pfusch - S.I.C.K.Arzt+Pfusch - S.I.C.K.Gerade bin ich \xfcber das Album S.I.C.K von Arzt+Pfusch gestolpert, welches Arzt+Pfusch zum Download f\xfcr lau anbieten. Das Album steht unter einer Creative Commons BY-NC-ND-Lizenz. - Die Ladung noisebmstupidevildustrial gibts als MP3s mit 64kbps und VBR, als Ogg Vorbis und als FLAC (letztere hier). Artwork und Lyrics gibts nochmal einzeln zum Download.''', True) write_content.assert_any_call( - 'new_site/stories/kontakt.md'.replace('/', os.sep), """

Datenschutz

- + 'new_site/pages/kontakt.md'.replace('/', os.sep), """

Datenschutz

Ich erhebe und speichere automatisch in meine Server Log Files Informationen, die dein Browser an mich \xfcbermittelt. Dies sind: -
    -
  • Browsertyp und -version
  • -
  • verwendetes Betriebssystem
  • -
  • Referrer URL (die zuvor besuchte Seite)
  • -
  • IP Adresse des zugreifenden Rechners
  • -
  • Uhrzeit der Serveranfrage.
  • -
- Diese Daten sind f\xfcr mich nicht bestimmten Personen zuordenbar. Eine Zusammenf\xfchrung dieser Daten mit anderen Datenquellen wird nicht vorgenommen, die Daten werden einzig zu statistischen Zwecken erhoben.""", True) self.assertTrue(len(self.import_command.url_map) > 0) @@ -296,7 +290,7 @@ Diese Daten sind f\xfcr mich nicht bestimmten Personen zuordenbar. Eine Zusammen 'http://some.blog/posts/2008/07/arzt-und-pfusch-s-i-c-k.html') self.assertEqual( self.import_command.url_map['http://some.blog/kontakt/'], - 'http://some.blog/stories/kontakt.html') + 'http://some.blog/pages/kontakt.html') image_thumbnails = [ 'http://some.blog/wp-content/uploads/2012/12/2012-12-19-1355925145_1024x600_scrot-64x64.png', @@ -325,6 +319,8 @@ Diese Daten sind f\xfcr mich nicht bestimmten Personen zuordenbar. Eine Zusammen transform_caption = mock.MagicMock() transform_newlines = mock.MagicMock() + self.import_command.html2text = False + self.import_command.transform_to_markdown = False self.import_command.transform_to_html = False self.import_command.use_wordpress_compiler = False diff --git a/tests/test_compile_markdown.py b/tests/test_compile_markdown.py index 2275b04..99f44fd 100644 --- a/tests/test_compile_markdown.py +++ b/tests/test_compile_markdown.py @@ -52,7 +52,7 @@ class CompileMarkdownTests(BaseTestCase): expected_output = '''\
\
1
\ -
\
+
\
 from this
 
@@ -62,54 +62,11 @@ class CompileMarkdownTests(BaseTestCase): self.assertEquals(actual_output.strip(), expected_output.strip()) def test_compile_strikethrough(self): - input_str = '~~strik text~~' - expected_output = '

strik text

' + input_str = '~~striked out text~~' + expected_output = '

striked out text

' actual_output = self.compile(input_str) self.assertEquals(actual_output.strip(), expected_output.strip()) - def test_compile_html_gist(self): - input_str = '''\ -Here's a gist file inline: -[:gist: 4747847 zen.py] - -Cool, eh? -''' - expected_output = '''\ -

Here's a gist file inline: -

- - -
-

-

Cool, eh?

-''' - actual_output = self.compile(input_str) - self.assertEquals(actual_output.strip(), expected_output.strip()) - - def test_compile_html_gist_2(self): - input_str = '''\ -Here's a gist file inline, using reStructuredText syntax: -..gist:: 4747847 zen.py - -Cool, eh? -''' - expected_output = '''\ -

Here's a gist file inline, using reStructuredText syntax: -

- - -
-

-

Cool, eh?

-''' - actual_output = self.compile(input_str) - self.assertEquals(actual_output.strip(), expected_output.strip()) - - if __name__ == '__main__': unittest.main() diff --git a/tests/test_integration.py b/tests/test_integration.py index 352aab6..87a3eba 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -203,8 +203,8 @@ class TranslatedBuildTest(EmptyBuildTest): def test_translated_titles(self): """Check that translated title is picked up.""" - en_file = os.path.join(self.target_dir, "output", "stories", "1.html") - pl_file = os.path.join(self.target_dir, "output", self.ol, "stories", "1.html") + en_file = os.path.join(self.target_dir, "output", "pages", "1.html") + pl_file = os.path.join(self.target_dir, "output", self.ol, "pages", "1.html") # Files should be created self.assertTrue(os.path.isfile(en_file)) self.assertTrue(os.path.isfile(pl_file)) @@ -223,8 +223,8 @@ class TranslationsPatternTest1(TranslatedBuildTest): @classmethod def patch_site(self): """Set the TRANSLATIONS_PATTERN to the old v6 default""" - os.rename(os.path.join(self.target_dir, "stories", "1.%s.txt" % self.ol), - os.path.join(self.target_dir, "stories", "1.txt.%s" % self.ol) + os.rename(os.path.join(self.target_dir, "pages", "1.%s.txt" % self.ol), + os.path.join(self.target_dir, "pages", "1.txt.%s" % self.ol) ) conf_path = os.path.join(self.target_dir, "conf.py") with io.open(conf_path, "r", encoding="utf-8") as inf: @@ -241,7 +241,7 @@ class MissingDefaultLanguageTest(TranslatedBuildTest): @classmethod def fill_site(self): super(MissingDefaultLanguageTest, self).fill_site() - os.unlink(os.path.join(self.target_dir, "stories", "1.txt")) + os.unlink(os.path.join(self.target_dir, "pages", "1.txt")) def test_translated_titles(self): """Do not test titles as we just removed the translation""" @@ -255,8 +255,8 @@ class TranslationsPatternTest2(TranslatedBuildTest): def patch_site(self): """Set the TRANSLATIONS_PATTERN to the old v6 default""" conf_path = os.path.join(self.target_dir, "conf.py") - os.rename(os.path.join(self.target_dir, "stories", "1.%s.txt" % self.ol), - os.path.join(self.target_dir, "stories", "1.txt.%s" % self.ol) + os.rename(os.path.join(self.target_dir, "pages", "1.%s.txt" % self.ol), + os.path.join(self.target_dir, "pages", "1.txt.%s" % self.ol) ) with io.open(conf_path, "r", encoding="utf-8") as inf: data = inf.read() @@ -313,6 +313,7 @@ class TestCheck(DemoBuildTest): with cd(self.target_dir): self.assertIsNone(__main__.main(['check', '-f'])) + class TestCheckAbsoluteSubFolder(TestCheck): """Validate links in a site which is: @@ -383,7 +384,7 @@ class TestCheckFailure(DemoBuildTest): class RelativeLinkTest2(DemoBuildTest): - """Check that dropping stories to the root doesn't break links.""" + """Check that dropping pages to the root doesn't break links.""" @classmethod def patch_site(self): @@ -391,10 +392,10 @@ class RelativeLinkTest2(DemoBuildTest): conf_path = os.path.join(self.target_dir, "conf.py") with io.open(conf_path, "r", encoding="utf-8") as inf: data = inf.read() - data = data.replace('("stories/*.txt", "stories", "story.tmpl"),', - '("stories/*.txt", "", "story.tmpl"),') - data = data.replace('("stories/*.rst", "stories", "story.tmpl"),', - '("stories/*.rst", "", "story.tmpl"),') + data = data.replace('("pages/*.txt", "pages", "story.tmpl"),', + '("pages/*.txt", "", "story.tmpl"),') + data = data.replace('("pages/*.rst", "pages", "story.tmpl"),', + '("pages/*.rst", "", "story.tmpl"),') data = data.replace('# INDEX_PATH = ""', 'INDEX_PATH = "blog"') with io.open(conf_path, "w+", encoding="utf8") as outf: @@ -402,7 +403,7 @@ class RelativeLinkTest2(DemoBuildTest): outf.flush() def test_relative_links(self): - """Check that the links in a story are correct""" + """Check that the links in a page are correct""" test_path = os.path.join(self.target_dir, "output", "about-nikola.html") flag = False with open(test_path, "rb") as inf: @@ -497,52 +498,6 @@ class SubdirRunningTest(DemoBuildTest): self.assertEquals(result, 0) -class InvariantBuildTest(EmptyBuildTest): - """Test that a default build of --demo works.""" - - @classmethod - def build(self): - """Build the site.""" - try: - self.oldlocale = locale.getlocale() - locale.setlocale(locale.LC_ALL, ("en_US", "utf8")) - except: - pytest.skip('no en_US locale!') - else: - with cd(self.target_dir): - __main__.main(["build", "--invariant"]) - finally: - try: - locale.setlocale(locale.LC_ALL, self.oldlocale) - except: - pass - - @classmethod - def fill_site(self): - """Fill the site with demo content.""" - self.init_command.copy_sample_site(self.target_dir) - self.init_command.create_configuration(self.target_dir) - src1 = os.path.join(os.path.dirname(__file__), 'data', '1-nolinks.rst') - dst1 = os.path.join(self.target_dir, 'posts', '1.rst') - shutil.copy(src1, dst1) - os.system('rm "{0}/stories/creating-a-theme.rst" "{0}/stories/extending.txt" "{0}/stories/internals.txt" "{0}/stories/manual.rst" "{0}/stories/social_buttons.txt" "{0}/stories/theming.rst" "{0}/stories/path_handlers.txt" "{0}/stories/charts.txt"'.format(self.target_dir)) - def test_invariance(self): - """Compare the output to the canonical output.""" - if sys.version_info[0:2] != (2, 7): - pytest.skip('only python 2.7 is supported for invariance') - good_path = os.path.join(os.path.dirname(__file__), 'data', 'baseline{0[0]}.{0[1]}'.format(sys.version_info)) - if not os.path.exists(good_path): - pytest.skip('no baseline found') - with cd(self.target_dir): - try: - diff = subprocess.check_output(['diff', '-ubwr', good_path, 'output']) - self.assertEqual(diff.strip(), '') - except subprocess.CalledProcessError as exc: - print('Unexplained diff for the invariance test. (-canonical +built)') - print(exc.output.decode('utf-8')) - self.assertEqual(exc.returncode, 0, 'Unexplained diff for the invariance test.') - - class RedirectionsTest1(TestCheck): """Check REDIRECTIONS""" @@ -560,6 +515,7 @@ class RedirectionsTest1(TestCheck): with io.open(target_path, "w+", encoding="utf8") as outf: outf.write("foo") + class RedirectionsTest2(TestCheck): """Check external REDIRECTIONS""" @@ -570,6 +526,7 @@ class RedirectionsTest2(TestCheck): with io.open(conf_path, "a", encoding="utf8") as outf: outf.write("""\n\nREDIRECTIONS = [ ("foo.html", "http://www.example.com/"), ]\n\n""") + class RedirectionsTest3(TestCheck): """Check relative REDIRECTIONS""" diff --git a/tests/test_rss_feeds.py b/tests/test_rss_feeds.py index f67ed40..d44cd7b 100644 --- a/tests/test_rss_feeds.py +++ b/tests/test_rss_feeds.py @@ -50,13 +50,15 @@ class RSSFeedTest(unittest.TestCase): with mock.patch('nikola.post.get_meta', mock.Mock(return_value=( ({'title': 'post title', - 'slug': 'awesome_article', - 'date': '2012-10-01 22:41', - 'author': None, - 'tags': 'tags', - 'link': 'link', - 'description': 'description', - 'enclosure': 'http://www.example.org/foo.mp3'}, True) + 'slug': 'awesome_article', + 'date': '2012-10-01 22:41', + 'author': None, + 'tags': 'tags', + 'link': 'link', + 'description': 'description', + 'enclosure': 'http://www.example.org/foo.mp3', + 'enclosure_length': '5'}, + True) ))): with mock.patch('nikola.nikola.utils.os.path.isdir', mock.Mock(return_value=True)): diff --git a/tests/test_rst_compiler.py b/tests/test_rst_compiler.py index 5bbd617..5b33db7 100644 --- a/tests/test_rst_compiler.py +++ b/tests/test_rst_compiler.py @@ -4,7 +4,7 @@ """ Test cases for Nikola ReST extensions. -A base class ReSTExtensionTestCase provides the tests basic behaivor. +A base class ReSTExtensionTestCase provides the tests basic behaviour. Subclasses must override the "sample" class attribute with the ReST markup. The sample will be rendered as HTML using publish_parts() by setUp(). One method is provided for checking the resulting HTML: @@ -44,12 +44,11 @@ import pytest import unittest import nikola.plugins.compile.rest -from nikola.plugins.compile.rest import gist from nikola.plugins.compile.rest import vimeo import nikola.plugins.compile.rest.listing from nikola.plugins.compile.rest.doc import Plugin as DocPlugin from nikola.utils import _reload -from .base import BaseTestCase, FakeSite +from .base import BaseTestCase, FakeSite, FakePost class ReSTExtensionTestCase(BaseTestCase): @@ -72,20 +71,20 @@ class ReSTExtensionTestCase(BaseTestCase): tmpdir = tempfile.mkdtemp() inf = os.path.join(tmpdir, 'inf') outf = os.path.join(tmpdir, 'outf') - depf = os.path.join(tmpdir, 'outf.dep') with io.open(inf, 'w+', encoding='utf8') as f: f.write(rst) + p = FakePost('', '') + p._depfile[outf] = [] + self.compiler.site.post_per_input_file[inf] = p self.html = self.compiler.compile_html(inf, outf) with io.open(outf, 'r', encoding='utf8') as f: self.html = f.read() os.unlink(inf) os.unlink(outf) - if os.path.isfile(depf): - with io.open(depf, 'r', encoding='utf8') as f: - self.assertEqual(self.deps, f.read()) - os.unlink(depf) - else: - self.assertEqual(self.deps, None) + depfile = [p for p in p._depfile[outf] if p != outf] + depfile = '\n'.join(depfile) + if depfile: + self.assertEqual(self.deps.strip(), depfile) os.rmdir(tmpdir) self.html_doc = html.parse(StringIO(self.html)) @@ -129,55 +128,6 @@ class MathTestCase(ReSTExtensionTestCase): text="\(e^{ix} = \cos x + i\sin x\)") -class GistTestCase(ReSTExtensionTestCase): - """ Test GitHubGist. - We will replace get_raw_gist() and get_raw_gist_with_filename() - monkeypatching the GitHubGist class for avoiding network dependency - - """ - gist_type = gist.GitHubGist - sample = '.. gist:: fake_id\n :file: spam.py' - sample_without_filename = '.. gist:: fake_id2' - - def setUp(self): - """ Patch GitHubGist for avoiding network dependency """ - super(GistTestCase, self).setUp() - self.gist_type.get_raw_gist_with_filename = lambda *_: 'raw_gist_file' - self.gist_type.get_raw_gist = lambda *_: "raw_gist" - _reload(nikola.plugins.compile.rest) - - @pytest.mark.skipif(True, reason="This test indefinitely skipped.") - def test_gist(self): - """ Test the gist directive with filename """ - self.setHtmlFromRst(self.sample) - output = 'https://gist.github.com/fake_id.js?file=spam.py' - self.assertHTMLContains("script", attributes={"src": output}) - self.assertHTMLContains("pre", text="raw_gist_file") - - @pytest.mark.skipif(True, reason="This test indefinitely skipped.") - def test_gist_without_filename(self): - """ Test the gist directive without filename """ - self.setHtmlFromRst(self.sample_without_filename) - output = 'https://gist.github.com/fake_id2.js' - self.assertHTMLContains("script", attributes={"src": output}) - self.assertHTMLContains("pre", text="raw_gist") - - -class GistIntegrationTestCase(ReSTExtensionTestCase): - """ Test requests integration. The gist plugin uses requests to fetch gist - contents and place it in a noscript tag. - - """ - sample = '.. gist:: 1812835' - - def test_gist_integration(self): - """ Fetch contents of the gist from GH and render in a noscript tag """ - self.basic_test() - text = ('Be alone, that is the secret of invention: be alone, that is' - ' when ideas are born. -- Nikola Tesla') - self.assertHTMLContains('pre', text=text) - - class SlidesTestCase(ReSTExtensionTestCase): """ Slides test case """ @@ -223,7 +173,7 @@ class VimeoTestCase(ReSTExtensionTestCase): """ Test Vimeo iframe tag generation """ self.basic_test() self.assertHTMLContains("iframe", - attributes={"src": ("//player.vimeo.com/" + attributes={"src": ("https://player.vimeo.com/" "video/VID"), "height": "400", "width": "600"}) @@ -237,7 +187,7 @@ class YoutubeTestCase(ReSTExtensionTestCase): """ Test Youtube iframe tag generation """ self.basic_test() self.assertHTMLContains("iframe", - attributes={"src": ("//www.youtube.com/" + attributes={"src": ("https://www.youtube.com/" "embed/YID?rel=0&hd=1&" "wmode=transparent"), "height": "400", "width": "600"}) diff --git a/tests/test_shortcodes.py b/tests/test_shortcodes.py new file mode 100644 index 0000000..22f603e --- /dev/null +++ b/tests/test_shortcodes.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +# vim: set wrap textwidth=300 + +u"""Test shortcodes.""" + +from __future__ import unicode_literals +import pytest +from nikola import shortcodes +from .base import FakeSite, BaseTestCase +import sys + +def noargs(site, data='', lang=''): + return "noargs {0} success!".format(data) + +def arg(*args, **kwargs): + # don’t clutter the kwargs dict + _ = kwargs.pop('site') + data = kwargs.pop('data') + lang = kwargs.pop('lang') + # TODO hack for Python 2.7 -- remove when possible + if sys.version_info[0] == 2: + args = tuple(i.encode('utf-8') for i in args) + kwargs = {k.encode('utf-8'): v.encode('utf-8') for k, v in kwargs.items()} + return "arg {0}/{1}/{2}".format(args, sorted(kwargs.items()), data) + + +@pytest.fixture(scope="module") +def fakesite(): + s = FakeSite() + s.register_shortcode('noargs', noargs) + s.register_shortcode('arg', arg) + return s + +def test_noargs(fakesite): + assert shortcodes.apply_shortcodes('test({{% noargs %}})', fakesite.shortcode_registry) == 'test(noargs success!)' + assert shortcodes.apply_shortcodes('test({{% noargs %}}\\hello world/{{% /noargs %}})', fakesite.shortcode_registry) == 'test(noargs \\hello world/ success!)' + +def test_arg_pos(fakesite): + assert shortcodes.apply_shortcodes('test({{% arg 1 %}})', fakesite.shortcode_registry) == "test(arg ('1',)/[]/)" + assert shortcodes.apply_shortcodes('test({{% arg 1 2aa %}})', fakesite.shortcode_registry) == "test(arg ('1', '2aa')/[]/)" + assert shortcodes.apply_shortcodes('test({{% arg "hello world" %}})', fakesite.shortcode_registry) == "test(arg ('hello world',)/[]/)" + assert shortcodes.apply_shortcodes('test({{% arg back\ slash arg2 %}})', fakesite.shortcode_registry) == "test(arg ('back slash', 'arg2')/[]/)" + assert shortcodes.apply_shortcodes('test({{% arg "%}}" %}})', fakesite.shortcode_registry) == "test(arg ('%}}',)/[]/)" + +def test_arg_keyword(fakesite): + assert shortcodes.apply_shortcodes('test({{% arg 1a=2b %}})', fakesite.shortcode_registry) == "test(arg ()/[('1a', '2b')]/)" + assert shortcodes.apply_shortcodes('test({{% arg 1a="2b 3c" 4d=5f %}})', fakesite.shortcode_registry) == "test(arg ()/[('1a', '2b 3c'), ('4d', '5f')]/)" + assert shortcodes.apply_shortcodes('test({{% arg 1a="2b 3c" 4d=5f back=slash\ slash %}})', fakesite.shortcode_registry) == "test(arg ()/[('1a', '2b 3c'), ('4d', '5f'), ('back', 'slash slash')]/)" + +def test_data(fakesite): + assert shortcodes.apply_shortcodes('test({{% arg 123 %}}Hello!{{% /arg %}})', fakesite.shortcode_registry) == "test(arg ('123',)/[]/Hello!)" + assert shortcodes.apply_shortcodes('test({{% arg 123 456 foo=bar %}}Hello world!{{% /arg %}})', fakesite.shortcode_registry) == "test(arg ('123', '456')/[('foo', 'bar')]/Hello world!)" + assert shortcodes.apply_shortcodes('test({{% arg 123 456 foo=bar baz="quotes rock." %}}Hello test suite!{{% /arg %}})', fakesite.shortcode_registry) == "test(arg ('123', '456')/[('baz', 'quotes rock.'), ('foo', 'bar')]/Hello test suite!)" + assert shortcodes.apply_shortcodes('test({{% arg "123 foo" foobar foo=bar baz="quotes rock." %}}Hello test suite!!{{% /arg %}})', fakesite.shortcode_registry) == "test(arg ('123 foo', 'foobar')/[('baz', 'quotes rock.'), ('foo', 'bar')]/Hello test suite!!)" + + +class TestErrors(BaseTestCase): + def setUp(self): + self.fakesite = fakesite() + + def test_errors(self): + self.assertRaisesRegexp(shortcodes.ParsingError, "^Shortcode 'start' starting at .* is not terminated correctly with '%}}'!", shortcodes.apply_shortcodes, '{{% start', self.fakesite.shortcode_registry, raise_exceptions=True) + self.assertRaisesRegexp(shortcodes.ParsingError, "^Syntax error in shortcode 'wrong' at .*: expecting whitespace!", shortcodes.apply_shortcodes, '{{% wrong ending %%}', self.fakesite.shortcode_registry, raise_exceptions=True) + self.assertRaisesRegexp(shortcodes.ParsingError, "^Found shortcode ending '{{% /end %}}' which isn't closing a started shortcode", shortcodes.apply_shortcodes, '{{% start %}} {{% /end %}}', self.fakesite.shortcode_registry, raise_exceptions=True) + self.assertRaisesRegexp(shortcodes.ParsingError, "^Unexpected end of unquoted string", shortcodes.apply_shortcodes, '{{% start "asdf %}}', self.fakesite.shortcode_registry, raise_exceptions=True) + self.assertRaisesRegexp(shortcodes.ParsingError, "^String starting at .* must be non-empty!", shortcodes.apply_shortcodes, '{{% start =b %}}', self.fakesite.shortcode_registry, raise_exceptions=True) + self.assertRaisesRegexp(shortcodes.ParsingError, "^Unexpected end of data while escaping", shortcodes.apply_shortcodes, '{{% start "a\\', self.fakesite.shortcode_registry, raise_exceptions=True) + self.assertRaisesRegexp(shortcodes.ParsingError, "^Unexpected end of data while escaping", shortcodes.apply_shortcodes, '{{% start a\\', self.fakesite.shortcode_registry, raise_exceptions=True) + self.assertRaisesRegexp(shortcodes.ParsingError, "^Unexpected quotation mark in unquoted string", shortcodes.apply_shortcodes, '{{% start a"b" %}}', self.fakesite.shortcode_registry, raise_exceptions=True) + self.assertRaisesRegexp(shortcodes.ParsingError, "^Syntax error in shortcode 'start' at .*: expecting whitespace!", shortcodes.apply_shortcodes, '{{% start "a"b %}}', self.fakesite.shortcode_registry, raise_exceptions=True) + self.assertRaisesRegexp(shortcodes.ParsingError, "^Syntax error: '{{%' must be followed by shortcode name", shortcodes.apply_shortcodes, '{{% %}}', self.fakesite.shortcode_registry, raise_exceptions=True) + self.assertRaisesRegexp(shortcodes.ParsingError, "^Syntax error: '{{%' must be followed by shortcode name", shortcodes.apply_shortcodes, '{{%', self.fakesite.shortcode_registry, raise_exceptions=True) + self.assertRaisesRegexp(shortcodes.ParsingError, "^Syntax error: '{{%' must be followed by shortcode name", shortcodes.apply_shortcodes, '{{% ', self.fakesite.shortcode_registry, raise_exceptions=True) + self.assertRaisesRegexp(shortcodes.ParsingError, "^Found shortcode ending '{{% / %}}' which isn't closing a started shortcode", shortcodes.apply_shortcodes, '{{% / %}}', self.fakesite.shortcode_registry, raise_exceptions=True) + self.assertRaisesRegexp(shortcodes.ParsingError, "^Syntax error: '{{% /' must be followed by ' %}}'", shortcodes.apply_shortcodes, '{{% / a %}}', self.fakesite.shortcode_registry, raise_exceptions=True) + self.assertRaisesRegexp(shortcodes.ParsingError, "^Shortcode '<==' starting at .* is not terminated correctly with '%}}'!", shortcodes.apply_shortcodes, '==> {{% <==', self.fakesite.shortcode_registry, raise_exceptions=True) diff --git a/tests/test_slugify.py b/tests/test_slugify.py index 9dec5a6..c910aaa 100644 --- a/tests/test_slugify.py +++ b/tests/test_slugify.py @@ -8,41 +8,41 @@ import nikola.utils def test_ascii(): """Test an ASCII-only string.""" - o = nikola.utils.slugify(u'hello') + o = nikola.utils.slugify(u'hello', lang='en') assert o == u'hello' assert isinstance(o, nikola.utils.unicode_str) def test_ascii_dash(): """Test an ASCII string, with dashes.""" - o = nikola.utils.slugify(u'hello-world') + o = nikola.utils.slugify(u'hello-world', lang='en') assert o == u'hello-world' assert isinstance(o, nikola.utils.unicode_str) def test_ascii_fancy(): """Test an ASCII string, with fancy characters.""" - o = nikola.utils.slugify(u'The quick brown fox jumps over the lazy dog!-123.456') + o = nikola.utils.slugify(u'The quick brown fox jumps over the lazy dog!-123.456', lang='en') assert o == u'the-quick-brown-fox-jumps-over-the-lazy-dog-123456' assert isinstance(o, nikola.utils.unicode_str) def test_pl(): """Test a string with Polish diacritical characters.""" - o = nikola.utils.slugify(u'zażółćgęśląjaźń') + o = nikola.utils.slugify(u'zażółćgęśląjaźń', lang='pl') assert o == u'zazolcgeslajazn' assert isinstance(o, nikola.utils.unicode_str) def test_pl_dash(): """Test a string with Polish diacritical characters and dashes.""" - o = nikola.utils.slugify(u'zażółć-gęślą-jaźń') + o = nikola.utils.slugify(u'zażółć-gęślą-jaźń', lang='pl') assert o == u'zazolc-gesla-jazn' def test_pl_fancy(): """Test a string with Polish diacritical characters and fancy characters.""" - o = nikola.utils.slugify(u'Zażółć gęślą jaźń!-123.456') + o = nikola.utils.slugify(u'Zażółć gęślą jaźń!-123.456', lang='pl') assert o == u'zazolc-gesla-jazn-123456' assert isinstance(o, nikola.utils.unicode_str) @@ -50,7 +50,7 @@ def test_pl_fancy(): def test_disarmed(): """Test disarmed slugify.""" nikola.utils.USE_SLUGIFY = False - o = nikola.utils.slugify(u'Zażółć gęślą jaźń!-123.456') + o = nikola.utils.slugify(u'Zażółć gęślą jaźń!-123.456', lang='pl') assert o == u'Zażółć gęślą jaźń!-123.456' assert isinstance(o, nikola.utils.unicode_str) nikola.utils.USE_SLUGIFY = True @@ -59,7 +59,7 @@ def test_disarmed(): def test_disarmed_weird(): """Test disarmed slugify with banned characters.""" nikola.utils.USE_SLUGIFY = False - o = nikola.utils.slugify(u'Zażółć gęślą jaźń!-123.456 "Hello World"?#Hl/l\\o:W\'o\rr*l\td|!\n') + o = nikola.utils.slugify(u'Zażółć gęślą jaźń!-123.456 "Hello World"?#Hl/l\\o:W\'o\rr*l\td|!\n', lang='pl') assert o == u'Zażółć gęślą jaźń!-123.456 -Hello World---H-e-l-l-o-W-o-r-l-d-!-' assert isinstance(o, nikola.utils.unicode_str) nikola.utils.USE_SLUGIFY = True diff --git a/tests/test_template_shortcodes.py b/tests/test_template_shortcodes.py new file mode 100644 index 0000000..a1d3a91 --- /dev/null +++ b/tests/test_template_shortcodes.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- +# vim: set wrap textwidth=100 +"""Test template-based shortcodes.""" + +from __future__ import unicode_literals + +import pytest +from nikola import Nikola + + +class ShortcodeFakeSite(Nikola): + def _get_template_system(self): + if self._template_system is None: + # Load template plugin + self._template_system = self.plugin_manager.getPluginByName( + 'jinja', "TemplateSystem").plugin_object + self._template_system.set_directories('.', 'cache') + self._template_system.set_site(self) + + return self._template_system + + template_system = property(_get_template_system) + + +@pytest.fixture(scope="module") +def fakesite(): + s = ShortcodeFakeSite() + s.init_plugins() + s._template_system = None + return s + + +def test_mixedargs(fakesite): + TEST_TMPL = """ +arg1: {{ _args[0] }} +arg2: {{ _args[1] }} +kwarg1: {{ kwarg1 }} +kwarg2: {{ kwarg2 }} +""" + + fakesite.shortcode_registry['test1'] = \ + fakesite._make_renderfunc(TEST_TMPL) + fakesite.shortcode_registry['test2'] = \ + fakesite._make_renderfunc('Something completely different') + + res = fakesite.apply_shortcodes( + '{{% test1 kwarg1=spamm arg1 kwarg2=foo,bar arg2 %}}') + + assert res.strip() == """ +arg1: arg1 +arg2: arg2 +kwarg1: spamm +kwarg2: foo,bar""".strip() + + +def test_onearg(fakesite): + fakesite.shortcode_registry['test1'] = \ + fakesite._make_renderfunc('arg={{ _args[0] }}') + + assert fakesite.apply_shortcodes('{{% test1 onearg %}}') == 'arg=onearg' + assert fakesite.apply_shortcodes('{{% test1 "one two" %}}') == 'arg=one two' + + +def test_kwarg(fakesite): + fakesite.shortcode_registry['test1'] = \ + fakesite._make_renderfunc('foo={{ foo }}') + + res = fakesite.apply_shortcodes('{{% test1 foo=bar %}}') + assert res == 'foo=bar' + res = fakesite.apply_shortcodes('{{% test1 foo="bar baz" %}}') + assert res == 'foo=bar baz' + res = fakesite.apply_shortcodes('{{% test1 foo="bar baz" spamm=ham %}}') + assert res == 'foo=bar baz' + + +def test_data(fakesite): + fakesite.shortcode_registry['test1'] = \ + fakesite._make_renderfunc('data={{ data }}') + + res = fakesite.apply_shortcodes('{{% test1 %}}spamm spamm{{% /test1 %}}') + assert res == 'data=spamm spamm' + res = fakesite.apply_shortcodes('{{% test1 spamm %}}') + assert res == 'data=' + # surprise! + res = fakesite.apply_shortcodes('{{% test1 data=dummy %}}') + assert res == 'data=' diff --git a/tests/test_utils.py b/tests/test_utils.py index 9507de2..225ff13 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,10 +1,5 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals - -import os -import sys - - import unittest import mock import lxml.html @@ -13,7 +8,7 @@ from nikola.utils import demote_headers, TranslatableSetting class dummy(object): - pass + default_lang = 'en' class GetMetaTest(unittest.TestCase): @@ -323,13 +318,14 @@ def test_get_metadata_from_file(): from nikola.post import _get_metadata_from_file g = _get_metadata_from_file assert list(g([]).values()) == [] - assert str(g(["======","FooBar","======"])["title"]) == 'FooBar' - assert str(g(["FooBar","======"])["title"]) == 'FooBar' + assert str(g(["======", "FooBar", "======"])["title"]) == 'FooBar' + assert str(g(["FooBar", "======"])["title"]) == 'FooBar' assert str(g(["#FooBar"])["title"]) == 'FooBar' assert str(g([".. title: FooBar"])["title"]) == 'FooBar' - assert 'title' not in g(["","",".. title: FooBar"]) - assert 'title' in g(["",".. title: FooBar"]) - assert 'title' in g([".. foo: bar","","FooBar", "------"]) + assert 'title' not in g(["", "", ".. title: FooBar"]) + assert 'title' in g(["", ".. title: FooBar"]) + assert 'title' in g([".. foo: bar", "", "FooBar", "------"]) + if __name__ == '__main__': unittest.main() diff --git a/translations/README.md b/translations/README.md new file mode 100644 index 0000000..324e245 --- /dev/null +++ b/translations/README.md @@ -0,0 +1,5 @@ +# Nikola translations + +Translations for Nikola are handled on Transifex. Please direct all new languages and changes to existing ones there: + +https://www.transifex.com/ralsina/nikola/dashboard/ diff --git a/translations/nikola.messages/README.md b/translations/nikola.messages/README.md new file mode 100644 index 0000000..324e245 --- /dev/null +++ b/translations/nikola.messages/README.md @@ -0,0 +1,5 @@ +# Nikola translations + +Translations for Nikola are handled on Transifex. Please direct all new languages and changes to existing ones there: + +https://www.transifex.com/ralsina/nikola/dashboard/ diff --git a/translations/nikola.messages/ar.po b/translations/nikola.messages/ar.po index 3faa811..bff68fc 100644 --- a/translations/nikola.messages/ar.po +++ b/translations/nikola.messages/ar.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -7,6 +7,7 @@ # Translators: # Translators: # Translators: +# darkwise , 2014 # Mohammed Attia , 2014 # darkwise , 2014 msgid "" @@ -14,8 +15,8 @@ msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 07:51+0000\n" -"Last-Translator: Chris Warrick \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Arabic (http://www.transifex.com/ralsina/nikola/language/ar/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -47,16 +48,16 @@ msgid "Read more" msgstr "قراءة المزيد" msgid "Posts about %s" -msgstr "مقالات عن s%" +msgstr "مقالات عن %s" msgid "Next post" msgstr "المقالة التالية" msgid "old posts, page %d" -msgstr "مقالات قديمة, صفحة d%" +msgstr "مقالات قديمة, صفحة %d" msgid "page %d" -msgstr "صفحة d%" +msgstr "صفحة %d" msgid "Source" msgstr "المصدر" @@ -67,7 +68,7 @@ msgid "Read in English" msgstr "اقرأ بالعربية" msgid "Posts for year %s" -msgstr "مقالات سنة s%" +msgstr "مقالات سنة %s" msgid "Newer posts" msgstr "مقالات أحدث" @@ -144,3 +145,6 @@ msgstr "" msgid "Posts by %s" msgstr "" + +msgid "Toggle navigation" +msgstr "" diff --git a/translations/nikola.messages/az.po b/translations/nikola.messages/az.po index 9fa307c..d116605 100644 --- a/translations/nikola.messages/az.po +++ b/translations/nikola.messages/az.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -7,14 +7,14 @@ # Translators: # Translators: # Translators: -# knulp , 2014 +# knulp , 2014,2016 msgid "" msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 07:51+0000\n" -"Last-Translator: Chris Warrick \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Azerbaijani (http://www.transifex.com/ralsina/nikola/language/az/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -117,29 +117,32 @@ msgid "Skip to main content" msgstr "Əsas mövzuya keç" msgid "Subcategories:" -msgstr "" +msgstr "Subkateqoriyalar" #. Used for active navigation links, for screen readers only msgid "(active)" -msgstr "" +msgstr "(aktiv)" #. Default content for a new post msgid "Write your post here." -msgstr "" +msgstr "Öz məqaləni bura yaz" #. Default content for a new page msgid "Write your page here." -msgstr "" +msgstr "Öz səhifəni bura yaz" msgid "Uncategorized" -msgstr "" +msgstr "Kateqoriyasız" #. Used for feeds of any format msgid "Updates" -msgstr "" +msgstr "Yenilənmələr" msgid "Authors" -msgstr "" +msgstr "Müəlliflər" msgid "Posts by %s" +msgstr "%s tərəfindən yazılmış yazılar" + +msgid "Toggle navigation" msgstr "" diff --git a/translations/nikola.messages/bg.po b/translations/nikola.messages/bg.po index 1ba03c5..aa525d0 100644 --- a/translations/nikola.messages/bg.po +++ b/translations/nikola.messages/bg.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -7,6 +7,7 @@ # Translators: # Translators: # Translators: +# Hristo Iliev, 2016 # Ivaylo Kuzev , 2013 # Ivaylo Kuzev , 2013 msgid "" @@ -14,8 +15,8 @@ msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 07:50+0000\n" -"Last-Translator: Chris Warrick \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Bulgarian (http://www.transifex.com/ralsina/nikola/language/bg/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -41,10 +42,10 @@ msgid "Tags and Categories" msgstr "Тагове и Категории" msgid "Comments" -msgstr "" +msgstr "Коментари" msgid "Read more" -msgstr "Прочети още" +msgstr "Чети нататък" msgid "Posts about %s" msgstr "Публикации относно %s" @@ -59,7 +60,7 @@ msgid "page %d" msgstr "страница %d" msgid "Source" -msgstr "Source" +msgstr "Изходен код" #. Here your translation should NOT say English but the name of your language #. instead @@ -76,10 +77,10 @@ msgid "Previous post" msgstr "Предишна публикация" msgid "Also available in:" -msgstr "Също достъпно в:" +msgstr "Достъпно също на:" msgid "Languages:" -msgstr "" +msgstr "Езици:" msgid "Original site" msgstr "Оригиналния сайт" @@ -91,56 +92,59 @@ msgid "Archive" msgstr "Архив" msgid "Publication date" -msgstr "" +msgstr "Дата на публикуване" msgid "Posted:" -msgstr "Публиковано:" +msgstr "Публикyвано:" msgid "Posts for {month} {year}" msgstr "Публикации за {month} {year}" msgid "Posts for {month} {day}, {year}" -msgstr "" +msgstr "Публикации от {day} {month} {year}" msgid "Nothing found." -msgstr "" +msgstr "Нищо не е намерено." msgid "No posts found." -msgstr "" +msgstr "Не са намерени публикации." msgid "RSS feed" -msgstr "" +msgstr "RSS поток" msgid "%d min remaining to read" -msgstr "" +msgstr "%d минути до прочитане" msgid "Skip to main content" -msgstr "" +msgstr "Прескочи до основното съдържание" msgid "Subcategories:" -msgstr "" +msgstr "Подкатегории:" #. Used for active navigation links, for screen readers only msgid "(active)" -msgstr "" +msgstr "(активно)" #. Default content for a new post msgid "Write your post here." -msgstr "" +msgstr "Напиши тук текста на твоята публикация." #. Default content for a new page msgid "Write your page here." -msgstr "" +msgstr "Напиши тук текста на твоята страница." msgid "Uncategorized" -msgstr "" +msgstr "Без категория" #. Used for feeds of any format msgid "Updates" -msgstr "" +msgstr "Обновления" msgid "Authors" -msgstr "" +msgstr "Автори" msgid "Posts by %s" +msgstr "Публикации от %s" + +msgid "Toggle navigation" msgstr "" diff --git a/translations/nikola.messages/bs.po b/translations/nikola.messages/bs.po index 69dbd53..9b25d32 100644 --- a/translations/nikola.messages/bs.po +++ b/translations/nikola.messages/bs.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -13,8 +13,8 @@ msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-09-05 09:55+0000\n" -"Last-Translator: Saša Savić \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Bosnian (http://www.transifex.com/ralsina/nikola/language/bs/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -143,3 +143,6 @@ msgstr "Autori" msgid "Posts by %s" msgstr "Objave prema %s" + +msgid "Toggle navigation" +msgstr "" diff --git a/translations/nikola.messages/ca.po b/translations/nikola.messages/ca.po index 932627f..a42c3ab 100644 --- a/translations/nikola.messages/ca.po +++ b/translations/nikola.messages/ca.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -15,8 +15,8 @@ msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 07:51+0000\n" -"Last-Translator: Chris Warrick \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Catalan (http://www.transifex.com/ralsina/nikola/language/ca/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -145,3 +145,6 @@ msgstr "" msgid "Posts by %s" msgstr "" + +msgid "Toggle navigation" +msgstr "" diff --git a/translations/nikola.messages/cs.po b/translations/nikola.messages/cs.po index 2b5c4b7..2f11740 100644 --- a/translations/nikola.messages/cs.po +++ b/translations/nikola.messages/cs.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -15,8 +15,8 @@ msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 07:51+0000\n" -"Last-Translator: Chris Warrick \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Czech (http://www.transifex.com/ralsina/nikola/language/cs/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -145,3 +145,6 @@ msgstr "" msgid "Posts by %s" msgstr "" + +msgid "Toggle navigation" +msgstr "" diff --git a/translations/nikola.messages/da.po b/translations/nikola.messages/da.po index b6aeb68..5522127 100644 --- a/translations/nikola.messages/da.po +++ b/translations/nikola.messages/da.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -15,8 +15,8 @@ msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 07:51+0000\n" -"Last-Translator: Chris Warrick \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Danish (http://www.transifex.com/ralsina/nikola/language/da/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -145,3 +145,6 @@ msgstr "" msgid "Posts by %s" msgstr "" + +msgid "Toggle navigation" +msgstr "" diff --git a/translations/nikola.messages/de.po b/translations/nikola.messages/de.po index 2dccb77..b20e316 100644 --- a/translations/nikola.messages/de.po +++ b/translations/nikola.messages/de.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -24,8 +24,8 @@ msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-09-19 18:32+0000\n" -"Last-Translator: Felix Fontein \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: German (http://www.transifex.com/ralsina/nikola/language/de/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -154,3 +154,6 @@ msgstr "Autoren" msgid "Posts by %s" msgstr "Einträge von %s" + +msgid "Toggle navigation" +msgstr "" diff --git a/translations/nikola.messages/el.po b/translations/nikola.messages/el.po index 3173007..02c7528 100644 --- a/translations/nikola.messages/el.po +++ b/translations/nikola.messages/el.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -16,8 +16,8 @@ msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 07:51+0000\n" -"Last-Translator: Chris Warrick \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Greek (http://www.transifex.com/ralsina/nikola/language/el/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -146,3 +146,6 @@ msgstr "" msgid "Posts by %s" msgstr "" + +msgid "Toggle navigation" +msgstr "" diff --git a/translations/nikola.messages/en.po b/translations/nikola.messages/en.po index 7976058..d0c87da 100644 --- a/translations/nikola.messages/en.po +++ b/translations/nikola.messages/en.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -143,3 +143,6 @@ msgstr "Authors" msgid "Posts by %s" msgstr "Posts by %s" + +msgid "Toggle navigation" +msgstr "Toggle navigation" diff --git a/translations/nikola.messages/eo.po b/translations/nikola.messages/eo.po index ab5dfa2..de94881 100644 --- a/translations/nikola.messages/eo.po +++ b/translations/nikola.messages/eo.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -7,7 +7,7 @@ # Translators: # Translators: # Translators: -# Baptiste Darthenay , 2015 +# Baptiste Darthenay , 2015-2016 # David Paleino , 2013 # David Paleino , 2013 msgid "" @@ -15,7 +15,7 @@ msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-09-28 10:13+0000\n" +"PO-Revision-Date: 2016-09-27 15:56+0000\n" "Last-Translator: Baptiste Darthenay \n" "Language-Team: Esperanto (http://www.transifex.com/ralsina/nikola/language/eo/)\n" "MIME-Version: 1.0\n" @@ -145,3 +145,6 @@ msgstr "Aŭtoroj" msgid "Posts by %s" msgstr "Artikoloj de %s" + +msgid "Toggle navigation" +msgstr "Ŝalti menuon" diff --git a/translations/nikola.messages/es.po b/translations/nikola.messages/es.po index e4b5b7b..8544e03 100644 --- a/translations/nikola.messages/es.po +++ b/translations/nikola.messages/es.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -12,13 +12,13 @@ # Joaquín Tita , 2014 # Juan José Conti , 2015 # Roberto Alsina , 2013 -# Roberto Alsina , 2013-2015 +# Roberto Alsina , 2013-2016 msgid "" msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-09-04 14:11+0000\n" +"PO-Revision-Date: 2016-09-24 22:41+0000\n" "Last-Translator: Roberto Alsina \n" "Language-Team: Spanish (http://www.transifex.com/ralsina/nikola/language/es/)\n" "MIME-Version: 1.0\n" @@ -148,3 +148,6 @@ msgstr "Autores" msgid "Posts by %s" msgstr "Publicaciones de %s" + +msgid "Toggle navigation" +msgstr "Mostrar navegación" diff --git a/translations/nikola.messages/et.po b/translations/nikola.messages/et.po index 95ad490..ecbf746 100644 --- a/translations/nikola.messages/et.po +++ b/translations/nikola.messages/et.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -15,8 +15,8 @@ msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 07:50+0000\n" -"Last-Translator: Chris Warrick \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Estonian (http://www.transifex.com/ralsina/nikola/language/et/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -145,3 +145,6 @@ msgstr "" msgid "Posts by %s" msgstr "" + +msgid "Toggle navigation" +msgstr "" diff --git a/translations/nikola.messages/eu.po b/translations/nikola.messages/eu.po index 8721395..775bcbd 100644 --- a/translations/nikola.messages/eu.po +++ b/translations/nikola.messages/eu.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -8,14 +8,16 @@ # Translators: # Translators: # aitorp , 2013 +# Aitor P , 2016 # aitorp , 2013 +# Sukil Etxenike , 2016 msgid "" msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 07:50+0000\n" -"Last-Translator: Chris Warrick \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Basque (http://www.transifex.com/ralsina/nikola/language/eu/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -24,7 +26,7 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" msgid "More posts about %s" -msgstr "%s-ri buruzko post gehiago" +msgstr "%s-ri buruzko argitalpen gehiago" #. Here your translation should NOT say English but the name of your language #. instead @@ -41,22 +43,22 @@ msgid "Tags and Categories" msgstr "Etiketak eta Kategoriak" msgid "Comments" -msgstr "" +msgstr "Iruzkinak" msgid "Read more" msgstr "Irakurri gehiago" msgid "Posts about %s" -msgstr "%s-ri buruzko postak" +msgstr "%s-ri buruzko argitalpenak" msgid "Next post" -msgstr "Hurrengo posta" +msgstr "Hurrengo argitalpena" msgid "old posts, page %d" -msgstr "Post zaharren, orria %d" +msgstr "Argitalpen zaharragoak,%d. orria" msgid "page %d" -msgstr "orria %d" +msgstr "%d. orria" msgid "Source" msgstr "Iturria" @@ -67,80 +69,83 @@ msgid "Read in English" msgstr "Euskaraz irakurri" msgid "Posts for year %s" -msgstr "%s. urteko postak" +msgstr "%s. urteko argitalpenak" msgid "Newer posts" -msgstr "Post berrienak" +msgstr "Argitalpen berriagoak" msgid "Previous post" -msgstr "Aurreko posta" +msgstr "Aurreko argitalpena" msgid "Also available in:" -msgstr "Eskuragarria hemen ere:" +msgstr "Eskuragarria hizkuntza hauetan ere:" msgid "Languages:" -msgstr "" +msgstr "Hizkuntzak:" msgid "Original site" msgstr "Jatorrizko orria" msgid "Older posts" -msgstr "Post zaharrenak" +msgstr "Post zaharragoak" msgid "Archive" msgstr "Artxiboa" msgid "Publication date" -msgstr "" +msgstr "Argitaratze-data" msgid "Posted:" msgstr "Argitaratuta:" msgid "Posts for {month} {year}" -msgstr "{year}ko {month}ren postak" +msgstr "{year}ko {month}ren argitalpenak" msgid "Posts for {month} {day}, {year}" -msgstr "" +msgstr "{year}ko {month}aren {day}ko argitalpenak" msgid "Nothing found." -msgstr "" +msgstr "Ez da ezer aurkitu" msgid "No posts found." -msgstr "" +msgstr "Ez da argitalpenik aurkitu" msgid "RSS feed" -msgstr "" +msgstr "RSS jarioa" msgid "%d min remaining to read" -msgstr "" +msgstr "%d minutu gelditzen dira irakurtzeko" msgid "Skip to main content" -msgstr "" +msgstr "Joan eduki nagusira" msgid "Subcategories:" -msgstr "" +msgstr "Azpikategoriak:" #. Used for active navigation links, for screen readers only msgid "(active)" -msgstr "" +msgstr "(aktibo)" #. Default content for a new post msgid "Write your post here." -msgstr "" +msgstr "Idatzi zure argitalpena hemen" #. Default content for a new page msgid "Write your page here." -msgstr "" +msgstr "Idatzi zure orria hemen" msgid "Uncategorized" -msgstr "" +msgstr "Kategorizatu-gabeak" #. Used for feeds of any format msgid "Updates" -msgstr "" +msgstr "Eguneraketak" msgid "Authors" -msgstr "" +msgstr "Egileak" msgid "Posts by %s" +msgstr "%s-ek idatzitako argitalpenak" + +msgid "Toggle navigation" msgstr "" diff --git a/translations/nikola.messages/fa.po b/translations/nikola.messages/fa.po index 2c205ea..34dc4d9 100644 --- a/translations/nikola.messages/fa.po +++ b/translations/nikola.messages/fa.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -8,7 +8,7 @@ # Translators: # Translators: # Hossein Raysehman , 2014 -# Shahinism , 2013,2015 +# Shahinism , 2013,2015-2016 # Soroosh Azary Marhabi , 2013 # Shahinism , 2013 # Soroosh Azary Marhabi , 2013 @@ -17,8 +17,8 @@ msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 07:51+0000\n" -"Last-Translator: Chris Warrick \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Persian (http://www.transifex.com/ralsina/nikola/language/fa/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -121,29 +121,32 @@ msgid "Skip to main content" msgstr "متن اصلی را نادیده بگیر" msgid "Subcategories:" -msgstr "" +msgstr "زیر بخش‌ها:" #. Used for active navigation links, for screen readers only msgid "(active)" -msgstr "" +msgstr "(فعال)" #. Default content for a new post msgid "Write your post here." -msgstr "" +msgstr "متن پست‌تان را این‌جا بنویسید." #. Default content for a new page msgid "Write your page here." -msgstr "" +msgstr "من صفحه را این‌جا بنویسید. " msgid "Uncategorized" -msgstr "" +msgstr "دسته‌بندی نشده" #. Used for feeds of any format msgid "Updates" -msgstr "" +msgstr "بروزرسانی‌ها" msgid "Authors" -msgstr "" +msgstr "نویسنده‌ها" msgid "Posts by %s" +msgstr "ارسال‌های %s" + +msgid "Toggle navigation" msgstr "" diff --git a/translations/nikola.messages/fi.po b/translations/nikola.messages/fi.po index c0d0ab9..c189a33 100644 --- a/translations/nikola.messages/fi.po +++ b/translations/nikola.messages/fi.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -9,14 +9,15 @@ # Translators: # ekari , 2013 # ekari , 2013-2014 +# Timo Kankare , 2016 # Tuomas Räsänen , 2015 msgid "" msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 07:51+0000\n" -"Last-Translator: Chris Warrick \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Finnish (http://www.transifex.com/ralsina/nikola/language/fi/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -101,7 +102,7 @@ msgid "Posts for {month} {year}" msgstr "Postauksia ajalle {month} {year}" msgid "Posts for {month} {day}, {year}" -msgstr "" +msgstr "Kirjoituksia ajalta {day}. {month}ta {year}" msgid "Nothing found." msgstr "Ei hakutuloksia." @@ -127,21 +128,24 @@ msgstr "(aktiivinen)" #. Default content for a new post msgid "Write your post here." -msgstr "" +msgstr "Kirjoita sisältö tähän." #. Default content for a new page msgid "Write your page here." -msgstr "" +msgstr "Kirjoita sisältö tähän." msgid "Uncategorized" -msgstr "" +msgstr "Luokittelematon" #. Used for feeds of any format msgid "Updates" -msgstr "" +msgstr "Päivitykset" msgid "Authors" -msgstr "" +msgstr "Kirjoittajat" msgid "Posts by %s" +msgstr "Postaukset kirjoittajalta %s" + +msgid "Toggle navigation" msgstr "" diff --git a/translations/nikola.messages/fil.po b/translations/nikola.messages/fil.po index 0c9a505..3a7ebed 100644 --- a/translations/nikola.messages/fil.po +++ b/translations/nikola.messages/fil.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -12,8 +12,8 @@ msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 07:50+0000\n" -"Last-Translator: Chris Warrick \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Filipino (http://www.transifex.com/ralsina/nikola/language/fil/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -142,3 +142,6 @@ msgstr "" msgid "Posts by %s" msgstr "" + +msgid "Toggle navigation" +msgstr "" diff --git a/translations/nikola.messages/fr.po b/translations/nikola.messages/fr.po index f47d347..6f0ae1e 100644 --- a/translations/nikola.messages/fr.po +++ b/translations/nikola.messages/fr.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -21,8 +21,8 @@ msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-10-14 09:26+0000\n" -"Last-Translator: Baptiste Darthenay \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: French (http://www.transifex.com/ralsina/nikola/language/fr/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -151,3 +151,6 @@ msgstr "Auteurs" msgid "Posts by %s" msgstr "Publiés par %s" + +msgid "Toggle navigation" +msgstr "" diff --git a/translations/nikola.messages/gl.po b/translations/nikola.messages/gl.po index 1b45a39..dbfee3f 100644 --- a/translations/nikola.messages/gl.po +++ b/translations/nikola.messages/gl.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -7,13 +7,14 @@ # Translators: # Translators: # Translators: +# Rubén Santos , 2015-2016 msgid "" msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 07:50+0000\n" -"Last-Translator: Chris Warrick \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Galician (http://www.transifex.com/ralsina/nikola/language/gl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -22,123 +23,126 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" msgid "More posts about %s" -msgstr "" +msgstr "Máis artigos sobre %s" #. Here your translation should NOT say English but the name of your language #. instead msgid "LANGUAGE" -msgstr "" +msgstr "Inglés" msgid "Tags" -msgstr "" +msgstr "Etiquetas" msgid "Categories" -msgstr "" +msgstr "Categorías" msgid "Tags and Categories" -msgstr "" +msgstr "Etiquetas e categorías" msgid "Comments" -msgstr "" +msgstr "Comentarios" msgid "Read more" -msgstr "" +msgstr "Ler máis" msgid "Posts about %s" -msgstr "" +msgstr "Artigos sobre %s" msgid "Next post" -msgstr "" +msgstr "Seguinte artigo" msgid "old posts, page %d" -msgstr "" +msgstr "Artigos vellos, páxina %d" msgid "page %d" -msgstr "" +msgstr "páxina %d" msgid "Source" -msgstr "" +msgstr "Fonte" #. Here your translation should NOT say English but the name of your language #. instead msgid "Read in English" -msgstr "" +msgstr "Ler en Inglés" msgid "Posts for year %s" -msgstr "" +msgstr "Artigos do ano %s" msgid "Newer posts" -msgstr "" +msgstr "Últimos artigos" msgid "Previous post" -msgstr "" +msgstr "Artigo anterior" msgid "Also available in:" -msgstr "" +msgstr "Tamén dispoñible en:" msgid "Languages:" -msgstr "" +msgstr "Linguas:" msgid "Original site" -msgstr "" +msgstr "Sitio orixinal" msgid "Older posts" -msgstr "" +msgstr "Artigos vellos" msgid "Archive" -msgstr "" +msgstr "Arquivo" msgid "Publication date" -msgstr "" +msgstr "Data de publicación" msgid "Posted:" -msgstr "" +msgstr "Publicado:" msgid "Posts for {month} {year}" -msgstr "" +msgstr "Artigos de {month} {year}" msgid "Posts for {month} {day}, {year}" -msgstr "" +msgstr "Artigos de {month} {day}, {year}" msgid "Nothing found." -msgstr "" +msgstr "Non se atopou nada." msgid "No posts found." -msgstr "" +msgstr "Non se atoparon artigos." msgid "RSS feed" -msgstr "" +msgstr "Sindicación RSS" msgid "%d min remaining to read" -msgstr "" +msgstr "%d min restantes para ler" msgid "Skip to main content" -msgstr "" +msgstr "Saltar ó contido principal" msgid "Subcategories:" -msgstr "" +msgstr "Subcategorías:" #. Used for active navigation links, for screen readers only msgid "(active)" -msgstr "" +msgstr "(activo)" #. Default content for a new post msgid "Write your post here." -msgstr "" +msgstr "Escribe o teu artigo aquí." #. Default content for a new page msgid "Write your page here." -msgstr "" +msgstr "Escribe a túa páxina aquí." msgid "Uncategorized" -msgstr "" +msgstr "Sen categoría" #. Used for feeds of any format msgid "Updates" -msgstr "" +msgstr "Actualizacións" msgid "Authors" -msgstr "" +msgstr "Autores" msgid "Posts by %s" +msgstr "Publicacións de %s" + +msgid "Toggle navigation" msgstr "" diff --git a/translations/nikola.messages/he.po b/translations/nikola.messages/he.po new file mode 100644 index 0000000..ff7cd52 --- /dev/null +++ b/translations/nikola.messages/he.po @@ -0,0 +1,148 @@ +# Messages in Nikola +# Copyright (C) 2012-2016 +# This file is distributed under the same license as the Nikola package. +# +# Translators: +# Translators: +# Translators: +# Translators: +# Translators: +# Yitzhak Bar Geva , 2016 +msgid "" +msgstr "" +"Project-Id-Version: Nikola\n" +"Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" +"POT-Creation-Date: 2013-03-15 12:24-0300\n" +"PO-Revision-Date: 2016-09-27 15:27+0000\n" +"Last-Translator: Yitzhak Bar Geva \n" +"Language-Team: Hebrew (http://www.transifex.com/ralsina/nikola/language/he/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: he\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "More posts about %s" +msgstr "עוד פוסטים אודות s%" + +#. Here your translation should NOT say English but the name of your language +#. instead +msgid "LANGUAGE" +msgstr "אנגלית" + +msgid "Tags" +msgstr "תגים" + +msgid "Categories" +msgstr "קטגוריות" + +msgid "Tags and Categories" +msgstr "תגים וקטגוריות" + +msgid "Comments" +msgstr "הערות" + +msgid "Read more" +msgstr "קרא עוד" + +msgid "Posts about %s" +msgstr "פוסטים אודות s%" + +msgid "Next post" +msgstr "לפוסט הבא" + +msgid "old posts, page %d" +msgstr "פוסטים קודמים, דף d%" + +msgid "page %d" +msgstr "עמוד d%" + +msgid "Source" +msgstr "מקור" + +#. Here your translation should NOT say English but the name of your language +#. instead +msgid "Read in English" +msgstr "קרא באנגלית" + +msgid "Posts for year %s" +msgstr "פוסטים לשנת s%" + +msgid "Newer posts" +msgstr "פוסטים חדשים" + +msgid "Previous post" +msgstr "פוסט הקודם" + +msgid "Also available in:" +msgstr "זמין גם ב:" + +msgid "Languages:" +msgstr "שפות:" + +msgid "Original site" +msgstr "אתר המקורי" + +msgid "Older posts" +msgstr "פוסטים ישנים" + +msgid "Archive" +msgstr "ארכיב" + +msgid "Publication date" +msgstr "תאריך פרסום" + +msgid "Posted:" +msgstr "פורסם:" + +msgid "Posts for {month} {year}" +msgstr "פוסטים עבוד {year}{month}" + +msgid "Posts for {month} {day}, {year}" +msgstr "פוסטים עבוד {year},{day}{month}" + +msgid "Nothing found." +msgstr "לא נמצא" + +msgid "No posts found." +msgstr "לא נמצאו פוסטים" + +msgid "RSS feed" +msgstr "פיד RSS" + +msgid "%d min remaining to read" +msgstr "d% דקות נותרים לסיום קריאה" + +msgid "Skip to main content" +msgstr "דלג לתוכן ראשי" + +msgid "Subcategories:" +msgstr "תתי קטגוריות:" + +#. Used for active navigation links, for screen readers only +msgid "(active)" +msgstr "(פעיל)" + +#. Default content for a new post +msgid "Write your post here." +msgstr "תכתוב את הפוסט שלך פה." + +#. Default content for a new page +msgid "Write your page here." +msgstr "תכתוב את העמוד שלך פה." + +msgid "Uncategorized" +msgstr "לא משויך לקטגוריה" + +#. Used for feeds of any format +msgid "Updates" +msgstr "עדכונים" + +msgid "Authors" +msgstr "מחברים" + +msgid "Posts by %s" +msgstr "פוסטים ע״י s%" + +msgid "Toggle navigation" +msgstr "החלף מצב ניווט" diff --git a/translations/nikola.messages/hi.po b/translations/nikola.messages/hi.po index a254f14..7479c03 100644 --- a/translations/nikola.messages/hi.po +++ b/translations/nikola.messages/hi.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -7,14 +7,14 @@ # Translators: # Translators: # Translators: -# seanpue , 2014-2015 +# seanpue , 2014-2016 msgid "" msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 07:51+0000\n" -"Last-Translator: Chris Warrick \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Hindi (http://www.transifex.com/ralsina/nikola/language/hi/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -117,29 +117,32 @@ msgid "Skip to main content" msgstr "मुख्य सामग्री पर जाएँ" msgid "Subcategories:" -msgstr "" +msgstr "उपश्रेणी" #. Used for active navigation links, for screen readers only msgid "(active)" -msgstr "" +msgstr "(सक्रिय)" #. Default content for a new post msgid "Write your post here." -msgstr "" +msgstr "अपनी पोस्ट यहाँ लिखिए" #. Default content for a new page msgid "Write your page here." -msgstr "" +msgstr "अपना पेज यहाँ लिखिए" msgid "Uncategorized" -msgstr "" +msgstr "बिना श्रेणी" #. Used for feeds of any format msgid "Updates" -msgstr "" +msgstr "अपडेट्स" msgid "Authors" -msgstr "" +msgstr "लेखक" msgid "Posts by %s" +msgstr "%s की पोस्टें" + +msgid "Toggle navigation" msgstr "" diff --git a/translations/nikola.messages/hr.po b/translations/nikola.messages/hr.po index facd1b8..c216ba5 100644 --- a/translations/nikola.messages/hr.po +++ b/translations/nikola.messages/hr.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -7,6 +7,7 @@ # Translators: # Translators: # Translators: +# Gour , 2016 # Saša Savić , 2015 # tty, 2013 # tty, 2013-2014 @@ -15,8 +16,8 @@ msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 07:51+0000\n" -"Last-Translator: Chris Warrick \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Croatian (http://www.transifex.com/ralsina/nikola/language/hr/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -101,7 +102,7 @@ msgid "Posts for {month} {year}" msgstr "Postovi za {month} {year}" msgid "Posts for {month} {day}, {year}" -msgstr "" +msgstr "Objave za {month} {day}, {year}" msgid "Nothing found." msgstr "Nema ničeg." @@ -127,21 +128,24 @@ msgstr "(aktivno)" #. Default content for a new post msgid "Write your post here." -msgstr "" +msgstr "Napiši svoju objavu ovdje" #. Default content for a new page msgid "Write your page here." -msgstr "" +msgstr "Napiši svoju stranicu ovdje" msgid "Uncategorized" -msgstr "" +msgstr "Nekategorizirano" #. Used for feeds of any format msgid "Updates" -msgstr "" +msgstr "Nadopune" msgid "Authors" -msgstr "" +msgstr "Autori" msgid "Posts by %s" +msgstr "Objave od %s" + +msgid "Toggle navigation" msgstr "" diff --git a/translations/nikola.messages/hu.po b/translations/nikola.messages/hu.po index 9324874..9f48608 100644 --- a/translations/nikola.messages/hu.po +++ b/translations/nikola.messages/hu.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -15,8 +15,8 @@ msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-10-14 07:32+0000\n" -"Last-Translator: Chris Warrick \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Hungarian (http://www.transifex.com/ralsina/nikola/language/hu/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -145,3 +145,6 @@ msgstr "Szerzők" msgid "Posts by %s" msgstr "Bejegyzések %s által" + +msgid "Toggle navigation" +msgstr "" diff --git a/translations/nikola.messages/id.po b/translations/nikola.messages/id.po index 6a76d96..0c8aed7 100644 --- a/translations/nikola.messages/id.po +++ b/translations/nikola.messages/id.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -13,8 +13,8 @@ msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 07:51+0000\n" -"Last-Translator: Chris Warrick \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Indonesian (http://www.transifex.com/ralsina/nikola/language/id/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -28,7 +28,7 @@ msgstr "Lebih banyak tulisan tentang %s" #. Here your translation should NOT say English but the name of your language #. instead msgid "LANGUAGE" -msgstr "Inggris" +msgstr "Bahasa Indonesia" msgid "Tags" msgstr "Tag" @@ -63,7 +63,7 @@ msgstr "Sumber" #. Here your translation should NOT say English but the name of your language #. instead msgid "Read in English" -msgstr "Baca dalam bahasa Inggris" +msgstr "Baca dalam Bahasa Indonesia" msgid "Posts for year %s" msgstr "Tulisan untuk tahun %s" @@ -125,21 +125,24 @@ msgstr "(aktif)" #. Default content for a new post msgid "Write your post here." -msgstr "" +msgstr "Tulis tulisan Anda disini." #. Default content for a new page msgid "Write your page here." -msgstr "" +msgstr "Tulis halaman Anda disini." msgid "Uncategorized" -msgstr "" +msgstr "Tanpa kategori" #. Used for feeds of any format msgid "Updates" -msgstr "" +msgstr "Update" msgid "Authors" -msgstr "" +msgstr "Penulis" msgid "Posts by %s" +msgstr "Tulisan oleh %s" + +msgid "Toggle navigation" msgstr "" diff --git a/translations/nikola.messages/it.po b/translations/nikola.messages/it.po index 0f1cda1..5d9b9ab 100644 --- a/translations/nikola.messages/it.po +++ b/translations/nikola.messages/it.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -19,8 +19,8 @@ msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 09:26+0000\n" -"Last-Translator: Nicola Larosa \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Italian (http://www.transifex.com/ralsina/nikola/language/it/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -149,3 +149,6 @@ msgstr "Autori" msgid "Posts by %s" msgstr "Articoli di %s" + +msgid "Toggle navigation" +msgstr "" diff --git a/translations/nikola.messages/ja.po b/translations/nikola.messages/ja.po index 09d6eac..0f8dba0 100644 --- a/translations/nikola.messages/ja.po +++ b/translations/nikola.messages/ja.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -20,8 +20,8 @@ msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-10-17 14:22+0000\n" -"Last-Translator: IGARASHI Masanao \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Japanese (http://www.transifex.com/ralsina/nikola/language/ja/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -150,3 +150,6 @@ msgstr "著者一覧" msgid "Posts by %s" msgstr "%sの文書一覧" + +msgid "Toggle navigation" +msgstr "" diff --git a/translations/nikola.messages/ko.po b/translations/nikola.messages/ko.po index fd34fcb..6c09b68 100644 --- a/translations/nikola.messages/ko.po +++ b/translations/nikola.messages/ko.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -8,13 +8,14 @@ # Translators: # Translators: # Dong Geun , 2014 +# Yi donghoon , 2016 msgid "" msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 07:51+0000\n" -"Last-Translator: Chris Warrick \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Korean (http://www.transifex.com/ralsina/nikola/language/ko/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -75,7 +76,7 @@ msgid "Previous post" msgstr "이전 포스트" msgid "Also available in:" -msgstr "" +msgstr "이곳에서도 가능함:" msgid "Languages:" msgstr "언어:" @@ -93,13 +94,13 @@ msgid "Publication date" msgstr "발간일" msgid "Posted:" -msgstr "" +msgstr "작성됨:" msgid "Posts for {month} {year}" msgstr "{year}년 {month}월에 쓴 포스트" msgid "Posts for {month} {day}, {year}" -msgstr "" +msgstr " {year}년 {month}월 {day}일에 작성된 포스트" msgid "Nothing found." msgstr "검색 결과 없음." @@ -117,29 +118,32 @@ msgid "Skip to main content" msgstr "주 콘텐츠로 바로가기" msgid "Subcategories:" -msgstr "" +msgstr "서브 카테고리" #. Used for active navigation links, for screen readers only msgid "(active)" -msgstr "" +msgstr "(활성됨)" #. Default content for a new post msgid "Write your post here." -msgstr "" +msgstr "이곳에 글을 작성하세요." #. Default content for a new page msgid "Write your page here." -msgstr "" +msgstr "여기에 페이지를 작성하세요." msgid "Uncategorized" -msgstr "" +msgstr "카테고리 없음" #. Used for feeds of any format msgid "Updates" -msgstr "" +msgstr "업데이트" msgid "Authors" -msgstr "" +msgstr "작성자" msgid "Posts by %s" +msgstr "%s에 의해 작성된 글" + +msgid "Toggle navigation" msgstr "" diff --git a/translations/nikola.messages/lt.po b/translations/nikola.messages/lt.po new file mode 100644 index 0000000..8ead2a2 --- /dev/null +++ b/translations/nikola.messages/lt.po @@ -0,0 +1,148 @@ +# Messages in Nikola +# Copyright (C) 2012-2016 +# This file is distributed under the same license as the Nikola package. +# +# Translators: +# Translators: +# Translators: +# Translators: +# Translators: +# Antanas Lasys , 2016 +msgid "" +msgstr "" +"Project-Id-Version: Nikola\n" +"Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" +"POT-Creation-Date: 2013-03-15 12:24-0300\n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" +"Language-Team: Lithuanian (http://www.transifex.com/ralsina/nikola/language/lt/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: lt\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +msgid "More posts about %s" +msgstr "Daugiau įrašų apie %s" + +#. Here your translation should NOT say English but the name of your language +#. instead +msgid "LANGUAGE" +msgstr "Lietuvių" + +msgid "Tags" +msgstr "Žymės" + +msgid "Categories" +msgstr "Kategorijos" + +msgid "Tags and Categories" +msgstr "Žymės ir Kategorijos" + +msgid "Comments" +msgstr "Komentarai" + +msgid "Read more" +msgstr "Skaityti daugiau" + +msgid "Posts about %s" +msgstr "Įrašai apie %s" + +msgid "Next post" +msgstr "Sekantis įrašas" + +msgid "old posts, page %d" +msgstr "seni įrašai, %d puslapis" + +msgid "page %d" +msgstr "%d puslapis" + +msgid "Source" +msgstr "Pirminis tekstas" + +#. Here your translation should NOT say English but the name of your language +#. instead +msgid "Read in English" +msgstr "Skaityti lietuviškai" + +msgid "Posts for year %s" +msgstr "%s metų įrašai" + +msgid "Newer posts" +msgstr "Naujesni įrašai" + +msgid "Previous post" +msgstr "Ankstesnis įrašas" + +msgid "Also available in:" +msgstr "Taip pat turimas šiomis kalbomis:" + +msgid "Languages:" +msgstr "Kalbos:" + +msgid "Original site" +msgstr "Originalo svetainė" + +msgid "Older posts" +msgstr "Senesni įrašai" + +msgid "Archive" +msgstr "Archyvas" + +msgid "Publication date" +msgstr "Publikavimo data" + +msgid "Posted:" +msgstr "Įrašyta:" + +msgid "Posts for {month} {year}" +msgstr "{month} {year} įrašai" + +msgid "Posts for {month} {day}, {year}" +msgstr "{month} {day}, {year} įrašai" + +msgid "Nothing found." +msgstr "Nieko nerasta." + +msgid "No posts found." +msgstr "Įrašų nerasta." + +msgid "RSS feed" +msgstr "RSS srautas" + +msgid "%d min remaining to read" +msgstr "liko %d min skaitymo" + +msgid "Skip to main content" +msgstr "Pereiti į pagrindinį turinį" + +msgid "Subcategories:" +msgstr "Subkategorijos:" + +#. Used for active navigation links, for screen readers only +msgid "(active)" +msgstr "(aktyvi)" + +#. Default content for a new post +msgid "Write your post here." +msgstr "Čia rašykite įrašo tekstą." + +#. Default content for a new page +msgid "Write your page here." +msgstr "Čia rašykite puslapio tekstą." + +msgid "Uncategorized" +msgstr "Nekategorizuota" + +#. Used for feeds of any format +msgid "Updates" +msgstr "Atnaujinimai" + +msgid "Authors" +msgstr "Autoriai" + +msgid "Posts by %s" +msgstr "Autoriaus %s įrašai" + +msgid "Toggle navigation" +msgstr "" diff --git a/translations/nikola.messages/nb.po b/translations/nikola.messages/nb.po index 2b0c5e0..4257c50 100644 --- a/translations/nikola.messages/nb.po +++ b/translations/nikola.messages/nb.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -14,8 +14,8 @@ msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 07:51+0000\n" -"Last-Translator: Chris Warrick \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Norwegian Bokmål (http://www.transifex.com/ralsina/nikola/language/nb/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -144,3 +144,6 @@ msgstr "" msgid "Posts by %s" msgstr "" + +msgid "Toggle navigation" +msgstr "" diff --git a/translations/nikola.messages/nl.po b/translations/nikola.messages/nl.po index c7c05b3..2a92b5d 100644 --- a/translations/nikola.messages/nl.po +++ b/translations/nikola.messages/nl.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -8,13 +8,13 @@ # Translators: # Translators: # Joes Staal , 2013 -# Joes Staal , 2013-2015 +# Joes Staal , 2013-2016 msgid "" msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 11:04+0000\n" +"PO-Revision-Date: 2016-09-25 16:13+0000\n" "Last-Translator: Joes Staal \n" "Language-Team: Dutch (http://www.transifex.com/ralsina/nikola/language/nl/)\n" "MIME-Version: 1.0\n" @@ -144,3 +144,6 @@ msgstr "Auteurs" msgid "Posts by %s" msgstr "Berichten van %s" + +msgid "Toggle navigation" +msgstr "Toggle navigatie" diff --git a/translations/nikola.messages/pa.po b/translations/nikola.messages/pa.po index dd137f0..d9625b2 100644 --- a/translations/nikola.messages/pa.po +++ b/translations/nikola.messages/pa.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -7,14 +7,15 @@ # Translators: # Translators: # Translators: +# seanpue , 2016 # jasdeep , 2015 msgid "" msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 07:51+0000\n" -"Last-Translator: Chris Warrick \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Panjabi (Punjabi) (http://www.transifex.com/ralsina/nikola/language/pa/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -28,7 +29,7 @@ msgstr "%s ਬਾਰੇ ਹੋਰ ਲਿਖਤਾਂ" #. Here your translation should NOT say English but the name of your language #. instead msgid "LANGUAGE" -msgstr "ਅੰਗਰੇਜ਼ੀ" +msgstr "ਪੰਜਾਬੀ " msgid "Tags" msgstr "ਟੈਗ" @@ -63,7 +64,7 @@ msgstr "ਮੂਲ" #. Here your translation should NOT say English but the name of your language #. instead msgid "Read in English" -msgstr "ਅੰਗਰੇਜ਼ੀ ਵਿੱਚ ਪੜ੍ਹੋ" +msgstr "ਪੰਜਾਬੀ ਵਿੱਚ ਪੜ੍ਹੋ" msgid "Posts for year %s" msgstr "ਸਾਲ %s ਦੀਆਂ ਲਿਖਤਾਂ" @@ -132,14 +133,17 @@ msgid "Write your page here." msgstr "ਆਪਣਾ ਸਫ਼ਾ ਏਥੇ ਲਿਖੋ |" msgid "Uncategorized" -msgstr "" +msgstr "ਇਤਾਹਾਸ" #. Used for feeds of any format msgid "Updates" -msgstr "" +msgstr "ਅੱਪਡੇਟਸ" msgid "Authors" -msgstr "" +msgstr "ਲੇਖਕ" msgid "Posts by %s" +msgstr "%s ਦੀ ਲਿਖਤਾਂ" + +msgid "Toggle navigation" msgstr "" diff --git a/translations/nikola.messages/pl.po b/translations/nikola.messages/pl.po index 7770beb..07bfd9a 100644 --- a/translations/nikola.messages/pl.po +++ b/translations/nikola.messages/pl.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -7,7 +7,7 @@ # Translators: # Translators: # Translators: -# Chris Warrick , 2013-2015 +# Chris Warrick , 2013-2016 # Chris Warrick , 2013 # Chris Warrick , 2013 # Roberto Alsina , 2013 @@ -17,7 +17,7 @@ msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 07:55+0000\n" +"PO-Revision-Date: 2016-09-25 07:43+0000\n" "Last-Translator: Chris Warrick \n" "Language-Team: Polish (http://www.transifex.com/ralsina/nikola/language/pl/)\n" "MIME-Version: 1.0\n" @@ -147,3 +147,6 @@ msgstr "Autorzy" msgid "Posts by %s" msgstr "Posty autora %s" + +msgid "Toggle navigation" +msgstr "Pokaż/ukryj menu" diff --git a/translations/nikola.messages/pt.po b/translations/nikola.messages/pt.po index ec16a60..2f6cd38 100644 --- a/translations/nikola.messages/pt.po +++ b/translations/nikola.messages/pt.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -13,8 +13,8 @@ msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 07:51+0000\n" -"Last-Translator: Chris Warrick \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Portuguese (http://www.transifex.com/ralsina/nikola/language/pt/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -143,3 +143,6 @@ msgstr "" msgid "Posts by %s" msgstr "" + +msgid "Toggle navigation" +msgstr "" diff --git a/translations/nikola.messages/pt_BR.po b/translations/nikola.messages/pt_BR.po index f3d1b6f..a59383d 100644 --- a/translations/nikola.messages/pt_BR.po +++ b/translations/nikola.messages/pt_BR.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -15,13 +15,14 @@ # Roberto Alsina , 2013 # Roberto Alsina , 2013 # eduardo , 2013 +# Tiago Nunes , 2016 msgid "" msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 07:51+0000\n" -"Last-Translator: Chris Warrick \n" +"PO-Revision-Date: 2016-10-11 16:10+0000\n" +"Last-Translator: Tiago Nunes \n" "Language-Team: Portuguese (Brazil) (http://www.transifex.com/ralsina/nikola/language/pt_BR/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -124,29 +125,32 @@ msgid "Skip to main content" msgstr "Pular para o conteúdo principal" msgid "Subcategories:" -msgstr "" +msgstr "Subcategorias:" #. Used for active navigation links, for screen readers only msgid "(active)" -msgstr "" +msgstr "(ativo)" #. Default content for a new post msgid "Write your post here." -msgstr "" +msgstr "Escreva o seu comentário aqui." #. Default content for a new page msgid "Write your page here." -msgstr "" +msgstr "Insira a sua página aqui" msgid "Uncategorized" -msgstr "" +msgstr "Sem categoria" #. Used for feeds of any format msgid "Updates" -msgstr "" +msgstr "Atualizações" msgid "Authors" -msgstr "" +msgstr "Autores" msgid "Posts by %s" -msgstr "" +msgstr "Postado por %s" + +msgid "Toggle navigation" +msgstr "Alternar navegação" diff --git a/translations/nikola.messages/ru.po b/translations/nikola.messages/ru.po index a40aea6..a00089f 100644 --- a/translations/nikola.messages/ru.po +++ b/translations/nikola.messages/ru.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -7,7 +7,8 @@ # Translators: # Translators: # Translators: -# Dmitry Verkhoturov , 2014 +# Dmitry Verkhoturov , 2014 +# Evgeny [PTM] , 2016 # Max Arnold , 2013 # Max Arnold , 2013 # Oleksandr Fedorov, 2015 @@ -18,8 +19,8 @@ msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 07:51+0000\n" -"Last-Translator: Chris Warrick \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Russian (http://www.transifex.com/ralsina/nikola/language/ru/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -137,14 +138,17 @@ msgid "Write your page here." msgstr "Создайте Вашу страницу здесь." msgid "Uncategorized" -msgstr "" +msgstr "Несортированное" #. Used for feeds of any format msgid "Updates" -msgstr "" +msgstr "Обновления" msgid "Authors" -msgstr "" +msgstr "Разработчики" msgid "Posts by %s" +msgstr "Запись %s" + +msgid "Toggle navigation" msgstr "" diff --git a/translations/nikola.messages/si_LK.po b/translations/nikola.messages/si_LK.po index 63ff497..84432f9 100644 --- a/translations/nikola.messages/si_LK.po +++ b/translations/nikola.messages/si_LK.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -12,8 +12,8 @@ msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 07:50+0000\n" -"Last-Translator: Chris Warrick \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Sinhala (Sri Lanka) (http://www.transifex.com/ralsina/nikola/language/si_LK/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -142,3 +142,6 @@ msgstr "" msgid "Posts by %s" msgstr "" + +msgid "Toggle navigation" +msgstr "" diff --git a/translations/nikola.messages/sk.po b/translations/nikola.messages/sk.po index 86d4d89..a13c78b 100644 --- a/translations/nikola.messages/sk.po +++ b/translations/nikola.messages/sk.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -15,8 +15,8 @@ msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 07:51+0000\n" -"Last-Translator: Chris Warrick \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Slovak (http://www.transifex.com/ralsina/nikola/language/sk/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -145,3 +145,6 @@ msgstr "" msgid "Posts by %s" msgstr "" + +msgid "Toggle navigation" +msgstr "" diff --git a/translations/nikola.messages/sl.po b/translations/nikola.messages/sl.po index 82eba33..bb7241f 100644 --- a/translations/nikola.messages/sl.po +++ b/translations/nikola.messages/sl.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -14,8 +14,8 @@ msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 07:51+0000\n" -"Last-Translator: Chris Warrick \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Slovenian (http://www.transifex.com/ralsina/nikola/language/sl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -144,3 +144,6 @@ msgstr "" msgid "Posts by %s" msgstr "" + +msgid "Toggle navigation" +msgstr "" diff --git a/translations/nikola.messages/sq.po b/translations/nikola.messages/sq.po new file mode 100644 index 0000000..fa65b24 --- /dev/null +++ b/translations/nikola.messages/sq.po @@ -0,0 +1,148 @@ +# Messages in Nikola +# Copyright (C) 2012-2016 +# This file is distributed under the same license as the Nikola package. +# +# Translators: +# Translators: +# Translators: +# Translators: +# Translators: +# Vango Stavro , 2016 +msgid "" +msgstr "" +"Project-Id-Version: Nikola\n" +"Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" +"POT-Creation-Date: 2013-03-15 12:24-0300\n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" +"Language-Team: Albanian (http://www.transifex.com/ralsina/nikola/language/sq/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: sq\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "More posts about %s" +msgstr "Më shumë postime rreth %s" + +#. Here your translation should NOT say English but the name of your language +#. instead +msgid "LANGUAGE" +msgstr "Shqip" + +msgid "Tags" +msgstr "Etiketa" + +msgid "Categories" +msgstr "Kategori" + +msgid "Tags and Categories" +msgstr "Etiketa dhe Kategori" + +msgid "Comments" +msgstr "Komente" + +msgid "Read more" +msgstr "Lexo më shumë" + +msgid "Posts about %s" +msgstr "Postime rreth %s" + +msgid "Next post" +msgstr "Postimi i rradhës" + +msgid "old posts, page %d" +msgstr "Postime të kaluara, faqe %d" + +msgid "page %d" +msgstr "faqe %d" + +msgid "Source" +msgstr "Burim" + +#. Here your translation should NOT say English but the name of your language +#. instead +msgid "Read in English" +msgstr "Lexo në Shqip" + +msgid "Posts for year %s" +msgstr "Postime për vitin %s" + +msgid "Newer posts" +msgstr "Postime më të reja" + +msgid "Previous post" +msgstr "Postim i kaluar" + +msgid "Also available in:" +msgstr "Gjithashtu e disponueshme në:" + +msgid "Languages:" +msgstr "Gjuhë:" + +msgid "Original site" +msgstr "Faqja origjinale" + +msgid "Older posts" +msgstr "Postime më të vjetra" + +msgid "Archive" +msgstr "Arkiva" + +msgid "Publication date" +msgstr "Data e publikimit" + +msgid "Posted:" +msgstr "Postuar:" + +msgid "Posts for {month} {year}" +msgstr "Postime për {month} {year}" + +msgid "Posts for {month} {day}, {year}" +msgstr "Postime për {month} {day}, {year}" + +msgid "Nothing found." +msgstr "Nuk është gjetur asgjë." + +msgid "No posts found." +msgstr "Nuk është gjetur asnjë post." + +msgid "RSS feed" +msgstr "Furnizim RSS" + +msgid "%d min remaining to read" +msgstr "%d min ngelen për tu lexuar" + +msgid "Skip to main content" +msgstr "Shko tek përmbajtja kryesore" + +msgid "Subcategories:" +msgstr "Nënkategori:" + +#. Used for active navigation links, for screen readers only +msgid "(active)" +msgstr "(aktiv)" + +#. Default content for a new post +msgid "Write your post here." +msgstr "Shkruaj postin tënd këtu." + +#. Default content for a new page +msgid "Write your page here." +msgstr "Shkruaj faqen tënde këtu." + +msgid "Uncategorized" +msgstr "E pa kategorizuar" + +#. Used for feeds of any format +msgid "Updates" +msgstr "Përditësime" + +msgid "Authors" +msgstr "Autorë" + +msgid "Posts by %s" +msgstr "Postime nga %s" + +msgid "Toggle navigation" +msgstr "" diff --git a/translations/nikola.messages/sr.po b/translations/nikola.messages/sr.po index 0223753..4bbd704 100644 --- a/translations/nikola.messages/sr.po +++ b/translations/nikola.messages/sr.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -14,8 +14,8 @@ msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 07:51+0000\n" -"Last-Translator: Chris Warrick \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Serbian (http://www.transifex.com/ralsina/nikola/language/sr/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -144,3 +144,6 @@ msgstr "" msgid "Posts by %s" msgstr "" + +msgid "Toggle navigation" +msgstr "" diff --git a/translations/nikola.messages/sr@latin.po b/translations/nikola.messages/sr@latin.po index 2ff2a3d..c87de46 100644 --- a/translations/nikola.messages/sr@latin.po +++ b/translations/nikola.messages/sr@latin.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -13,8 +13,8 @@ msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 07:51+0000\n" -"Last-Translator: Chris Warrick \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Serbian (Latin) (http://www.transifex.com/ralsina/nikola/language/sr@latin/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -143,3 +143,6 @@ msgstr "" msgid "Posts by %s" msgstr "" + +msgid "Toggle navigation" +msgstr "" diff --git a/translations/nikola.messages/sv.po b/translations/nikola.messages/sv.po index 69526e0..345b7df 100644 --- a/translations/nikola.messages/sv.po +++ b/translations/nikola.messages/sv.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -15,8 +15,8 @@ msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 07:51+0000\n" -"Last-Translator: Chris Warrick \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Swedish (http://www.transifex.com/ralsina/nikola/language/sv/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -145,3 +145,6 @@ msgstr "" msgid "Posts by %s" msgstr "" + +msgid "Toggle navigation" +msgstr "" diff --git a/translations/nikola.messages/te.po b/translations/nikola.messages/te.po new file mode 100644 index 0000000..c4018db --- /dev/null +++ b/translations/nikola.messages/te.po @@ -0,0 +1,148 @@ +# Messages in Nikola +# Copyright (C) 2012-2016 +# This file is distributed under the same license as the Nikola package. +# +# Translators: +# Translators: +# Translators: +# Translators: +# Translators: +# chillar anand , 2016 +msgid "" +msgstr "" +"Project-Id-Version: Nikola\n" +"Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" +"POT-Creation-Date: 2013-03-15 12:24-0300\n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" +"Language-Team: Telugu (http://www.transifex.com/ralsina/nikola/language/te/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: te\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "More posts about %s" +msgstr "%s గూర్చి మరిన్ని టపాలు" + +#. Here your translation should NOT say English but the name of your language +#. instead +msgid "LANGUAGE" +msgstr "ఆంగ్లం " + +msgid "Tags" +msgstr "ట్యాగ్లు" + +msgid "Categories" +msgstr "వర్గాలు" + +msgid "Tags and Categories" +msgstr "ట్యాగ్లు మరియు వర్గాలు" + +msgid "Comments" +msgstr "వ్యాఖ్యలు" + +msgid "Read more" +msgstr "ఇంకా చదవండి" + +msgid "Posts about %s" +msgstr "%s గూర్చి టపాలు" + +msgid "Next post" +msgstr "తరువాత టపా" + +msgid "old posts, page %d" +msgstr "పాత టపాలు, పేజీ %d" + +msgid "page %d" +msgstr "పేజీ %d" + +msgid "Source" +msgstr "మూలం" + +#. Here your translation should NOT say English but the name of your language +#. instead +msgid "Read in English" +msgstr "ఆంగ్లంలో చదవండి" + +msgid "Posts for year %s" +msgstr "%s సంవత్సర టపాలు" + +msgid "Newer posts" +msgstr "కొత్త టపాలు" + +msgid "Previous post" +msgstr "మునుపటి టపా" + +msgid "Also available in:" +msgstr "ఇందులో కూడా లభించును:" + +msgid "Languages:" +msgstr "భాషలు:" + +msgid "Original site" +msgstr "వాస్తవ సైట్" + +msgid "Older posts" +msgstr "పాత టపాలు" + +msgid "Archive" +msgstr "అభిలేఖలు " + +msgid "Publication date" +msgstr "ప్రచురణ తేదీ" + +msgid "Posted:" +msgstr "ప్రచురుంచిన తేదీ:" + +msgid "Posts for {month} {year}" +msgstr "" + +msgid "Posts for {month} {day}, {year}" +msgstr "" + +msgid "Nothing found." +msgstr "" + +msgid "No posts found." +msgstr "" + +msgid "RSS feed" +msgstr "RSS ఫీడ్" + +msgid "%d min remaining to read" +msgstr "%d నిమిషాలు చదవడానికి కావలెను " + +msgid "Skip to main content" +msgstr "ప్రధాన విషయానికి వెళ్ళు" + +msgid "Subcategories:" +msgstr "ఉపవర్గాలు:" + +#. Used for active navigation links, for screen readers only +msgid "(active)" +msgstr "(క్రియాశీల)" + +#. Default content for a new post +msgid "Write your post here." +msgstr "ఇక్కడ మీ టపా ను వ్రాయండి." + +#. Default content for a new page +msgid "Write your page here." +msgstr "మీ పేజీ ఇక్కడ రాయండి." + +msgid "Uncategorized" +msgstr "వర్గీకరించని" + +#. Used for feeds of any format +msgid "Updates" +msgstr "నవీకరణలు" + +msgid "Authors" +msgstr "రచయితలు" + +msgid "Posts by %s" +msgstr "%s యొక్క టపాలు" + +msgid "Toggle navigation" +msgstr "" diff --git a/translations/nikola.messages/tl.po b/translations/nikola.messages/tl.po index 88f4866..91a2f92 100644 --- a/translations/nikola.messages/tl.po +++ b/translations/nikola.messages/tl.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -13,8 +13,8 @@ msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 07:50+0000\n" -"Last-Translator: Chris Warrick \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Tagalog (http://www.transifex.com/ralsina/nikola/language/tl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -143,3 +143,6 @@ msgstr "" msgid "Posts by %s" msgstr "" + +msgid "Toggle navigation" +msgstr "" diff --git a/translations/nikola.messages/tr.po b/translations/nikola.messages/tr.po index 2df6246..552da21 100644 --- a/translations/nikola.messages/tr.po +++ b/translations/nikola.messages/tr.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -7,16 +7,16 @@ # Translators: # Translators: # Translators: -# Batuhan Büyükgüzel , 2013-2015 -# Caner Başaran , 2013-2014 +# Batuhan Büyükgüzel , 2013-2016 +# Caner Başaran , 2013-2014,2016 # Caner Başaran , 2013 msgid "" msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 07:51+0000\n" -"Last-Translator: Chris Warrick \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Turkish (http://www.transifex.com/ralsina/nikola/language/tr/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -25,7 +25,7 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n > 1);\n" msgid "More posts about %s" -msgstr "%s ilgili diğer yazılar" +msgstr "%s hakkında diğer yazılar" #. Here your translation should NOT say English but the name of your language #. instead @@ -101,7 +101,7 @@ msgid "Posts for {month} {year}" msgstr "{month} {year} göre yazılar" msgid "Posts for {month} {day}, {year}" -msgstr "{month} {day}, {year} 'den beri olan yazılar" +msgstr "{month} {day}, {year} tarihinden itibaren yazılar" msgid "Nothing found." msgstr "Hiçbir şey bulunamadı." @@ -119,29 +119,32 @@ msgid "Skip to main content" msgstr "Ana içeriğe geç" msgid "Subcategories:" -msgstr "" +msgstr "Alt kategoriler:" #. Used for active navigation links, for screen readers only msgid "(active)" -msgstr "" +msgstr "(etkin)" #. Default content for a new post msgid "Write your post here." -msgstr "" +msgstr "Yazınızı buraya yazın." #. Default content for a new page msgid "Write your page here." -msgstr "" +msgstr "Sayfanızı buraya yazın." msgid "Uncategorized" -msgstr "" +msgstr "Kategorisiz" #. Used for feeds of any format msgid "Updates" -msgstr "" +msgstr "Güncellemeler" msgid "Authors" -msgstr "" +msgstr "Yazarlar" msgid "Posts by %s" +msgstr "%s tarafından yazılanlar" + +msgid "Toggle navigation" msgstr "" diff --git a/translations/nikola.messages/uk.po b/translations/nikola.messages/uk.po index 85fbba5..bafa359 100644 --- a/translations/nikola.messages/uk.po +++ b/translations/nikola.messages/uk.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -8,14 +8,15 @@ # Translators: # Translators: # Alex Fedorov , 2015 +# Oleh Prypin , 2016 # Oleksandr Fedorov, 2015 msgid "" msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 07:51+0000\n" -"Last-Translator: Chris Warrick \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Ukrainian (http://www.transifex.com/ralsina/nikola/language/uk/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -133,14 +134,17 @@ msgid "Write your page here." msgstr "Напишіть Вашу сторінку тут." msgid "Uncategorized" -msgstr "" +msgstr "Без категорії" #. Used for feeds of any format msgid "Updates" -msgstr "" +msgstr "Підписки" msgid "Authors" -msgstr "" +msgstr "Автори" msgid "Posts by %s" +msgstr "Статті %s" + +msgid "Toggle navigation" msgstr "" diff --git a/translations/nikola.messages/ur.po b/translations/nikola.messages/ur.po index f4ef010..5de4dc1 100644 --- a/translations/nikola.messages/ur.po +++ b/translations/nikola.messages/ur.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -7,7 +7,7 @@ # Translators: # Translators: # Translators: -# seanpue , 2014 +# seanpue , 2014,2016 # saadat, 2013 # Saadat M. , 2013-2014 # Saadat M. , 2015 @@ -17,8 +17,8 @@ msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-09-23 04:19+0000\n" -"Last-Translator: Saadat M. \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Urdu (http://www.transifex.com/ralsina/nikola/language/ur/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -136,7 +136,7 @@ msgid "Write your page here." msgstr "اپنے صفحے کا متن یہاں لکھیں۔" msgid "Uncategorized" -msgstr "" +msgstr "بے زمرہ" #. Used for feeds of any format msgid "Updates" @@ -147,3 +147,6 @@ msgstr "مصنفین" msgid "Posts by %s" msgstr "%s کی تحاریر" + +msgid "Toggle navigation" +msgstr "" diff --git a/translations/nikola.messages/zh_CN.po b/translations/nikola.messages/zh_CN.po index 91e8573..650d5cf 100644 --- a/translations/nikola.messages/zh_CN.po +++ b/translations/nikola.messages/zh_CN.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -7,18 +7,19 @@ # Translators: # Translators: # Translators: -# Christopher Meng , 2013 +# Christopher Meng , 2013 # Kade For , 2013 # Kade For , 2013 # Roberto Alsina , 2013 # Roberto Alsina , 2013 +# spaceoi , 2016 msgid "" msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 07:50+0000\n" -"Last-Translator: Chris Warrick \n" +"PO-Revision-Date: 2016-09-24 22:40+0000\n" +"Last-Translator: Roberto Alsina \n" "Language-Team: Chinese (China) (http://www.transifex.com/ralsina/nikola/language/zh_CN/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -27,7 +28,7 @@ msgstr "" "Plural-Forms: nplurals=1; plural=0;\n" msgid "More posts about %s" -msgstr "更多相关文章: %s" +msgstr "更多 %s 相关文章" #. Here your translation should NOT say English but the name of your language #. instead @@ -44,7 +45,7 @@ msgid "Tags and Categories" msgstr "标签和分类" msgid "Comments" -msgstr "" +msgstr "注释" msgid "Read more" msgstr "更多" @@ -59,7 +60,7 @@ msgid "old posts, page %d" msgstr "旧文章页 %d" msgid "page %d" -msgstr "" +msgstr "第 %d 页" msgid "Source" msgstr "源代码" @@ -82,7 +83,7 @@ msgid "Also available in:" msgstr "其他语言版本:" msgid "Languages:" -msgstr "" +msgstr "语言:" msgid "Original site" msgstr "原文地址" @@ -94,7 +95,7 @@ msgid "Archive" msgstr "文章存档" msgid "Publication date" -msgstr "" +msgstr "发布日期" msgid "Posted:" msgstr "发表于:" @@ -103,47 +104,50 @@ msgid "Posts for {month} {year}" msgstr "{year}年{month}月文章" msgid "Posts for {month} {day}, {year}" -msgstr "" +msgstr "{year}年{month}月{day}日文章" msgid "Nothing found." -msgstr "" +msgstr "没有找到。" msgid "No posts found." -msgstr "" +msgstr "没有找到文章" msgid "RSS feed" -msgstr "" +msgstr "RSS 源" msgid "%d min remaining to read" -msgstr "" +msgstr "剩余 %d 分钟去阅读" msgid "Skip to main content" -msgstr "" +msgstr "跳到主内容" msgid "Subcategories:" -msgstr "" +msgstr "子类别:" #. Used for active navigation links, for screen readers only msgid "(active)" -msgstr "" +msgstr "(活跃)" #. Default content for a new post msgid "Write your post here." -msgstr "" +msgstr "在这里书写你的文章。" #. Default content for a new page msgid "Write your page here." -msgstr "" +msgstr "在这里书写你的页面。" msgid "Uncategorized" -msgstr "" +msgstr "未分类" #. Used for feeds of any format msgid "Updates" -msgstr "" +msgstr "更新" msgid "Authors" -msgstr "" +msgstr "作者" msgid "Posts by %s" +msgstr "%s 发布" + +msgid "Toggle navigation" msgstr "" diff --git a/translations/nikola.messages/zh_TW.po b/translations/nikola.messages/zh_TW.po index 63155bf..1b1653f 100644 --- a/translations/nikola.messages/zh_TW.po +++ b/translations/nikola.messages/zh_TW.po @@ -1,5 +1,5 @@ # Messages in Nikola -# Copyright (C) 2012-2013 +# Copyright (C) 2012-2016 # This file is distributed under the same license as the Nikola package. # # Translators: @@ -7,13 +7,15 @@ # Translators: # Translators: # Translators: +# Gafung Fong , 2016 +# Po-Wen Chi , 2016 msgid "" msgstr "" "Project-Id-Version: Nikola\n" "Report-Msgid-Bugs-To: ralsina@netmanagers.com.ar\n" "POT-Creation-Date: 2013-03-15 12:24-0300\n" -"PO-Revision-Date: 2015-08-31 07:50+0000\n" -"Last-Translator: Chris Warrick \n" +"PO-Revision-Date: 2016-10-07 05:52+0000\n" +"Last-Translator: Gafung Fong \n" "Language-Team: Chinese (Taiwan) (http://www.transifex.com/ralsina/nikola/language/zh_TW/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -22,123 +24,126 @@ msgstr "" "Plural-Forms: nplurals=1; plural=0;\n" msgid "More posts about %s" -msgstr "" +msgstr "更多 %s 的文章" #. Here your translation should NOT say English but the name of your language #. instead msgid "LANGUAGE" -msgstr "" +msgstr "繁體中文" msgid "Tags" -msgstr "" +msgstr "標籤" msgid "Categories" -msgstr "" +msgstr "分類" msgid "Tags and Categories" -msgstr "" +msgstr "標籤和分類" msgid "Comments" -msgstr "" +msgstr "迴響" msgid "Read more" -msgstr "" +msgstr "閱讀更多" msgid "Posts about %s" -msgstr "" +msgstr "文章分類:%s" msgid "Next post" -msgstr "" +msgstr "下一篇" msgid "old posts, page %d" -msgstr "" +msgstr "舊文章,第 %d 頁" msgid "page %d" -msgstr "" +msgstr "第 %d 頁" msgid "Source" -msgstr "" +msgstr "原始碼" #. Here your translation should NOT say English but the name of your language #. instead msgid "Read in English" -msgstr "" +msgstr "繁體中文版" msgid "Posts for year %s" -msgstr "" +msgstr "%s 年的文章" msgid "Newer posts" -msgstr "" +msgstr "較新的文章" msgid "Previous post" -msgstr "" +msgstr "上一篇" msgid "Also available in:" -msgstr "" +msgstr "其他語言版本:" msgid "Languages:" -msgstr "" +msgstr "語言:" msgid "Original site" -msgstr "" +msgstr "原始網站" msgid "Older posts" -msgstr "" +msgstr "較舊的文章" msgid "Archive" -msgstr "" +msgstr "彙整" msgid "Publication date" -msgstr "" +msgstr "發佈日期" msgid "Posted:" -msgstr "" +msgstr "發佈於:" msgid "Posts for {month} {year}" -msgstr "" +msgstr "{year}年{month}月的文章" msgid "Posts for {month} {day}, {year}" -msgstr "" +msgstr "{year}年{month}月{day}日的文章" msgid "Nothing found." -msgstr "" +msgstr "沒有找到。" msgid "No posts found." -msgstr "" +msgstr "沒有找到文章。" msgid "RSS feed" -msgstr "" +msgstr "RSS 訂閱" msgid "%d min remaining to read" -msgstr "" +msgstr "尚餘 %d 分鐘" msgid "Skip to main content" -msgstr "" +msgstr "略過主內容" msgid "Subcategories:" -msgstr "" +msgstr "子分類" #. Used for active navigation links, for screen readers only msgid "(active)" -msgstr "" +msgstr "(啟用)" #. Default content for a new post msgid "Write your post here." -msgstr "" +msgstr "從這裡開始編輯文章" #. Default content for a new page msgid "Write your page here." -msgstr "" +msgstr "從這裡開始編輯頁面" msgid "Uncategorized" -msgstr "" +msgstr "未分類" #. Used for feeds of any format msgid "Updates" -msgstr "" +msgstr "更新" msgid "Authors" -msgstr "" +msgstr "作者" msgid "Posts by %s" -msgstr "" +msgstr "%s 發佈" + +msgid "Toggle navigation" +msgstr "切換導航" -- cgit v1.2.3